https://github.com/akkartik/mu/blob/master/subx/apps/pack.subx
   1 # Read a text file of SubX instructions from stdin, and convert it into a list
   2 # of whitespace-separated ascii hex bytes on stdout, suitable to be further
   3 # processed by apps/hex.
   4 #
   5 # To run (from the subx/ directory):
   6 #   $ ./subx translate *.subx apps/pack.subx -o apps/pack
   7 #   $ echo '05/add-to-EAX 0x20/imm32'  |./subx run apps/pack
   8 # Expected output:
   9 #   05 20 00 00 00  # 05/add-to-EAX 0x20/imm32
  10 # The original instruction gets included as a comment at the end of each
  11 # converted line.
  12 #
  13 # There's zero error-checking. For now we assume the input program is valid.
  14 # We'll continue to rely on the C++ version for error messages.
  15 #
  16 # Label definitions and uses are left untouched for a future 'pass'.
  17 
  18 == code
  19 #   instruction                     effective address                                                   register    displacement    immediate
  20 # . op          subop               mod             rm32          base        index         scale       r32
  21 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
  22 
  23 Entry:  # run tests if necessary, convert stdin if not
  24 
  25     # for debugging: run a single test
  26 #?     e8/call test-convert-in-data-segment/disp32
  27 #?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
  28 #?     eb/jump  $main:end/disp8
  29 
  30     # . prolog
  31     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
  32     # - if argc > 1 and argv[1] == "test", then return run_tests()
  33     # . argc > 1
  34     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
  35     7e/jump-if-lesser-or-equal  $run-main/disp8
  36     # . argv[1] == "test"
  37     # . . push args
  38     68/push  "test"/imm32
  39     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
  40     # . . call
  41     e8/call  kernel-string-equal?/disp32
  42     # . . discard args
  43     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
  44     # . check result
  45     3d/compare-EAX  1/imm32
  46     75/jump-if-not-equal  $run-main/disp8
  47     # . run-tests()
  48     e8/call  run-tests/disp32
  49     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
  50     eb/jump  $main:end/disp8
  51 $run-main:
  52     # - otherwise convert stdin
  53     # var ed/EAX : exit-descriptor
  54     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
  55     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
  56     # configure ed to really exit()
  57     # . ed->target = 0
  58     c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
  59     # return convert(Stdin, 1/stdout, 2/stderr, ed)
  60     # . . push args
  61     50/push-EAX/ed
  62     68/push  Stderr/imm32
  63     68/push  Stdout/imm32
  64     68/push  Stdin/imm32
  65     # . . call
  66     e8/call  convert/disp32
  67     # . . discard args
  68     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
  69     # . syscall(exit, 0)
  70     bb/copy-to-EBX  0/imm32
  71 $main:end:
  72     b8/copy-to-EAX  1/imm32/exit
  73     cd/syscall  0x80/imm8
  74 
  75 # - big picture
  76 # We'll operate on each line/instruction in isolation. That way we only need to
  77 # allocate memory for converting a single instruction.
  78 #
  79 # To pack an entire file, convert every segment in it
  80 # To convert a code segment, convert every instruction (line) until the next segment header
  81 # To convert a non-data segment, convert every word until the next segment header
  82 #
  83 # primary state: line
  84 #   stream of 512 bytes; abort if it ever overflows
  85 
  86 # conceptual hierarchy within a line:
  87 #   line = words separated by ' ', maybe followed by comment starting with '#'
  88 #   word = name until '/', then 0 or more metadata separated by '/'
  89 #
  90 # we won't bother saving the internal structure of lines; reparsing should be cheap using three primitives:
  91 #   next-token(stream, delim char) -> slice (start, end pointers)
  92 #   next-token-from-slice(start, end, delim char) -> slice
  93 #   slice-equal?(slice, string)
  94 
  95 convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
  96     # pseudocode:
  97     #   var line = new-stream(512, 1)
  98     #   var in-code? = false
  99     #   while true
 100     #     clear-stream(line)
 101     #     read-line(in, line)
 102     #     if (line->write == 0) break             # end of file
 103     #     var word-slice = next-word(line)
 104     #     if slice-empty?(word-slice)             # whitespace
 105     #       write-stream-buffered(out, line)
 106     #     else if (slice-equal?(word-slice, "=="))
 107     #       word-slice = next-word(line)
 108     #       in-code? = slice-equal?(word-slice, "code")
 109     #       write-stream-buffered(out, line)
 110     #     else if (in-code?)
 111     #       rewind-stream(line)
 112     #       convert-instruction(line, out)
 113     #     else
 114     #       rewind-stream(line)
 115     #       convert-data(line, out)
 116     #   flush(out)
 117     #
 118     # . prolog
 119     55/push-EBP
 120     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 121     # . save registers
 122     50/push-EAX
 123     51/push-ECX
 124     52/push-EDX
 125     53/push-EBX
 126     # var line/ECX : (address stream byte) = stream(512)
 127     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x200/imm32       # subtract from ESP
 128     68/push  0x200/imm32/length
 129     68/push  0/imm32/read
 130     68/push  0/imm32/write
 131     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 132     # var word-slice/EDX = {0, 0}
 133     68/push  0/imm32/end
 134     68/push  0/imm32/curr
 135     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
 136     # var in-code?/EBX = false
 137     68/push  0/imm32/false
 138     89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
 139 $convert:loop:
 140     # clear-stream(line)
 141     # . . push args
 142     51/push-ECX
 143     # . . call
 144     e8/call  clear-stream/disp32
 145     # . . discard args
 146     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 147     # read-line(in, line)
 148     # . . push args
 149     51/push-ECX
 150     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
 151     # . . call
 152     e8/call  read-line/disp32
 153     # . . discard args
 154     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 155 $convert:check0:
 156     # if (line->write == 0) break
 157     81          7/subop/compare     0/mod/indirect  1/rm32/ECX    .           .             .           .           .               0/imm32           # compare *ECX
 158     0f 84/jump-if-equal  $convert:break/disp32
 159 +-- 34 lines: #?     # dump current line -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 193     # next-word(line, word-slice)
 194     # . . push args
 195     52/push-EDX
 196     51/push-ECX
 197     # . . call
 198     e8/call  next-word/disp32
 199     # . . discard args
 200     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 201 $convert:check1:
 202     # if (slice-empty?(word-slice)) write-stream-buffered(out, line)
 203     # . EAX = slice-empty?(word-slice)
 204     # . . push args
 205     52/push-EDX
 206     # . . call
 207     e8/call  slice-empty?/disp32
 208     # . . discard args
 209     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 210     # . if (EAX != 0) write-stream-buffered(out, line)
 211     3d/compare-EAX  0/imm32
 212     0f 85/jump-if-not-equal  $convert:pass-through/disp32
 213 $convert:check2:
 214 +-- 50 lines: #?     # dump current word -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 264     # if (slice-equal?(word-slice, "=="))
 265     #   word-slice = next-word(line)
 266     #   in-code? = slice-equal?(word-slice, "code")
 267     #   write-stream-buffered(out, line)
 268     # . EAX = slice-equal?(word-slice, "==")
 269     # . . push args
 270     68/push  "=="/imm32
 271     52/push-EDX
 272     # . . call
 273     e8/call  slice-equal?/disp32
 274     # . . discard args
 275     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 276     # . if (EAX == 0) goto check3
 277     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
 278     0f 84/jump-if-equal  $convert:check3/disp32
 279     # . next-word(line, word-slice)
 280     # . . push args
 281     52/push-EDX
 282     51/push-ECX
 283     # . . call
 284     e8/call  next-word/disp32
 285     # . . discard args
 286     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 287 +-- 50 lines: #?     # dump segment name -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 337     # . in-code? = slice-equal?(word-slice, "code")
 338     # . . push args
 339     68/push  "code"/imm32
 340     52/push-EDX
 341     # . . call
 342     e8/call  slice-equal?/disp32
 343     # . . discard args
 344     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 345     # . . in-code? = EAX
 346     89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
 347     # . goto pass-through
 348     eb/jump  $convert:pass-through/disp8
 349 $convert:check3:
 350     # else rewind-stream(line)
 351     # . rewind-stream(line)
 352     # . . push args
 353     51/push-ECX
 354     # . . call
 355     e8/call  rewind-stream/disp32
 356     # . . discard args
 357     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 358     # if (in-code? != 0) convert-instruction(line, out)
 359     81          7/subop/compare     3/mod/direct    3/rm32/EBX    .           .             .           .           .               0/imm32           # compare EBX
 360     74/jump-if-equal  $convert:data/disp8
 361 $convert:code:
 362     # . convert-instruction(line, out)
 363     # . . push args
 364     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 365     51/push-ECX
 366     # . . call
 367     e8/call  convert-instruction/disp32
 368     # . . discard args
 369     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 370     # . loop
 371     e9/jump  $convert:loop/disp32
 372 $convert:data:
 373     # else convert-data(line, out)
 374     # . . push args
 375     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 376     51/push-ECX
 377     # . . call
 378     e8/call  convert-data/disp32
 379     # . . discard args
 380     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 381     # . loop
 382     e9/jump  $convert:loop/disp32
 383 $convert:pass-through:
 384     # write-stream-buffered(out, line)
 385     # . . push args
 386     51/push-ECX
 387     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 388     # . . call
 389     e8/call  write-stream-buffered/disp32
 390     # . . discard args
 391     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 392     # . loop
 393     e9/jump  $convert:loop/disp32
 394 $convert:break:
 395     # flush(out)
 396     # . . push args
 397     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 398     # . . call
 399     e8/call  flush/disp32
 400     # . . discard args
 401     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 402 $convert:end:
 403     # . reclaim locals
 404     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x218/imm32       # add to ESP
 405     # . restore registers
 406     5b/pop-to-EBX
 407     5a/pop-to-EDX
 408     59/pop-to-ECX
 409     58/pop-to-EAX
 410     # . epilog
 411     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 412     5d/pop-to-EBP
 413     c3/return
 414 
 415 test-convert-passes-empty-lines-through:
 416     # if a line is empty, pass it along unchanged
 417     # . prolog
 418     55/push-EBP
 419     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 420     # setup
 421     # . clear-stream(_test-input-stream)
 422     # . . push args
 423     68/push  _test-input-stream/imm32
 424     # . . call
 425     e8/call  clear-stream/disp32
 426     # . . discard args
 427     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 428     # . clear-stream(_test-input-buffered-file+4)
 429     # . . push args
 430     b8/copy-to-EAX  _test-input-buffered-file/imm32
 431     05/add-to-EAX  4/imm32
 432     50/push-EAX
 433     # . . call
 434     e8/call  clear-stream/disp32
 435     # . . discard args
 436     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 437     # . clear-stream(_test-output-stream)
 438     # . . push args
 439     68/push  _test-output-stream/imm32
 440     # . . call
 441     e8/call  clear-stream/disp32
 442     # . . discard args
 443     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 444     # . clear-stream(_test-output-buffered-file+4)
 445     # . . push args
 446     b8/copy-to-EAX  _test-output-buffered-file/imm32
 447     05/add-to-EAX  4/imm32
 448     50/push-EAX
 449     # . . call
 450     e8/call  clear-stream/disp32
 451     # . . discard args
 452     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 453     # write nothing to input
 454     # convert(_test-input-buffered-file, _test-output-buffered-file)
 455     # . . push args
 456     68/push  _test-output-buffered-file/imm32
 457     68/push  _test-input-buffered-file/imm32
 458     # . . call
 459     e8/call  convert/disp32
 460     # . . discard args
 461     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 462     # check that the line just passed through
 463     # . flush(_test-output-buffered-file)
 464     # . . push args
 465     68/push  _test-output-buffered-file/imm32
 466     # . . call
 467     e8/call  flush/disp32
 468     # . . discard args
 469     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 470     # . check-stream-equal(_test-output-stream, "", msg)
 471     # . . push args
 472     68/push  "F - test-convert-passes-empty-lines-through"/imm32
 473     68/push  ""/imm32
 474     68/push  _test-output-stream/imm32
 475     # . . call
 476     e8/call  check-stream-equal/disp32
 477     # . . discard args
 478     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 479     # . epilog
 480     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 481     5d/pop-to-EBP
 482     c3/return
 483 
 484 test-convert-passes-lines-with-just-whitespace-through:
 485     # if a line is empty, pass it along unchanged
 486     # . prolog
 487     55/push-EBP
 488     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 489     # setup
 490     # . clear-stream(_test-input-stream)
 491     # . . push args
 492     68/push  _test-input-stream/imm32
 493     # . . call
 494     e8/call  clear-stream/disp32
 495     # . . discard args
 496     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 497     # . clear-stream(_test-input-buffered-file+4)
 498     # . . push args
 499     b8/copy-to-EAX  _test-input-buffered-file/imm32
 500     05/add-to-EAX  4/imm32
 501     50/push-EAX
 502     # . . call
 503     e8/call  clear-stream/disp32
 504     # . . discard args
 505     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 506     # . clear-stream(_test-output-stream)
 507     # . . push args
 508     68/push  _test-output-stream/imm32
 509     # . . call
 510     e8/call  clear-stream/disp32
 511     # . . discard args
 512     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 513     # . clear-stream(_test-output-buffered-file+4)
 514     # . . push args
 515     b8/copy-to-EAX  _test-output-buffered-file/imm32
 516     05/add-to-EAX  4/imm32
 517     50/push-EAX
 518     # . . call
 519     e8/call  clear-stream/disp32
 520     # . . discard args
 521     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 522     # initialize input
 523     # . write(_test-input-stream, "    ")
 524     # . . push args
 525     68/push  "    "/imm32
 526     68/push  _test-input-stream/imm32
 527     # . . call
 528     e8/call  write/disp32
 529     # . . discard args
 530     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 531     # convert(_test-input-buffered-file, _test-output-buffered-file)
 532     # . . push args
 533     68/push  _test-output-buffered-file/imm32
 534     68/push  _test-input-buffered-file/imm32
 535     # . . call
 536     e8/call  convert/disp32
 537     # . . discard args
 538     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 539     # check that the line just passed through
 540     # . flush(_test-output-buffered-file)
 541     # . . push args
 542     68/push  _test-output-buffered-file/imm32
 543     # . . call
 544     e8/call  flush/disp32
 545     # . . discard args
 546     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 547     # . check-next-stream-line-equal(_test-output-stream, "    ", msg)
 548     # . . push args
 549     68/push  "F - test-convert-passes-with-just-whitespace-through"/imm32
 550     68/push  "    "/imm32
 551     68/push  _test-output-stream/imm32
 552     # . . call
 553     e8/call  check-next-stream-line-equal/disp32
 554     # . . discard args
 555     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 556     # . epilog
 557     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 558     5d/pop-to-EBP
 559     c3/return
 560 
 561 test-convert-passes-segment-headers-through:
 562     # if a line starts with '==', pass it along unchanged
 563     # . prolog
 564     55/push-EBP
 565     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 566     # setup
 567     # . clear-stream(_test-input-stream)
 568     # . . push args
 569     68/push  _test-input-stream/imm32
 570     # . . call
 571     e8/call  clear-stream/disp32
 572     # . . discard args
 573     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 574     # . clear-stream(_test-input-buffered-file+4)
 575     # . . push args
 576     b8/copy-to-EAX  _test-input-buffered-file/imm32
 577     05/add-to-EAX  4/imm32
 578     50/push-EAX
 579     # . . call
 580     e8/call  clear-stream/disp32
 581     # . . discard args
 582     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 583     # . clear-stream(_test-output-stream)
 584     # . . push args
 585     68/push  _test-output-stream/imm32
 586     # . . call
 587     e8/call  clear-stream/disp32
 588     # . . discard args
 589     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 590     # . clear-stream(_test-output-buffered-file+4)
 591     # . . push args
 592     b8/copy-to-EAX  _test-output-buffered-file/imm32
 593     05/add-to-EAX  4/imm32
 594     50/push-EAX
 595     # . . call
 596     e8/call  clear-stream/disp32
 597     # . . discard args
 598     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 599     # initialize input
 600     # . write(_test-input-stream, "== abcd")
 601     # . . push args
 602     68/push  "== abcd"/imm32
 603     68/push  _test-input-stream/imm32
 604     # . . call
 605     e8/call  write/disp32
 606     # . . discard args
 607     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 608     # convert(_test-input-buffered-file, _test-output-buffered-file)
 609     # . . push args
 610     68/push  _test-output-buffered-file/imm32
 611     68/push  _test-input-buffered-file/imm32
 612     # . . call
 613     e8/call  convert/disp32
 614     # . . discard args
 615     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 616     # check that the line just passed through
 617     # . flush(_test-output-buffered-file)
 618     # . . push args
 619     68/push  _test-output-buffered-file/imm32
 620     # . . call
 621     e8/call  flush/disp32
 622     # . . discard args
 623     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 624     # . check-stream-equal(_test-output-stream, "== abcd", msg)
 625     # . . push args
 626     68/push  "F - test-convert-passes-segment-headers-through"/imm32
 627     68/push  "== abcd"/imm32
 628     68/push  _test-output-stream/imm32
 629     # . . call
 630     e8/call  check-stream-equal/disp32
 631     # . . discard args
 632     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 633     # . epilog
 634     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 635     5d/pop-to-EBP
 636     c3/return
 637 
 638 test-convert-in-data-segment:
 639     # correctly process lines in the data segment
 640     # . prolog
 641     55/push-EBP
 642     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 643     # setup
 644     # . clear-stream(_test-input-stream)
 645     # . . push args
 646     68/push  _test-input-stream/imm32
 647     # . . call
 648     e8/call  clear-stream/disp32
 649     # . . discard args
 650     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 651     # . clear-stream(_test-input-buffered-file+4)
 652     # . . push args
 653     b8/copy-to-EAX  _test-input-buffered-file/imm32
 654     05/add-to-EAX  4/imm32
 655     50/push-EAX
 656     # . . call
 657     e8/call  clear-stream/disp32
 658     # . . discard args
 659     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 660     # . clear-stream(_test-output-stream)
 661     # . . push args
 662     68/push  _test-output-stream/imm32
 663     # . . call
 664     e8/call  clear-stream/disp32
 665     # . . discard args
 666     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 667     # . clear-stream(_test-output-buffered-file+4)
 668     # . . push args
 669     b8/copy-to-EAX  _test-output-buffered-file/imm32
 670     05/add-to-EAX  4/imm32
 671     50/push-EAX
 672     # . . call
 673     e8/call  clear-stream/disp32
 674     # . . discard args
 675     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 676     # initialize input
 677     # . write(_test-input-stream, "== code")
 678     # . . push args
 679     68/push  "== code"/imm32
 680     68/push  _test-input-stream/imm32
 681     # . . call
 682     e8/call  write/disp32
 683     # . . discard args
 684     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 685     # . write(_test-input-stream, "\n")
 686     # . . push args
 687     68/push  Newline/imm32
 688     68/push  _test-input-stream/imm32
 689     # . . call
 690     e8/call  write/disp32
 691     # . . discard args
 692     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 693     # . write(_test-input-stream, "== data")
 694     # . . push args
 695     68/push  "== data"/imm32
 696     68/push  _test-input-stream/imm32
 697     # . . call
 698     e8/call  write/disp32
 699     # . . discard args
 700     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 701     # . write(_test-input-stream, "\n")
 702     # . . push args
 703     68/push  Newline/imm32
 704     68/push  _test-input-stream/imm32
 705     # . . call
 706     e8/call  write/disp32
 707     # . . discard args
 708     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 709     # . write(_test-input-stream, "3 4/imm32")
 710     # . . push args
 711     68/push  "3 4/imm32"/imm32
 712     68/push  _test-input-stream/imm32
 713     # . . call
 714     e8/call  write/disp32
 715     # . . discard args
 716     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 717     # . write(_test-input-stream, "\n")
 718     # . . push args
 719     68/push  Newline/imm32
 720     68/push  _test-input-stream/imm32
 721     # . . call
 722     e8/call  write/disp32
 723     # . . discard args
 724     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 725     # convert(_test-input-buffered-file, _test-output-buffered-file)
 726     # . . push args
 727     68/push  _test-output-buffered-file/imm32
 728     68/push  _test-input-buffered-file/imm32
 729     # . . call
 730     e8/call  convert/disp32
 731     # . . discard args
 732     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 733     # check output
 734 +-- 34 lines: #?     # debug print -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 768     # . flush(_test-output-buffered-file)
 769     # . . push args
 770     68/push  _test-output-buffered-file/imm32
 771     # . . call
 772     e8/call  flush/disp32
 773     # . . discard args
 774     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 775     # . check-next-stream-line-equal(_test-output-stream, "== code", msg)
 776     # . . push args
 777     68/push  "F - test-convert-in-data-segment/0"/imm32
 778     68/push  "== code"/imm32
 779     68/push  _test-output-stream/imm32
 780     # . . call
 781     e8/call  check-next-stream-line-equal/disp32
 782     # . . discard args
 783     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 784     # . check-next-stream-line-equal(_test-output-stream, "== data", msg)
 785     # . . push args
 786     68/push  "F - test-convert-in-data-segment/1"/imm32
 787     68/push  "== data"/imm32
 788     68/push  _test-output-stream/imm32
 789     # . . call
 790     e8/call  check-next-stream-line-equal/disp32
 791     # . . discard args
 792     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 793     # . check-next-stream-line-equal(_test-output-stream, "03 04 00 00 00 ", msg)
 794     # . . push args
 795     68/push  "F - test-convert-in-data-segment/2"/imm32
 796     68/push  "03 04 00 00 00 "/imm32
 797     68/push  _test-output-stream/imm32
 798     # . . call
 799     e8/call  check-next-stream-line-equal/disp32
 800     # . . discard args
 801     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 802     # . epilog
 803     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 804     5d/pop-to-EBP
 805     c3/return
 806 
 807 convert-data:  # line : (address stream byte), out : (address buffered-file) -> <void>
 808     # pseudocode:
 809     #   while true
 810     #     word-slice = next-word(line)
 811     #     if slice-empty?(word-slice)                 # end of file (maybe including trailing whitespace)
 812     #       break  # skip emitting some whitespace
 813     #     if slice-starts-with?(word-slice, "#")      # comment
 814     #       write-stream-buffered(out, line)
 815     #       break
 816     #     if slice-ends-with?(word-slice, ":")        # label
 817     #       write-stream-buffered(out, line)
 818     #       break
 819     #     if has-metadata?(word-slice, "imm32")
 820     #       emit(out, word-slice, 4)
 821     #     # disp32 is not permitted in data segments, and anything else is only a byte long
 822     #     else
 823     #       emit(out, word-slice, 1)
 824     #
 825     # . prolog
 826     55/push-EBP
 827     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 828     # . save registers
 829     50/push-EAX
 830     51/push-ECX
 831     52/push-EDX
 832     # var word-slice/ECX = {0, 0}
 833     68/push  0/imm32/end
 834     68/push  0/imm32/start
 835     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 836 +-- 26 lines: #?     # dump line -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 862 $convert-data:loop:
 863     # next-word(line, word-slice)
 864     # . . push args
 865     51/push-ECX
 866     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
 867     # . . call
 868     e8/call  next-word/disp32
 869     # . . discard args
 870     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 871 +-- 26 lines: #?     # dump word-slice -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 897 $convert-data:check0:
 898     # if (slice-empty?(word-slice)) break
 899     # . EAX = slice-empty?(word-slice)
 900     # . . push args
 901     51/push-ECX
 902     # . . call
 903     e8/call  slice-empty?/disp32
 904     # . . discard args
 905     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 906     # . if (EAX != 0) pass through
 907     3d/compare-EAX  0/imm32
 908     75/jump-if-not-equal  $convert-data:break/disp8
 909 $convert-data:check1:
 910     # if (slice-starts-with?(word-slice, "#")) write-stream-buffered(out, line)
 911     # . start/EDX = word-slice->start
 912     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
 913     # . c/EAX = *start
 914     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 915     8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/AL    .               .                 # copy byte at *EDX to AL
 916     # . if (EAX == '#') pass through
 917     3d/compare-with-EAX  0x23/imm32/hash
 918     74/jump-if-equal  $convert-data:pass-line-through/disp8
 919 $convert-data:check2:
 920     # if (slice-ends-with?(word-slice, ":")) write-stream-buffered(out, line)
 921     # . end/EDX = word-slice->end
 922     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ECX+4) to EDX
 923     # . c/EAX = *(end-1)
 924     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 925     8a/copy-byte                    1/mod/*+disp8   2/rm32/EDX    .           .             .           0/r32/AL    -1/disp8        .                 # copy byte at *ECX to AL
 926     # . if (EAX == ':') pass through
 927     3d/compare-with-EAX  0x3a/imm32/colon
 928     74/jump-if-equal  $convert-data:pass-line-through/disp8
 929 $convert-data:check3:
 930     # if (has-metadata?(word-slice, "imm32"))
 931     # . EAX = has-metadata?(ECX, "c")
 932     # . . push args
 933     68/push  "imm32"/imm32
 934     51/push-ECX
 935     # . . call
 936     e8/call  has-metadata?/disp32
 937     # . . discard args
 938     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
 939     # . if (EAX == 0) process as a single byte
 940     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
 941     74/jump-if-equal  $convert-data:single-byte/disp8
 942 $convert-data:imm32:
 943     # emit(out, word-slice, 4)
 944     # . . push args
 945     68/push  4/imm32
 946     51/push-ECX
 947     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 948     # . . call
 949     e8/call  emit/disp32
 950     # . . discard args
 951     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 952     e9/jump  $convert-data:loop/disp32
 953 $convert-data:single-byte:
 954     # emit(out, word-slice, 1)
 955     # . . push args
 956     68/push  1/imm32
 957     51/push-ECX
 958     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 959     # . . call
 960     e8/call  emit/disp32
 961     # . . discard args
 962     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 963     e9/jump  $convert-data:loop/disp32
 964 $convert-data:pass-line-through:
 965     # write-stream-buffered(out, line)
 966     # . . push args
 967     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
 968     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 969     # . . call
 970     e8/call  write-stream-buffered/disp32
 971     # . . discard args
 972     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 973     # break
 974 $convert-data:break:
 975 $convert-data:end:
 976     # . reclaim locals
 977     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 978     # . restore registers
 979     5a/pop-to-EDX
 980     59/pop-to-ECX
 981     58/pop-to-EAX
 982     # . epilog
 983     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 984     5d/pop-to-EBP
 985     c3/return
 986 
 987 test-convert-data-passes-comments-through:
 988     # if a line starts with '#', pass it along unchanged
 989     # . prolog
 990     55/push-EBP
 991     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 992     # setup
 993     # . clear-stream(_test-input-stream)
 994     # . . push args
 995     68/push  _test-input-stream/imm32
 996     # . . call
 997     e8/call  clear-stream/disp32
 998     # . . discard args
 999     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1000     # . clear-stream(_test-output-stream)
1001     # . . push args
1002     68/push  _test-output-stream/imm32
1003     # . . call
1004     e8/call  clear-stream/disp32
1005     # . . discard args
1006     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1007     # . clear-stream(_test-output-buffered-file+4)
1008     # . . push args
1009     b8/copy-to-EAX  _test-buffered-file/imm32
1010     05/add-to-EAX  4/imm32
1011     50/push-EAX
1012     # . . call
1013     e8/call  clear-stream/disp32
1014     # . . discard args
1015     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1016     # initialize input
1017     # . write(_test-input-stream, "# abcd")
1018     # . . push args
1019     68/push  "# abcd"/imm32
1020     68/push  _test-input-stream/imm32
1021     # . . call
1022     e8/call  write/disp32
1023     # . . discard args
1024     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1025     # convert-data(_test-input-stream, _test-output-buffered-file)
1026     # . . push args
1027     68/push  _test-output-buffered-file/imm32
1028     68/push  _test-input-stream/imm32
1029     # . . call
1030     e8/call  convert-data/disp32
1031     # . . discard args
1032     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1033     # check that the line just passed through
1034     # . flush(_test-output-buffered-file)
1035     # . . push args
1036     68/push  _test-output-buffered-file/imm32
1037     # . . call
1038     e8/call  flush/disp32
1039     # . . discard args
1040     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1041     # . check-stream-equal(_test-output-stream, "# abcd", msg)
1042     # . . push args
1043     68/push  "F - test-convert-data-passes-comments-through"/imm32
1044     68/push  "# abcd"/imm32
1045     68/push  _test-output-stream/imm32
1046     # . . call
1047     e8/call  check-stream-equal/disp32
1048     # . . discard args
1049     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1050     # . epilog
1051     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1052     5d/pop-to-EBP
1053     c3/return
1054 
1055 test-convert-data-passes-labels-through:
1056     # if the first word ends with ':', pass along the entire line unchanged
1057     # . prolog
1058     55/push-EBP
1059     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1060     # setup
1061     # . clear-stream(_test-input-stream)
1062     # . . push args
1063     68/push  _test-input-stream/imm32
1064     # . . call
1065     e8/call  clear-stream/disp32
1066     # . . discard args
1067     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1068     # . clear-stream(_test-output-stream)
1069     # . . push args
1070     68/push  _test-output-stream/imm32
1071     # . . call
1072     e8/call  clear-stream/disp32
1073     # . . discard args
1074     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1075     # . clear-stream(_test-output-buffered-file+4)
1076     # . . push args
1077     b8/copy-to-EAX  _test-output-buffered-file/imm32
1078     05/add-to-EAX  4/imm32
1079     50/push-EAX
1080     # . . call
1081     e8/call  clear-stream/disp32
1082     # . . discard args
1083     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1084     # initialize input
1085     # . write(_test-input-stream, "ab: # cd")
1086     # . . push args
1087     68/push  "ab: # cd"/imm32
1088     68/push  _test-input-stream/imm32
1089     # . . call
1090     e8/call  write/disp32
1091     # . . discard args
1092     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1093     # convert-data(_test-input-stream, _test-output-buffered-file)
1094     # . . push args
1095     68/push  _test-output-buffered-file/imm32
1096     68/push  _test-input-stream/imm32
1097     # . . call
1098     e8/call  convert-data/disp32
1099     # . . discard args
1100     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1101     # check that the line just passed through
1102     # . flush(_test-output-buffered-file)
1103     # . . push args
1104     68/push  _test-output-buffered-file/imm32
1105     # . . call
1106     e8/call  flush/disp32
1107     # . . discard args
1108     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1109     # . check-stream-equal(_test-output-stream, "ab: # cd", msg)
1110     # . . push args
1111     68/push  "F - test-convert-data-passes-labels-through"/imm32
1112     68/push  "ab: # cd"/imm32
1113     68/push  _test-output-stream/imm32
1114     # . . call
1115     e8/call  check-stream-equal/disp32
1116     # . . discard args
1117     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1118     # . epilog
1119     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1120     5d/pop-to-EBP
1121     c3/return
1122 
1123 test-convert-data-passes-names-through:
1124     # If a word is a valid name, just emit it unchanged.
1125     # Later phases will deal with it.
1126     # . prolog
1127     55/push-EBP
1128     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1129     # setup
1130     # . clear-stream(_test-input-stream)
1131     # . . push args
1132     68/push  _test-input-stream/imm32
1133     # . . call
1134     e8/call  clear-stream/disp32
1135     # . . discard args
1136     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1137     # . clear-stream(_test-output-stream)
1138     # . . push args
1139     68/push  _test-output-stream/imm32
1140     # . . call
1141     e8/call  clear-stream/disp32
1142     # . . discard args
1143     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1144     # . clear-stream(_test-output-buffered-file+4)
1145     # . . push args
1146     b8/copy-to-EAX  _test-output-buffered-file/imm32
1147     05/add-to-EAX  4/imm32
1148     50/push-EAX
1149     # . . call
1150     e8/call  clear-stream/disp32
1151     # . . discard args
1152     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1153     # initialize input
1154     # . write(_test-input-stream, "abcd/imm32")
1155     # . . push args
1156     68/push  "abcd/imm32"/imm32
1157     68/push  _test-input-stream/imm32
1158     # . . call
1159     e8/call  write/disp32
1160     # . . discard args
1161     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1162     # convert-data(_test-input-stream, _test-output-buffered-file)
1163     # . . push args
1164     68/push  _test-output-buffered-file/imm32
1165     68/push  _test-input-stream/imm32
1166     # . . call
1167     e8/call  convert-data/disp32
1168     # . . discard args
1169     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1170     # check that the line just passed through
1171     # . flush(_test-output-buffered-file)
1172     # . . push args
1173     68/push  _test-output-buffered-file/imm32
1174     # . . call
1175     e8/call  flush/disp32
1176     # . . discard args
1177     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1178     # . check-stream-equal(_test-output-stream, "abcd/imm32", msg)
1179     # . . push args
1180     68/push  "F - test-convert-data-passes-names-through"/imm32
1181     68/push  "abcd/imm32 "/imm32
1182     68/push  _test-output-stream/imm32
1183     # . . call
1184     e8/call  check-stream-equal/disp32
1185     # . . discard args
1186     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1187     # . epilog
1188     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1189     5d/pop-to-EBP
1190     c3/return
1191 
1192 test-convert-data-handles-imm32:
1193     # If a word has the /imm32 metadata, emit it in 4 bytes.
1194     # . prolog
1195     55/push-EBP
1196     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1197     # setup
1198     # . clear-stream(_test-input-stream)
1199     # . . push args
1200     68/push  _test-input-stream/imm32
1201     # . . call
1202     e8/call  clear-stream/disp32
1203     # . . discard args
1204     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1205     # . clear-stream(_test-output-stream)
1206     # . . push args
1207     68/push  _test-output-stream/imm32
1208     # . . call
1209     e8/call  clear-stream/disp32
1210     # . . discard args
1211     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1212     # . clear-stream(_test-output-buffered-file+4)
1213     # . . push args
1214     b8/copy-to-EAX  _test-output-buffered-file/imm32
1215     05/add-to-EAX  4/imm32
1216     50/push-EAX
1217     # . . call
1218     e8/call  clear-stream/disp32
1219     # . . discard args
1220     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1221     # initialize input
1222     # . write(_test-input-stream, "30/imm32")
1223     # . . push args
1224     68/push  "30/imm32"/imm32
1225     68/push  _test-input-stream/imm32
1226     # . . call
1227     e8/call  write/disp32
1228     # . . discard args
1229     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1230     # convert-data(_test-input-stream, _test-output-buffered-file)
1231     # . . push args
1232     68/push  _test-output-buffered-file/imm32
1233     68/push  _test-input-stream/imm32
1234     # . . call
1235     e8/call  convert-data/disp32
1236     # . . discard args
1237     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1238     # check that 4 bytes were written
1239     # . flush(_test-output-buffered-file)
1240     # . . push args
1241     68/push  _test-output-buffered-file/imm32
1242     # . . call
1243     e8/call  flush/disp32
1244     # . . discard args
1245     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1246     # . check-stream-equal(_test-output-stream, "30 00 00 00 ", msg)
1247     # . . push args
1248     68/push  "F - test-convert-data-handles-imm32"/imm32
1249     68/push  "30 00 00 00 "/imm32
1250     68/push  _test-output-stream/imm32
1251     # . . call
1252     e8/call  check-stream-equal/disp32
1253     # . . discard args
1254     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1255     # . epilog
1256     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1257     5d/pop-to-EBP
1258     c3/return
1259 
1260 test-convert-data-handles-single-byte:
1261     # Any metadata but /imm32 will emit a single byte.
1262     # Data segments can't have /disp32, and SubX doesn't support 16-bit operands.
1263     # . prolog
1264     55/push-EBP
1265     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1266     # setup
1267     # . clear-stream(_test-input-stream)
1268     # . . push args
1269     68/push  _test-input-stream/imm32
1270     # . . call
1271     e8/call  clear-stream/disp32
1272     # . . discard args
1273     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1274     # . clear-stream(_test-output-stream)
1275     # . . push args
1276     68/push  _test-output-stream/imm32
1277     # . . call
1278     e8/call  clear-stream/disp32
1279     # . . discard args
1280     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1281     # . clear-stream(_test-output-buffered-file+4)
1282     # . . push args
1283     b8/copy-to-EAX  _test-output-buffered-file/imm32
1284     05/add-to-EAX  4/imm32
1285     50/push-EAX
1286     # . . call
1287     e8/call  clear-stream/disp32
1288     # . . discard args
1289     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1290     # initialize input
1291     # . write(_test-input-stream, "30/imm16")
1292     # . . push args
1293     68/push  "30/imm16"/imm32
1294     68/push  _test-input-stream/imm32
1295     # . . call
1296     e8/call  write/disp32
1297     # . . discard args
1298     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1299     # convert-data(_test-input-stream, _test-output-buffered-file)
1300     # . . push args
1301     68/push  _test-output-buffered-file/imm32
1302     68/push  _test-input-stream/imm32
1303     # . . call
1304     e8/call  convert-data/disp32
1305     # . . discard args
1306     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1307     # check that a single byte was written (imm16 is not a valid operand type)
1308     # . flush(_test-output-buffered-file)
1309     # . . push args
1310     68/push  _test-output-buffered-file/imm32
1311     # . . call
1312     e8/call  flush/disp32
1313     # . . discard args
1314     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1315     # . check-stream-equal(_test-output-stream, "30 ", msg)
1316     # . . push args
1317     68/push  "F - test-convert-data-handles-single-byte"/imm32
1318     68/push  "30 "/imm32
1319     68/push  _test-output-stream/imm32
1320     # . . call
1321     e8/call  check-stream-equal/disp32
1322     # . . discard args
1323     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1324     # . epilog
1325     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1326     5d/pop-to-EBP
1327     c3/return
1328 
1329 test-convert-data-multiple-bytes:
1330     # Multiple single-byte words in input stream get processed one by one.
1331     # . prolog
1332     55/push-EBP
1333     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1334     # setup
1335     # . clear-stream(_test-input-stream)
1336     # . . push args
1337     68/push  _test-input-stream/imm32
1338     # . . call
1339     e8/call  clear-stream/disp32
1340     # . . discard args
1341     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1342     # . clear-stream(_test-output-stream)
1343     # . . push args
1344     68/push  _test-output-stream/imm32
1345     # . . call
1346     e8/call  clear-stream/disp32
1347     # . . discard args
1348     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1349     # . clear-stream(_test-output-buffered-file+4)
1350     # . . push args
1351     b8/copy-to-EAX  _test-output-buffered-file/imm32
1352     05/add-to-EAX  4/imm32
1353     50/push-EAX
1354     # . . call
1355     e8/call  clear-stream/disp32
1356     # . . discard args
1357     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1358     # initialize input
1359     # . write(_test-input-stream, "1 2")
1360     # . . push args
1361     68/push  "1 2"/imm32
1362     68/push  _test-input-stream/imm32
1363     # . . call
1364     e8/call  write/disp32
1365     # . . discard args
1366     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1367     # convert-data(_test-input-stream, _test-output-buffered-file)
1368     # . . push args
1369     68/push  _test-output-buffered-file/imm32
1370     68/push  _test-input-stream/imm32
1371     # . . call
1372     e8/call  convert-data/disp32
1373     # . . discard args
1374     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1375     # check output
1376     # . flush(_test-output-buffered-file)
1377     # . . push args
1378     68/push  _test-output-buffered-file/imm32
1379     # . . call
1380     e8/call  flush/disp32
1381     # . . discard args
1382     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1383     # . check-stream-equal(_test-output-stream, "01 02 ", msg)
1384     # . . push args
1385     68/push  "F - test-convert-data-multiple-bytes"/imm32
1386     68/push  "01 02 "/imm32
1387     68/push  _test-output-stream/imm32
1388     # . . call
1389     e8/call  check-stream-equal/disp32
1390     # . . discard args
1391     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1392     # . epilog
1393     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1394     5d/pop-to-EBP
1395     c3/return
1396 
1397 test-convert-data-byte-then-name:
1398     # Single-byte word followed by valid name get processed one by one.
1399     # . prolog
1400     55/push-EBP
1401     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1402     # setup
1403     # . clear-stream(_test-input-stream)
1404     # . . push args
1405     68/push  _test-input-stream/imm32
1406     # . . call
1407     e8/call  clear-stream/disp32
1408     # . . discard args
1409     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1410     # . clear-stream(_test-output-stream)
1411     # . . push args
1412     68/push  _test-output-stream/imm32
1413     # . . call
1414     e8/call  clear-stream/disp32
1415     # . . discard args
1416     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1417     # . clear-stream(_test-output-buffered-file+4)
1418     # . . push args
1419     b8/copy-to-EAX  _test-output-buffered-file/imm32
1420     05/add-to-EAX  4/imm32
1421     50/push-EAX
1422     # . . call
1423     e8/call  clear-stream/disp32
1424     # . . discard args
1425     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1426     # initialize input
1427     # . write(_test-input-stream, "30 abcd/o")
1428     # . . push args
1429     68/push  "30 abcd/o"/imm32
1430     68/push  _test-input-stream/imm32
1431     # . . call
1432     e8/call  write/disp32
1433     # . . discard args
1434     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1435     # convert-data(_test-input-stream, _test-output-buffered-file)
1436     # . . push args
1437     68/push  _test-output-buffered-file/imm32
1438     68/push  _test-input-stream/imm32
1439     # . . call
1440     e8/call  convert-data/disp32
1441     # . . discard args
1442     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1443     # check output
1444     # . flush(_test-output-buffered-file)
1445     # . . push args
1446     68/push  _test-output-buffered-file/imm32
1447     # . . call
1448     e8/call  flush/disp32
1449     # . . discard args
1450     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1451     # . check-stream-equal(_test-output-stream, "30 abcd/o ", msg)
1452     # . . push args
1453     68/push  "F - test-convert-data-byte-then-name"/imm32
1454     68/push  "30 abcd/o "/imm32
1455     68/push  _test-output-stream/imm32
1456     # . . call
1457     e8/call  check-stream-equal/disp32
1458     # . . discard args
1459     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1460     # . epilog
1461     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1462     5d/pop-to-EBP
1463     c3/return
1464 
1465 test-convert-data-multiple-words:
1466     # Multiple words in input stream get processed one by one.
1467     # . prolog
1468     55/push-EBP
1469     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1470     # setup
1471     # . clear-stream(_test-input-stream)
1472     # . . push args
1473     68/push  _test-input-stream/imm32
1474     # . . call
1475     e8/call  clear-stream/disp32
1476     # . . discard args
1477     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1478     # . clear-stream(_test-output-stream)
1479     # . . push args
1480     68/push  _test-output-stream/imm32
1481     # . . call
1482     e8/call  clear-stream/disp32
1483     # . . discard args
1484     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1485     # . clear-stream(_test-output-buffered-file+4)
1486     # . . push args
1487     b8/copy-to-EAX  _test-output-buffered-file/imm32
1488     05/add-to-EAX  4/imm32
1489     50/push-EAX
1490     # . . call
1491     e8/call  clear-stream/disp32
1492     # . . discard args
1493     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1494     # initialize input
1495     # . write(_test-input-stream, "30 abcd/o 42e1/imm32")
1496     # . . push args
1497     68/push  "30 abcd/o 42e1/imm32"/imm32
1498     68/push  _test-input-stream/imm32
1499     # . . call
1500     e8/call  write/disp32
1501     # . . discard args
1502     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1503     # convert-data(_test-input-stream, _test-output-buffered-file)
1504     # . . push args
1505     68/push  _test-output-buffered-file/imm32
1506     68/push  _test-input-stream/imm32
1507     # . . call
1508     e8/call  convert-data/disp32
1509     # . . discard args
1510     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1511     # check output
1512     # . flush(_test-output-buffered-file)
1513     # . . push args
1514     68/push  _test-output-buffered-file/imm32
1515     # . . call
1516     e8/call  flush/disp32
1517     # . . discard args
1518     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1519 +-- 34 lines: # dump output ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1553     # . check-stream-equal(_test-output-stream, "30 abcd/o 42 e1 00 00 ", msg)
1554     # . . push args
1555     68/push  "F - test-convert-data-multiple-words"/imm32
1556     68/push  "30 abcd/o e1 42 00 00 "/imm32
1557     68/push  _test-output-stream/imm32
1558     # . . call
1559     e8/call  check-stream-equal/disp32
1560     # . . discard args
1561     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1562     # . epilog
1563     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1564     5d/pop-to-EBP
1565     c3/return
1566 
1567 # pack an instruction, following the C++ version
1568 #
1569 # zero error handling at the moment (continuing to rely on the C++ version for that):
1570 #   missing fields are always 0-filled
1571 #   bytes never mentioned are silently dropped; if you don't provide /mod, /rm32 or /r32 you don't get a 0 ModR/M byte. You get *no* ModR/M byte.
1572 #   may pick up any of duplicate operands in an instruction
1573 #   silently drop extraneous operands
1574 #   unceremoniously abort on non-numeric operands except disp or imm
1575 #   opcodes must be lowercase and zero padded
1576 #   opcodes with misleading operand metadata may get duplicated as operands as well. don't rely on this.
1577 convert-instruction:  # line : (address stream byte), out : (address buffered-file) -> <void>
1578     # pseudocode:
1579     #   # some early exits
1580     #   var word-slice = next-word(line)
1581     #   if slice-empty?(word-slice)
1582     #     write-stream-buffered(out, line)
1583     #     return
1584     #   if slice-starts-with?(word-slice, "#")
1585     #     write-stream-buffered(out, line)
1586     #     return
1587     #   if slice-ends-with?(word-slice, ":")
1588     #     write-stream-buffered(out, line)
1589     #     return
1590     #   # really convert
1591     #   emit-opcodes(line, out)
1592     #   emit-modrm(line, out)
1593     #   emit-sib(line, out)
1594     #   emit-disp(line, out)
1595     #   emit-imm(line, out)
1596     #   emit-line-in-comment(line, out)
1597     #
1598     # . prolog
1599     55/push-EBP
1600     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1601     # . save registers
1602     50/push-EAX
1603     51/push-ECX
1604     52/push-EDX
1605     # var word-slice/ECX = {0, 0}
1606     68/push  0/imm32/end
1607     68/push  0/imm32/start
1608     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1609     # next-word(line, word-slice)
1610     # . . push args
1611     51/push-ECX
1612     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1613     # . . call
1614     e8/call  next-word/disp32
1615     # . . discard args
1616     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1617 $convert-instruction:check0:
1618     # if (slice-empty?(word-slice)) break
1619     # . EAX = slice-empty?(word-slice)
1620     # . . push args
1621     51/push-ECX
1622     # . . call
1623     e8/call  slice-empty?/disp32
1624     # . . discard args
1625     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1626     # . if (EAX != 0) pass through
1627     3d/compare-EAX  0/imm32
1628     75/jump-if-not-equal  $convert-instruction:pass-through/disp8
1629 $convert-instruction:check1:
1630     # if (slice-starts-with?(word-slice, "#")) write-stream-buffered(out, line)
1631     # . start/EDX = word-slice->start
1632     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # copy *ECX to EDX
1633     # . c/EAX = *start
1634     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
1635     8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/AL    .               .                 # copy byte at *EDX to AL
1636     # . if (EAX == '#') pass through
1637     3d/compare-with-EAX  0x23/imm32/hash
1638     74/jump-if-equal  $convert-instruction:pass-through/disp8
1639 $convert-instruction:check2:
1640     # if (slice-ends-with?(word-slice, ":")) write-stream-buffered(out, line)
1641     # . end/EDX = word-slice->end
1642     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ECX+4) to EDX
1643     # . c/EAX = *(end-1)
1644     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
1645     8a/copy-byte                    1/mod/*+disp8   2/rm32/EDX    .           .             .           0/r32/AL    -1/disp8        .                 # copy byte at *ECX to AL
1646     # . if (EAX == ':') pass through
1647     3d/compare-with-EAX  0x3a/imm32/colon
1648     74/jump-if-equal  $convert-instruction:pass-through/disp8
1649 $convert-instruction:pass-through:
1650     # write-stream-buffered(out, line)
1651     # . . push args
1652     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1653     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
1654     # . . call
1655     e8/call  write-stream-buffered/disp32
1656     # . . discard args
1657     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1658     # return
1659     eb/jump  $convert-instruction:end/disp8
1660 $convert-instruction:really-convert:
1661     # emit-opcodes(line, out)
1662     # . . push args
1663     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+8)
1664     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1665     # . . call
1666     e8/call  emit-opcodes/disp32
1667     # . . discard args
1668     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1669     # emit-modrm(line, out)
1670     # . . push args
1671     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+8)
1672     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1673     # . . call
1674     e8/call  emit-modrm/disp32
1675     # . . discard args
1676     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1677     # emit-sib(line, out)
1678     # . . push args
1679     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+8)
1680     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1681     # . . call
1682     e8/call  emit-sib/disp32
1683     # . . discard args
1684     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1685     # emit-disp(line, out)
1686     # . . push args
1687     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+8)
1688     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1689     # . . call
1690     e8/call  emit-disp/disp32
1691     # . . discard args
1692     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1693     # emit-imm(line, out)
1694     # . . push args
1695     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+8)
1696     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1697     # . . call
1698     e8/call  emit-imm/disp32
1699     # . . discard args
1700     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1701     # emit-line-in-comment(line, out)
1702     # . . push args
1703     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+8)
1704     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1705     # . . call
1706     e8/call  emit-line-in-comment/disp32
1707     # . . discard args
1708     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1709 $convert-instruction:end:
1710     # . restore registers
1711     5a/pop-to-EDX
1712     59/pop-to-ECX
1713     58/pop-to-EAX
1714     # . epilog
1715     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1716     5d/pop-to-EBP
1717     c3/return
1718 
1719 emit-opcodes:  # line : (address stream byte), out : (address buffered-file) -> <void>
1720     # pseudocode:
1721     #   rewind-stream(line)
1722     #   var op1 = word-slice
1723     #   write-slice(out, op1)
1724     #   if slice-equal?(op1, "0f") or slice-equal?(op1, "f2") or slice-equal?(op1, "f3")
1725     #     var op2 = next-word(line)
1726     #     if slice-empty?(op2)
1727     #       return
1728     #     if slice-starts-with?(op2, "#")
1729     #       return
1730     #     write-slice(out, op2)
1731     #     if slice-equal?(op1, "f2") or slice-equal?(op1, "f3")
1732     #       if slice-equal?(op2, "0f")
1733     #         var op3 = next-word(line)
1734     #         if slice-empty?(op3)
1735     #           return
1736     #         if slice-starts-with?(op2, "#")
1737     #           return
1738     #         write-slice(out, op3)
1739     #
1740     # . prolog
1741     55/push-EBP
1742     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1743     # . save registers
1744 $emit-opcodes:end:
1745     # . restore registers
1746     # . epilog
1747     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1748     5d/pop-to-EBP
1749     c3/return
1750 
1751 emit-modrm:  # line : (address stream byte), out : (address buffered-file) -> <void>
1752     # pseudocode:
1753     #   rewind-stream(line)
1754     #   var has-modrm? = false, mod = 0, rm32 = 0, r32 = 0
1755     #   while true
1756     #     word-slice = next-word(line)
1757     #     if (empty(word-slice)) break
1758     #     if (slice-starts-with?(word-slice, "#")) break
1759     #     if (has-metadata?(word-slice, "mod"))
1760     #       var mod = parse-hex-int(next-token-from-slice(word-slice, "/"))
1761     #       has-modrm? = true
1762     #     else if (has-metadata?(word-slice, "rm32"))
1763     #       var rm32 = parse-hex-int(next-token-from-slice(word-slice, "/"))
1764     #       has-modrm? = true
1765     #     else if (has-metadata?(word-slice, "r32") or has-metadata?(word-slice, "subop"))
1766     #       var r32 = parse-hex-int(next-token-from-slice(word-slice, "/"))
1767     #       has-modrm? = true
1768     #   if has-modrm?
1769     #     var modrm = mod & 0b11
1770     #     modrm <<= 2
1771     #     modrm |= r32 & 0b111
1772     #     modrm <<= 3
1773     #     modrm |= rm32 & 0b111
1774     #     emit-hex(out, modrm, 1)
1775     #
1776     # . prolog
1777     55/push-EBP
1778     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1779     # . save registers
1780 $emit-modrm:end:
1781     # . restore registers
1782     # . epilog
1783     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1784     5d/pop-to-EBP
1785     c3/return
1786 
1787 emit-sib:  # line : (address stream byte), out : (address buffered-file) -> <void>
1788     # pseudocode:
1789     #   var has-sib? = false, base = 0, index = 0, scale = 0
1790     #   while true
1791     #     word-slice = next-word(line)
1792     #     if (empty(word-slice)) break
1793     #     if (slice-starts-with?(word-slice, "#")) break
1794     #     if (has-metadata?(word-slice, "base")
1795     #       var base = parse-hex-int(next-token-from-slice(word-slice, "/"))
1796     #       has-sib? = true
1797     #     else if (has-metadata?(word-slice, "index")
1798     #       var index = parse-hex-int(next-token-from-slice(word-slice, "/"))
1799     #       has-sib? = true
1800     #     else if (has-metadata?(word-slice, "scale")
1801     #       var scale = parse-hex-int(next-token-from-slice(word-slice, "/"))
1802     #       has-sib? = true
1803     #   if has-sib?
1804     #     var sib = scale & 0b11
1805     #     sib <<= 2
1806     #     sib |= index & 0b111
1807     #     sib <<= 3
1808     #     sib |= base & 0b111
1809     #     emit-hex(out, sib, 1)
1810     #
1811     # . prolog
1812     55/push-EBP
1813     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1814     # . save registers
1815 $emit-sib:end:
1816     # . restore registers
1817     # . epilog
1818     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1819     5d/pop-to-EBP
1820     c3/return
1821 
1822 emit-disp:  # line : (address stream byte), out : (address buffered-file) -> <void>
1823     # pseudocode:
1824     #   rewind-stream(line)
1825     #   while true
1826     #     word-slice = next-word(line)
1827     #     if (empty(word-slice)) break
1828     #     if (slice-starts-with?(word-slice, "#")) break
1829     #     if has-metadata?(word-slice, "disp8")
1830     #       var disp = parse-hex-int(next-token-from-slice(word-slice, "/"))
1831     #       emit-hex(out, disp, 1)
1832     #       break
1833     #     else if has-metadata?(word-slice, "disp16")
1834     #       var disp = parse-hex-int(next-token-from-slice(word-slice, "/"))
1835     #       emit-hex(out, disp, 2)
1836     #       break
1837     #     else if has-metadata?(word-slice, "disp32")
1838     #       var disp = parse-hex-int(next-token-from-slice(word-slice, "/"))
1839     #       emit-hex(out, disp, 4)
1840     #       break
1841     #
1842     # . prolog
1843     55/push-EBP
1844     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1845     # . save registers
1846 $emit-disp:end:
1847     # . restore registers
1848     # . epilog
1849     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1850     5d/pop-to-EBP
1851     c3/return
1852 
1853 emit-imm:  # line : (address stream byte), out : (address buffered-file) -> <void>
1854     # pseudocode:
1855     #   rewind-stream(line)
1856     #   while true
1857     #     word-slice = next-word(line)
1858     #     if (slice-starts-with?(word-slice, "#")) break
1859     #     if (empty(word-slice)) break
1860     #     if has-metadata?(word-slice, "imm8")
1861     #       var imm = parse-hex-int(next-token-from-slice(word-slice, "/"))
1862     #       emit-hex(out, imm, 1)
1863     #       break
1864     #     if has-metadata?(word-slice, "imm16")
1865     #       var imm = parse-hex-int(next-token-from-slice(word-slice, "/"))
1866     #       emit-hex(out, imm, 2)
1867     #       break
1868     #     else if has-metadata?(word-slice, "imm32")
1869     #       var imm = parse-hex-int(next-token-from-slice(word-slice, "/"))
1870     #       emit-hex(out, imm, 4)
1871     #       break
1872     #
1873     # . prolog
1874     55/push-EBP
1875     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1876     # . save registers
1877 $emit-imm:end:
1878     # . restore registers
1879     # . epilog
1880     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1881     5d/pop-to-EBP
1882     c3/return
1883 
1884 emit-line-in-comment:  # line : (address stream byte), out : (address buffered-file) -> <void>
1885     # pseudocode:
1886     #   write-buffered(out, "  # ")
1887     #   write-stream-buffered(out, line)
1888     #
1889     # . prolog
1890     55/push-EBP
1891     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1892     # . save registers
1893 $emit-line-in-comment:end:
1894     # . restore registers
1895     # . epilog
1896     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1897     5d/pop-to-EBP
1898     c3/return
1899 
1900 test-convert-instruction-passes-comments-through:
1901     # if a line starts with '#', pass it along unchanged
1902     # . prolog
1903     55/push-EBP
1904     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1905     # setup
1906     # . clear-stream(_test-input-stream)
1907     # . . push args
1908     68/push  _test-input-stream/imm32
1909     # . . call
1910     e8/call  clear-stream/disp32
1911     # . . discard args
1912     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1913     # . clear-stream(_test-output-stream)
1914     # . . push args
1915     68/push  _test-output-stream/imm32
1916     # . . call
1917     e8/call  clear-stream/disp32
1918     # . . discard args
1919     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1920     # . clear-stream(_test-output-buffered-file+4)
1921     # . . push args
1922     b8/copy-to-EAX  _test-buffered-file/imm32
1923     05/add-to-EAX  4/imm32
1924     50/push-EAX
1925     # . . call
1926     e8/call  clear-stream/disp32
1927     # . . discard args
1928     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1929     # initialize input
1930     # . write(_test-input-stream, "# abcd")
1931     # . . push args
1932     68/push  "# abcd"/imm32
1933     68/push  _test-input-stream/imm32
1934     # . . call
1935     e8/call  write/disp32
1936     # . . discard args
1937     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1938     # convert-instruction(_test-input-stream, _test-output-buffered-file)
1939     # . . push args
1940     68/push  _test-output-buffered-file/imm32
1941     68/push  _test-input-stream/imm32
1942     # . . call
1943     e8/call  convert-instruction/disp32
1944     # . . discard args
1945     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1946     # check that the line just passed through
1947     # . flush(_test-output-buffered-file)
1948     # . . push args
1949     68/push  _test-output-buffered-file/imm32
1950     # . . call
1951     e8/call  flush/disp32
1952     # . . discard args
1953     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1954     # . check-stream-equal(_test-output-stream, "# abcd", msg)
1955     # . . push args
1956     68/push  "F - test-convert-instruction-passes-comments-through"/imm32
1957     68/push  "# abcd"/imm32
1958     68/push  _test-output-stream/imm32
1959     # . . call
1960     e8/call  check-stream-equal/disp32
1961     # . . discard args
1962     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1963     # . epilog
1964     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1965     5d/pop-to-EBP
1966     c3/return
1967 
1968 test-convert-instruction-passes-labels-through:
1969     # if the first word ends with ':', pass along the entire line unchanged
1970     # . prolog
1971     55/push-EBP
1972     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1973     # setup
1974     # . clear-stream(_test-input-stream)
1975     # . . push args
1976     68/push  _test-input-stream/imm32
1977     # . . call
1978     e8/call  clear-stream/disp32
1979     # . . discard args
1980     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1981     # . clear-stream(_test-output-stream)
1982     # . . push args
1983     68/push  _test-output-stream/imm32
1984     # . . call
1985     e8/call  clear-stream/disp32
1986     # . . discard args
1987     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1988     # . clear-stream(_test-output-buffered-file+4)
1989     # . . push args
1990     b8/copy-to-EAX  _test-output-buffered-file/imm32
1991     05/add-to-EAX  4/imm32
1992     50/push-EAX
1993     # . . call
1994     e8/call  clear-stream/disp32
1995     # . . discard args
1996     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1997     # initialize input
1998     # . write(_test-input-stream, "ab: # cd")
1999     # . . push args
2000     68/push  "ab: # cd"/imm32
2001     68/push  _test-input-stream/imm32
2002     # . . call
2003     e8/call  write/disp32
2004     # . . discard args
2005     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2006     # convert-instruction(_test-input-stream, _test-output-buffered-file)
2007     # . . push args
2008     68/push  _test-output-buffered-file/imm32
2009     68/push  _test-input-stream/imm32
2010     # . . call
2011     e8/call  convert-instruction/disp32
2012     # . . discard args
2013     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2014     # check that the line just passed through
2015     # . flush(_test-output-buffered-file)
2016     # . . push args
2017     68/push  _test-output-buffered-file/imm32
2018     # . . call
2019     e8/call  flush/disp32
2020     # . . discard args
2021     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2022     # . check-stream-equal(_test-output-stream, "ab: # cd", msg)
2023     # . . push args
2024     68/push  "F - test-convert-instruction-passes-labels-through"/imm32
2025     68/push  "ab: # cd"/imm32
2026     68/push  _test-output-stream/imm32
2027     # . . call
2028     e8/call  check-stream-equal/disp32
2029     # . . discard args
2030     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2031     # . epilog
2032     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2033     5d/pop-to-EBP
2034     c3/return
2035 
2036 # (re)compute the bounds of the next word in the line
2037 # return empty string on reaching end of file
2038 next-word:  # line : (address stream byte), out : (address slice)
2039     # . prolog
2040     55/push-EBP
2041     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2042     # . save registers
2043     50/push-EAX
2044     51/push-ECX
2045     56/push-ESI
2046     57/push-EDI
2047     # ESI = line
2048     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
2049     # EDI = out
2050     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
2051     # skip-chars-matching(line, ' ')
2052     # . . push args
2053     68/push  0x20/imm32/space
2054     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
2055     # . . call
2056     e8/call  skip-chars-matching/disp32
2057     # . . discard args
2058     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2059 $next-word:check0:
2060     # if (line->read >= line->write) clear out and return
2061     # . EAX = line->read
2062     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) to EAX
2063     # . if (EAX < line->write) goto next check
2064     3b/compare                      0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # compare EAX with *ESI
2065     7c/jump-if-lesser  $next-word:check1/disp8
2066     # . return out = {0, 0}
2067     c7          0/subop/copy        0/mod/direct    7/rm32/EDI    .           .             .           .           .               0/imm32           # copy to *EDI
2068     c7          0/subop/copy        1/mod/*+disp8   7/rm32/EDI    .           .             .           .           4/disp8         0/imm32           # copy to *(EDI+4)
2069     eb/jump  $next-word:end/disp8
2070 $next-word:check1:
2071     # out->start = &line->data[line->read]
2072     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
2073     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/EAX   0xc/disp8       .                 # copy ESI+ECX+12 to EAX
2074     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDI
2075     # if (line->data[line->read] == '#') out->end = &line->data[line->write]), skip rest of stream and return
2076     # . EAX = line->data[line->read]
2077     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
2078     8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(ESI+ECX+12) to AL
2079     # . compare
2080     3d/compare-EAX-with  0x23/imm32/pound
2081     75/jump-if-not-equal  $next-word:not-comment/disp8
2082     # . out->end = &line->data[line->write]
2083     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
2084     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  0/index/EAX   .           0/r32/EAX   0xc/disp8       .                 # copy ESI+EAX+12 to EAX
2085     89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDI+4)
2086     # . line->read = line->write
2087     89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(ESI+4)
2088     # . return
2089     eb/jump  $next-word:end/disp8
2090 $next-word:not-comment:
2091     # otherwise skip-chars-not-matching-whitespace(line)
2092     # . . push args
2093     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
2094     # . . call
2095     e8/call  skip-chars-not-matching-whitespace/disp32
2096     # . . discard args
2097     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2098     # out->end = &line->data[line->read]
2099     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
2100     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/EAX   0xc/disp8       .                 # copy ESI+ECX+12 to EAX
2101     89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDI+4)
2102 $next-word:end:
2103     # . restore registers
2104     5f/pop-to-EDI
2105     5e/pop-to-ESI
2106     59/pop-to-ECX
2107     58/pop-to-EAX
2108     # . epilog
2109     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2110     5d/pop-to-EBP
2111     c3/return
2112 
2113 test-next-word:
2114     # . prolog
2115     55/push-EBP
2116     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2117     # setup
2118     # . clear-stream(_test-stream)
2119     # . . push args
2120     68/push  _test-stream/imm32
2121     # . . call
2122     e8/call  clear-stream/disp32
2123     # . . discard args
2124     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2125     # var slice/ECX = {0, 0}
2126     68/push  0/imm32/end
2127     68/push  0/imm32/start
2128     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2129     # write(_test-stream, "  ab")
2130     # . . push args
2131     68/push  "  ab"/imm32
2132     68/push  _test-stream/imm32
2133     # . . call
2134     e8/call  write/disp32
2135     # . . discard args
2136     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2137     # next-word(_test-stream, slice)
2138     # . . push args
2139     51/push-ECX
2140     68/push  _test-stream/imm32
2141     # . . call
2142     e8/call  next-word/disp32
2143     # . . discard args
2144     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2145     # check-ints-equal(slice->start - _test-stream->data, 2, msg)
2146     # . check-ints-equal(slice->start - _test-stream, 14, msg)
2147     # . . push args
2148     68/push  "F - test-next-word: start"/imm32
2149     68/push  0xe/imm32
2150     # . . push slice->start - _test-stream
2151     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
2152     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
2153     50/push-EAX
2154     # . . call
2155     e8/call  check-ints-equal/disp32
2156     # . . discard args
2157     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2158     # check-ints-equal(slice->end - _test-stream->data, 4, msg)
2159     # . check-ints-equal(slice->end - _test-stream, 16, msg)
2160     # . . push args
2161     68/push  "F - test-next-word: end"/imm32
2162     68/push  0x10/imm32
2163     # . . push slice->end - _test-stream
2164     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
2165     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
2166     50/push-EAX
2167     # . . call
2168     e8/call  check-ints-equal/disp32
2169     # . . discard args
2170     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2171     # . epilog
2172     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2173     5d/pop-to-EBP
2174     c3/return
2175 
2176 test-next-word-returns-whole-comment:
2177     # . prolog
2178     55/push-EBP
2179     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2180     # setup
2181     # . clear-stream(_test-stream)
2182     # . . push args
2183     68/push  _test-stream/imm32
2184     # . . call
2185     e8/call  clear-stream/disp32
2186     # . . discard args
2187     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2188     # var slice/ECX = {0, 0}
2189     68/push  0/imm32/end
2190     68/push  0/imm32/start
2191     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2192     # write(_test-stream, "  # a")
2193     # . . push args
2194     68/push  "  # a"/imm32
2195     68/push  _test-stream/imm32
2196     # . . call
2197     e8/call  write/disp32
2198     # . . discard args
2199     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2200     # next-word(_test-stream, slice)
2201     # . . push args
2202     51/push-ECX
2203     68/push  _test-stream/imm32
2204     # . . call
2205     e8/call  next-word/disp32
2206     # . . discard args
2207     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2208     # check-ints-equal(slice->start - _test-stream->data, 2, msg)
2209     # . check-ints-equal(slice->start - _test-stream, 14, msg)
2210     # . . push args
2211     68/push  "F - test-next-word-returns-whole-comment: start"/imm32
2212     68/push  0xe/imm32
2213     # . . push slice->start - _test-stream
2214     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
2215     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
2216     50/push-EAX
2217     # . . call
2218     e8/call  check-ints-equal/disp32
2219     # . . discard args
2220     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2221     # check-ints-equal(slice->end - _test-stream->data, 5, msg)
2222     # . check-ints-equal(slice->end - _test-stream, 17, msg)
2223     # . . push args
2224     68/push  "F - test-next-word-returns-whole-comment: end"/imm32
2225     68/push  0x11/imm32
2226     # . . push slice->end - _test-stream
2227     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
2228     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
2229     50/push-EAX
2230     # . . call
2231     e8/call  check-ints-equal/disp32
2232     # . . discard args
2233     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2234     # . epilog
2235     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2236     5d/pop-to-EBP
2237     c3/return
2238 
2239 test-next-word-returns-empty-string-on-eof:
2240     # . prolog
2241     55/push-EBP
2242     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2243     # setup
2244     # . clear-stream(_test-stream)
2245     # . . push args
2246     68/push  _test-stream/imm32
2247     # . . call
2248     e8/call  clear-stream/disp32
2249     # . . discard args
2250     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2251     # var slice/ECX = {0, 0}
2252     68/push  0/imm32/end
2253     68/push  0/imm32/start
2254     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2255     # write nothing to _test-stream
2256     # next-word(_test-stream, slice)
2257     # . . push args
2258     51/push-ECX
2259     68/push  _test-stream/imm32
2260     # . . call
2261     e8/call  next-word/disp32
2262     # . . discard args
2263     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2264     # check-ints-equal(slice->end - slice->start, 0, msg)
2265     # . . push args
2266     68/push  "F - test-next-word-returns-empty-string-on-eof"/imm32
2267     68/push  0/imm32
2268     # . . push slice->end - slice->start
2269     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
2270     2b/subtract                     0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # subtract *ECX from EAX
2271     50/push-EAX
2272     # . . call
2273     e8/call  check-ints-equal/disp32
2274     # . . discard args
2275     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2276     # . epilog
2277     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2278     5d/pop-to-EBP
2279     c3/return
2280 
2281 has-metadata?:  # word : (address slice), s : (address string) -> EAX : boolean
2282     # pseudocode:
2283     #   var twig : &slice = next-token-from-slice(word->start, word->end, '/')  # skip name
2284     #   curr = twig->end
2285     #   while true
2286     #     twig = next-token-from-slice(curr, word->end, '/')
2287     #     if (twig.empty()) break
2288     #     if (slice-equal?(twig, s)) return true
2289     #     curr = twig->end
2290     #   return false
2291     # . prolog
2292     55/push-EBP
2293     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2294     # . save registers
2295     51/push-ECX
2296     52/push-EDX
2297     56/push-ESI
2298     57/push-EDI
2299     # ESI = word
2300     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
2301     # EDX = word->end
2302     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ESI+4) to EDX
2303     # var twig/EDI : (address slice) = {0, 0}
2304     68/push  0/imm32/end
2305     68/push  0/imm32/start
2306     89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDI
2307     # next-token-from-slice(word->start, word->end, '/', twig)
2308     # . . push args
2309     57/push-EDI
2310     68/push  0x2f/imm32/slash
2311     52/push-EDX
2312     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
2313     # . . call
2314     e8/call  next-token-from-slice/disp32
2315     # . . discard args
2316     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
2317     # curr/ECX = twig->end
2318     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EDI+4) to ECX
2319 $has-metadata?:loop:
2320     # next-token-from-slice(curr, word->end, '/', twig)
2321     # . . push args
2322     57/push-EDI
2323     68/push  0x2f/imm32/slash
2324     52/push-EDX
2325     51/push-ECX
2326     # . . call
2327     e8/call  next-token-from-slice/disp32
2328     # . . discard args
2329     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
2330     # if (slice-empty?(twig)) return false
2331     # . EAX = slice-empty?(twig)
2332     # . . push args
2333     57/push-EDI
2334     # . . call
2335     e8/call  slice-empty?/disp32
2336     # . . discard args
2337     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2338     # . if (EAX != 0) return false
2339     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
2340     75/jump-if-not-equal  $has-metadata?:false/disp8
2341     # if (slice-equal?(twig, s)) return true
2342     # . EAX = slice-equal?(twig, s)
2343     # . . push args
2344     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
2345     57/push-EDI
2346     # . . call
2347     e8/call  slice-equal?/disp32
2348     # . . discard args
2349     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2350     # . if (EAX != 0) return true
2351     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
2352     75/jump-if-not-equal  $has-metadata?:true/disp8
2353     # curr = twig->end
2354     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EDI+4) to ECX
2355     eb/jump  $has-metadata?:loop/disp8
2356 $has-metadata?:true:
2357     b8/copy-to-EAX  1/imm32/true
2358     eb/jump  $has-metadata?:end/disp8
2359 $has-metadata?:false:
2360     b8/copy-to-EAX  0/imm32/false
2361 $has-metadata?:end:
2362     # . reclaim locals
2363     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2364     # . restore registers
2365     5f/pop-to-EDI
2366     5e/pop-to-ESI
2367     5a/pop-to-EDX
2368     59/pop-to-ECX
2369     # . epilog
2370     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2371     5d/pop-to-EBP
2372     c3/return
2373 
2374 test-has-metadata-true:
2375     # . prolog
2376     55/push-EBP
2377     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2378     # (EAX..ECX) = "ab/c"
2379     b8/copy-to-EAX  "ab/c"/imm32
2380     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2381     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
2382     05/add-to-EAX  4/imm32
2383     # var in/ESI : (address slice) = {EAX, ECX}
2384     51/push-ECX
2385     50/push-EAX
2386     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
2387     # EAX = has-metadata?(ESI, "c")
2388     # . . push args
2389     68/push  "c"/imm32
2390     56/push-ESI
2391     # . . call
2392     e8/call  has-metadata?/disp32
2393     # . . discard args
2394     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
2395     # check-ints-equal(EAX, 1, msg)
2396     # . . push args
2397     68/push  "F - test-has-metadata-true"/imm32
2398     68/push  1/imm32/true
2399     50/push-EAX
2400     # . . call
2401     e8/call  check-ints-equal/disp32
2402     # . . discard args
2403     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2404     # . epilog
2405     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2406     5d/pop-to-EBP
2407     c3/return
2408 
2409 test-has-metadata-false:
2410     # . prolog
2411     55/push-EBP
2412     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2413     # (EAX..ECX) = "ab/c"
2414     b8/copy-to-EAX  "ab/c"/imm32
2415     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2416     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
2417     05/add-to-EAX  4/imm32
2418     # var in/ESI : (address slice) = {EAX, ECX}
2419     51/push-ECX
2420     50/push-EAX
2421     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
2422     # EAX = has-metadata?(ESI, "c")
2423     # . . push args
2424     68/push  "d"/imm32
2425     56/push-ESI
2426     # . . call
2427     e8/call  has-metadata?/disp32
2428     # . . discard args
2429     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
2430     # check-ints-equal(EAX, 0, msg)
2431     # . . push args
2432     68/push  "F - test-has-metadata-false"/imm32
2433     68/push  0/imm32/false
2434     50/push-EAX
2435     # . . call
2436     e8/call  check-ints-equal/disp32
2437     # . . discard args
2438     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2439     # . epilog
2440     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2441     5d/pop-to-EBP
2442     c3/return
2443 
2444 test-has-metadata-ignore-name:
2445     # . prolog
2446     55/push-EBP
2447     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2448     # (EAX..ECX) = "a/b"
2449     b8/copy-to-EAX  "a/b"/imm32
2450     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2451     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
2452     05/add-to-EAX  4/imm32
2453     # var in/ESI : (address slice) = {EAX, ECX}
2454     51/push-ECX
2455     50/push-EAX
2456     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
2457     # EAX = has-metadata?(ESI, "a")
2458     # . . push args
2459     68/push  "a"/imm32
2460     56/push-ESI
2461     # . . call
2462     e8/call  has-metadata?/disp32
2463     # . . discard args
2464     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
2465     # check-ints-equal(EAX, 0, msg)
2466     # . . push args
2467     68/push  "F - test-has-metadata-ignore-name"/imm32
2468     68/push  0/imm32/false
2469     50/push-EAX
2470     # . . call
2471     e8/call  check-ints-equal/disp32
2472     # . . discard args
2473     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2474     # . epilog
2475     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2476     5d/pop-to-EBP
2477     c3/return
2478 
2479 test-has-metadata-multiple-true:
2480     # . prolog
2481     55/push-EBP
2482     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2483     # (EAX..ECX) = "a/b/c"
2484     b8/copy-to-EAX  "a/b/c"/imm32
2485     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2486     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
2487     05/add-to-EAX  4/imm32
2488     # var in/ESI : (address slice) = {EAX, ECX}
2489     51/push-ECX
2490     50/push-EAX
2491     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
2492     # EAX = has-metadata?(ESI, "c")
2493     # . . push args
2494     68/push  "c"/imm32
2495     56/push-ESI
2496     # . . call
2497     e8/call  has-metadata?/disp32
2498     # . . discard args
2499     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
2500     # check-ints-equal(EAX, 1, msg)
2501     # . . push args
2502     68/push  "F - test-has-metadata-multiple-true"/imm32
2503     68/push  1/imm32/true
2504     50/push-EAX
2505     # . . call
2506     e8/call  check-ints-equal/disp32
2507     # . . discard args
2508     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2509     # . epilog
2510     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2511     5d/pop-to-EBP
2512     c3/return
2513 
2514 test-has-metadata-multiple-false:
2515     # . prolog
2516     55/push-EBP
2517     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2518     # (EAX..ECX) = "a/b/c"
2519     b8/copy-to-EAX  "a/b/c"/imm32
2520     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2521     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
2522     05/add-to-EAX  4/imm32
2523     # var in/ESI : (address slice) = {EAX, ECX}
2524     51/push-ECX
2525     50/push-EAX
2526     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
2527     # EAX = has-metadata?(ESI, "d")
2528     # . . push args
2529     68/push  "d"/imm32
2530     56/push-ESI
2531     # . . call
2532     e8/call  has-metadata?/disp32
2533     # . . discard args
2534     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
2535     # check-ints-equal(EAX, 0, msg)
2536     # . . push args
2537     68/push  "F - test-has-metadata-multiple-false"/imm32
2538     68/push  0/imm32/false
2539     50/push-EAX
2540     # . . call
2541     e8/call  check-ints-equal/disp32
2542     # . . discard args
2543     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2544     # . epilog
2545     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2546     5d/pop-to-EBP
2547     c3/return
2548 
2549 # If value of 'word' is not a valid name, it must be a hex int. Parse and print
2550 # it in 'width' bytes of hex, least significant first.
2551 # Otherwise just print the entire word including metadata.
2552 # Always print a trailing space.
2553 emit:  # out : (address buffered-file), word : (address slice), width : int -> <void>
2554     # . prolog
2555     55/push-EBP
2556     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2557     # . save registers
2558     50/push-EAX
2559     56/push-ESI
2560     57/push-EDI
2561     # ESI = word
2562     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
2563     # var name/EDI : (address slice) = {0, 0}
2564     68/push  0/imm32/end
2565     68/push  0/imm32/start
2566     89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDI
2567     # name = next-token-from-slice(word->start, word->end, '/')
2568     # . . push args
2569     57/push-EDI
2570     68/push  0x2f/imm32/slash
2571     ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           4/disp8         .                 # push *(ESI+4)
2572     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
2573     # . . call
2574     e8/call  next-token-from-slice/disp32
2575     # . . discard args
2576     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
2577     # if (is-valid-name?(name)) write-slice(out, word) and return
2578     # . EAX = is-valid-name?(name)
2579     # . . push args
2580     57/push-EDI
2581     # . . call
2582     e8/call  is-valid-name?/disp32
2583     # . . discard args
2584     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2585     # . if (EAX != 0)
2586     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
2587     74/jump-if-equal  $emit:hex-int/disp8
2588 $emit:name:
2589     # . write-slice(out, word)
2590     # . . push args
2591     56/push-ESI
2592     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
2593     # . . call
2594     e8/call  write-slice/disp32
2595     # . . discard args
2596     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2597     # . write-buffered(out, " ")
2598     # . . push args
2599     68/push  " "/imm32
2600     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
2601     # . . call
2602     e8/call  write-buffered/disp32
2603     # . . discard args
2604     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2605     # . return
2606     eb/jump  $emit:end/disp8
2607     # otherwise emit-hex(out, parse-hex-int(name), width)
2608     #   (Weird shit can happen here if the value of 'word' isn't either a valid
2609     #   name or a hex number, but we're only going to be passing in real legal
2610     #   programs. We just want to make sure that valid names aren't treated as
2611     #   (valid) hex numbers.)
2612 $emit:hex-int:
2613     # . n/EAX = parse-hex-int(name)
2614     # . . push args
2615     57/push-EDI
2616     # . . call
2617     e8/call  parse-hex-int/disp32
2618     # . . discard args
2619     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2620     # . emit-hex(out, n, width)
2621     # . . push args
2622     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
2623     50/push-EAX
2624     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
2625     # . . call
2626     e8/call  emit-hex/disp32
2627     # . . discard args
2628     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2629 $emit:end:
2630     # . reclaim locals
2631     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2632     # . restore registers
2633     5f/pop-to-EDI
2634     5e/pop-to-ESI
2635     58/pop-to-EAX
2636     # . epilog
2637     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2638     5d/pop-to-EBP
2639     c3/return
2640 
2641 test-emit-number:
2642     # . prolog
2643     55/push-EBP
2644     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2645     # setup
2646     # . clear-stream(_test-stream)
2647     # . . push args
2648     68/push  _test-stream/imm32
2649     # . . call
2650     e8/call  clear-stream/disp32
2651     # . . discard args
2652     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2653     # . clear-stream(_test-buffered-file+4)
2654     # . . push args
2655     b8/copy-to-EAX  _test-buffered-file/imm32
2656     05/add-to-EAX  4/imm32
2657     50/push-EAX
2658     # . . call
2659     e8/call  clear-stream/disp32
2660     # . . discard args
2661     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2662     # var slice/ECX = "30"
2663     68/push  _test-slice-three-zero-end/imm32/end
2664     68/push  _test-slice-three-zero/imm32/start
2665     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2666     # emit(_test-buffered-file, slice, 1)
2667     # . . push args
2668     68/push  1/imm32
2669     51/push-ECX
2670     68/push  _test-buffered-file/imm32
2671     # . . call
2672     e8/call  emit/disp32
2673     # . . discard args
2674     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2675     # flush(_test-buffered-file)
2676     # . . push args
2677     68/push  _test-buffered-file/imm32
2678     # . . call
2679     e8/call  flush/disp32
2680     # . . discard args
2681     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2682     # check-stream-equal(_test-stream, "30 ", msg)
2683     # . . push args
2684     68/push  "F - test-emit-number/1"/imm32
2685     68/push  "30 "/imm32
2686     68/push  _test-stream/imm32
2687     # . . call
2688     e8/call  check-stream-equal/disp32
2689     # . . discard args
2690     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2691     # . epilog
2692     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2693     5d/pop-to-EBP
2694     c3/return
2695 
2696 test-emit-negative-number:
2697     # test support for sign-extending negative numbers
2698     # . prolog
2699     55/push-EBP
2700     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2701     # setup
2702     # . clear-stream(_test-stream)
2703     # . . push args
2704     68/push  _test-stream/imm32
2705     # . . call
2706     e8/call  clear-stream/disp32
2707     # . . discard args
2708     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2709     # . clear-stream(_test-buffered-file+4)
2710     # . . push args
2711     b8/copy-to-EAX  _test-buffered-file/imm32
2712     05/add-to-EAX  4/imm32
2713     50/push-EAX
2714     # . . call
2715     e8/call  clear-stream/disp32
2716     # . . discard args
2717     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2718     # var slice/ECX = "-2"
2719     68/push  _test-slice-negative-two-end/imm32/end
2720     68/push  _test-slice-negative-two/imm32/start
2721     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2722     # emit(_test-buffered-file, slice, 2)
2723     # . . push args
2724     68/push  2/imm32
2725     51/push-ECX
2726     68/push  _test-buffered-file/imm32
2727     # . . call
2728     e8/call  emit/disp32
2729     # . . discard args
2730     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2731     # flush(_test-buffered-file)
2732     # . . push args
2733     68/push  _test-buffered-file/imm32
2734     # . . call
2735     e8/call  flush/disp32
2736     # . . discard args
2737     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2738     # check-stream-equal(_test-stream, "fe ff ", msg)
2739     # . . push args
2740     68/push  "F - test-emit-number/1"/imm32
2741     68/push  "fe ff "/imm32
2742     68/push  _test-stream/imm32
2743     # . . call
2744     e8/call  check-stream-equal/disp32
2745     # . . discard args
2746     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2747     # . epilog
2748     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2749     5d/pop-to-EBP
2750     c3/return
2751 
2752 test-emit-number-with-metadata:
2753     # . prolog
2754     55/push-EBP
2755     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2756     # setup
2757     # . clear-stream(_test-stream)
2758     # . . push args
2759     68/push  _test-stream/imm32
2760     # . . call
2761     e8/call  clear-stream/disp32
2762     # . . discard args
2763     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2764     # . clear-stream(_test-buffered-file+4)
2765     # . . push args
2766     b8/copy-to-EAX  _test-buffered-file/imm32
2767     05/add-to-EAX  4/imm32
2768     50/push-EAX
2769     # . . call
2770     e8/call  clear-stream/disp32
2771     # . . discard args
2772     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2773     # var slice/ECX = "-2/foo"
2774     68/push  _test-slice-negative-two-metadata-end/imm32/end
2775     68/push  _test-slice-negative-two/imm32/start
2776     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2777     # emit(_test-buffered-file, slice, 2)
2778     # . . push args
2779     68/push  2/imm32
2780     51/push-ECX
2781     68/push  _test-buffered-file/imm32
2782     # . . call
2783     e8/call  emit/disp32
2784     # . . discard args
2785     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2786     # flush(_test-buffered-file)
2787     # . . push args
2788     68/push  _test-buffered-file/imm32
2789     # . . call
2790     e8/call  flush/disp32
2791     # . . discard args
2792     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2793     # the '/foo' will have no impact on the output
2794     # check-stream-equal(_test-stream, "fe ff ", msg)
2795     # . . push args
2796     68/push  "F - test-emit-number-with-metadata"/imm32
2797     68/push  "fe ff "/imm32
2798     68/push  _test-stream/imm32
2799     # . . call
2800     e8/call  check-stream-equal/disp32
2801     # . . discard args
2802     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2803     # . epilog
2804     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2805     5d/pop-to-EBP
2806     c3/return
2807 
2808 test-emit-non-number:
2809     # . prolog
2810     55/push-EBP
2811     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2812     # setup
2813     # . clear-stream(_test-stream)
2814     # . . push args
2815     68/push  _test-stream/imm32
2816     # . . call
2817     e8/call  clear-stream/disp32
2818     # . . discard args
2819     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2820     # . clear-stream(_test-buffered-file+4)
2821     # . . push args
2822     b8/copy-to-EAX  _test-buffered-file/imm32
2823     05/add-to-EAX  4/imm32
2824     50/push-EAX
2825     # . . call
2826     e8/call  clear-stream/disp32
2827     # . . discard args
2828     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2829     # var slice/ECX = "xyz"
2830     68/push  _test-slice-non-number-word-end/imm32/end
2831     68/push  _test-slice-non-number-word/imm32/start
2832     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2833     # emit(_test-buffered-file, slice, 2)
2834     # . . push args
2835     68/push  2/imm32
2836     51/push-ECX
2837     68/push  _test-buffered-file/imm32
2838     # . . call
2839     e8/call  emit/disp32
2840     # . . discard args
2841     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2842     # flush(_test-buffered-file)
2843     # . . push args
2844     68/push  _test-buffered-file/imm32
2845     # . . call
2846     e8/call  flush/disp32
2847     # . . discard args
2848     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2849     # check-stream-equal(_test-stream, "xyz", msg)
2850     # . . push args
2851     68/push  "F - test-emit-non-number"/imm32
2852     68/push  "xyz "/imm32
2853     68/push  _test-stream/imm32
2854     # . . call
2855     e8/call  check-stream-equal/disp32
2856     # . . discard args
2857     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2858     # . epilog
2859     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2860     5d/pop-to-EBP
2861     c3/return
2862 
2863 test-emit-non-number-with-metadata:
2864     # . prolog
2865     55/push-EBP
2866     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2867     # setup
2868     # . clear-stream(_test-stream)
2869     # . . push args
2870     68/push  _test-stream/imm32
2871     # . . call
2872     e8/call  clear-stream/disp32
2873     # . . discard args
2874     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2875     # . clear-stream(_test-buffered-file+4)
2876     # . . push args
2877     b8/copy-to-EAX  _test-buffered-file/imm32
2878     05/add-to-EAX  4/imm32
2879     50/push-EAX
2880     # . . call
2881     e8/call  clear-stream/disp32
2882     # . . discard args
2883     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2884     # var slice/ECX = "xyz/"
2885     68/push  _test-slice-non-number-word-metadata-end/imm32/end
2886     68/push  _test-slice-non-number-word/imm32/start
2887     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2888     # emit(_test-buffered-file, slice, 2)
2889     # . . push args
2890     68/push  2/imm32
2891     51/push-ECX
2892     68/push  _test-buffered-file/imm32
2893     # . . call
2894     e8/call  emit/disp32
2895     # . . discard args
2896     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2897     # flush(_test-buffered-file)
2898     # . . push args
2899     68/push  _test-buffered-file/imm32
2900     # . . call
2901     e8/call  flush/disp32
2902     # . . discard args
2903     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2904     # check-stream-equal(_test-stream, "xyz/", msg)
2905     # . . push args
2906     68/push  "F - test-emit-non-number-with-metadata"/imm32
2907     68/push  "xyz/ "/imm32
2908     68/push  _test-stream/imm32
2909     # . . call
2910     e8/call  check-stream-equal/disp32
2911     # . . discard args
2912     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2913     # . epilog
2914     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2915     5d/pop-to-EBP
2916     c3/return
2917 
2918 test-emit-non-number-with-all-hex-digits-and-metadata:
2919     # . prolog
2920     55/push-EBP
2921     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2922     # setup
2923     # . clear-stream(_test-stream)
2924     # . . push args
2925     68/push  _test-stream/imm32
2926     # . . call
2927     e8/call  clear-stream/disp32
2928     # . . discard args
2929     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2930     # . clear-stream(_test-buffered-file+4)
2931     # . . push args
2932     b8/copy-to-EAX  _test-buffered-file/imm32
2933     05/add-to-EAX  4/imm32
2934     50/push-EAX
2935     # . . call
2936     e8/call  clear-stream/disp32
2937     # . . discard args
2938     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2939     # var slice/ECX = "abcd/xyz"
2940     68/push  _test-slice-hexlike-non-number-word-metadata-end/imm32/end
2941     68/push  _test-slice-hexlike-non-number-word/imm32/start
2942     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2943     # emit(_test-buffered-file, slice, 2)
2944     # . . push args
2945     68/push  2/imm32
2946     51/push-ECX
2947     68/push  _test-buffered-file/imm32
2948     # . . call
2949     e8/call  emit/disp32
2950     # . . discard args
2951     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2952     # flush(_test-buffered-file)
2953     # . . push args
2954     68/push  _test-buffered-file/imm32
2955     # . . call
2956     e8/call  flush/disp32
2957     # . . discard args
2958     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2959     # check-stream-equal(_test-stream, "abcd/xyz")
2960     # . . push args
2961     68/push  "F - test-emit-non-number-with-all-hex-digits"/imm32
2962     68/push  "abcd/xyz"/imm32
2963     68/push  _test-stream/imm32
2964     # . . call
2965     e8/call  check-stream-equal/disp32
2966     # . . discard args
2967     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2968     # . epilog
2969     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2970     5d/pop-to-EBP
2971     c3/return
2972 
2973 # conditions for 'valid' names that are not at risk of looking like hex numbers
2974 # keep in sync with the rules in labels.cc
2975 #: - if it starts with a digit, it's treated as a number. If it can't be
2976 #:   parsed as hex it will raise an error.
2977 #: - if it starts with '-' it's treated as a number.
2978 #: - if it starts with '0x' it's treated as a number. (redundant)
2979 #: - if it's two characters long, it can't be a name. Either it's a hex
2980 #:   byte, or it raises an error.
2981 is-valid-name?:  # in : (address slice) -> EAX : boolean
2982     # . prolog
2983     55/push-EBP
2984     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2985     # . save registers
2986     51/push-ECX
2987     56/push-ESI
2988     # ESI = in
2989     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
2990     # start/ECX = in->start
2991     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
2992     # end/EAX = in->end
2993     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) to EAX
2994 $is-valid-name?:check0:
2995     # if (start >= end) return false
2996     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # compare ECX with EAX
2997     7d/jump-if-greater-or-equal  $is-valid-name?:false/disp8
2998 $is-valid-name?:check1:
2999     # EAX -= ECX
3000     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
3001     # if (EAX == 2) return false
3002     3d/compare-with-EAX  2/imm32
3003     74/jump-if-equal  $is-valid-name?:false/disp8
3004 $is-valid-name?:check2:
3005     # c/EAX = *ECX
3006     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
3007     8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
3008     # if (c == "-") return false
3009     3d/compare-with-EAX  2d/imm32/-
3010     74/jump-if-equal  $is-valid-name?:false/disp8
3011 $is-valid-name?:check3a:
3012     # if (c < "0") return true
3013     3d/compare-with-EAX  30/imm32/0
3014     7c/jump-if-lesser  $is-valid-name?:true/disp8
3015 $is-valid-name?:check3b:
3016     # if (c > "9") return true
3017     3d/compare-with-EAX  39/imm32/9
3018     7f/jump-if-greater  $is-valid-name?:true/disp8
3019 $is-valid-name?:false:
3020     # return false
3021     b8/copy-to-EAX  0/imm32/false
3022     eb/jump  $is-valid-name?:end/disp8
3023 $is-valid-name?:true:
3024     # return true
3025     b8/copy-to-EAX  1/imm32/true
3026 $is-valid-name?:end:
3027     # . restore registers
3028     5e/pop-to-ESI
3029     59/pop-to-ECX
3030     # . epilog
3031     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
3032     5d/pop-to-EBP
3033     c3/return
3034 
3035 test-is-valid-name-digit-prefix:
3036     # . prolog
3037     55/push-EBP
3038     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
3039     # var slice/ECX = "34"
3040     68/push  _test-slice-hex-int-end/imm32
3041     68/push  _test-slice-hex-int/imm32
3042     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
3043     # EAX = is-valid-name?(slice)
3044     # . . push args
3045     51/push-ECX
3046     # . . call
3047     e8/call  is-valid-name?/disp32
3048     # . . discard args
3049     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3050     # check-ints-equal(EAX, 0, msg)
3051     # . . push args
3052     68/push  "F - test-is-valid-name-digit-prefix"/imm32
3053     68/push  0/imm32/false
3054     50/push-EAX
3055     # . . call
3056     e8/call  check-ints-equal/disp32
3057     # . . discard args
3058     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3059     # . epilog
3060     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
3061     5d/pop-to-EBP
3062     c3/return
3063 
3064 test-is-valid-name-negative-prefix:
3065     # . prolog
3066     55/push-EBP
3067     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
3068     # var slice/ECX = "-0x34"
3069     68/push  _test-slice-hex-int-with-0x-prefix-end/imm32
3070     68/push  _test-slice-hex-int-with-0x-prefix-negative/imm32
3071     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
3072     # EAX = is-valid-name?(slice)
3073     # . . push args
3074     51/push-ECX
3075     # . . call
3076     e8/call  is-valid-name?/disp32
3077     # . . discard args
3078     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3079     # check-ints-equal(EAX, 0, msg)
3080     # . . push args
3081     68/push  "F - test-is-valid-name-negative-prefix"/imm32
3082     68/push  0/imm32/false
3083     50/push-EAX
3084     # . . call
3085     e8/call  check-ints-equal/disp32
3086     # . . discard args
3087     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3088     # . epilog
3089     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
3090     5d/pop-to-EBP
3091     c3/return
3092 
3093 test-is-valid-name-0x-prefix:
3094     # . prolog
3095     55/push-EBP
3096     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
3097     # var slice/ECX = "0x34"
3098     68/push  _test-slice-hex-int-with-0x-prefix-end/imm32
3099     68/push  _test-slice-hex-int-with-0x-prefix/imm32
3100     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
3101     # EAX = is-valid-name?(slice)
3102     # . . push args
3103     51/push-ECX
3104     # . . call
3105     e8/call  is-valid-name?/disp32
3106     # . . discard args
3107     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3108     # check-ints-equal(EAX, 0, msg)
3109     # . . push args
3110     68/push  "F - test-is-valid-name-0x-prefix"/imm32
3111     68/push  0/imm32/false
3112     50/push-EAX
3113     # . . call
3114     e8/call  check-ints-equal/disp32
3115     # . . discard args
3116     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3117     # . epilog
3118     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
3119     5d/pop-to-EBP
3120     c3/return
3121 
3122 test-is-valid-name-starts-with-pre-digit:
3123     # . prolog
3124     55/push-EBP
3125     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
3126     # var slice/ECX = "/03"
3127     68/push  _test-slice-with-slash-prefix-end/imm32
3128     68/push  _test-slice-with-slash-prefix/imm32
3129     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
3130     # EAX = is-valid-name?(slice)
3131     # . . push args
3132     51/push-ECX
3133     # . . call
3134     e8/call  is-valid-name?/disp32
3135     # . . discard args
3136     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3137     # check-ints-equal(EAX, 1, msg)
3138     # . . push args
3139     68/push  "F - test-is-valid-name-starts-with-pre-digit"/imm32
3140     68/push  1/imm32/true
3141     50/push-EAX
3142     # . . call
3143     e8/call  check-ints-equal/disp32
3144     # . . discard args
3145     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3146     # . epilog
3147     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
3148     5d/pop-to-EBP
3149     c3/return
3150 
3151 test-is-valid-name-starts-with-post-digit:
3152     # . prolog
3153     55/push-EBP
3154     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
3155     # var slice/ECX = "q34"
3156     68/push  _test-slice-char-and-digits-end/imm32
3157     68/push  _test-slice-char-and-digits/imm32
3158     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
3159     # EAX = is-valid-name?(slice)
3160     # . . push args
3161     51/push-ECX
3162     # . . call
3163     e8/call  is-valid-name?/disp32
3164     # . . discard args
3165     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3166     # check-ints-equal(EAX, 1, msg)
3167     # . . push args
3168     68/push  "F - test-is-valid-name-starts-with-post-digit"/imm32
3169     68/push  1/imm32/true
3170     50/push-EAX
3171     # . . call
3172     e8/call  check-ints-equal/disp32
3173     # . . discard args
3174     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3175     # . epilog
3176     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
3177     5d/pop-to-EBP
3178     c3/return
3179 
3180 test-is-valid-name-starts-with-digit:
3181     # . prolog
3182     55/push-EBP
3183     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
3184     # var slice/ECX = "0x34"
3185     68/push  _test-slice-hex-int-with-0x-prefix-end/imm32
3186     68/push  _test-slice-hex-int-with-0x-prefix/imm32
3187     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
3188     # EAX = is-valid-name?(slice)
3189     # . . push args
3190     51/push-ECX
3191     # . . call
3192     e8/call  is-valid-name?/disp32
3193     # . . discard args
3194     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3195     # check-ints-equal(EAX, 0, msg)
3196     # . . push args
3197     68/push  "F - test-is-valid-name-starts-with-digit"/imm32
3198     68/push  0/imm32/false
3199     50/push-EAX
3200     # . . call
3201     e8/call  check-ints-equal/disp32
3202     # . . discard args
3203     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3204     # . epilog
3205     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
3206     5d/pop-to-EBP
3207     c3/return
3208 
3209 # print 'n' in hex in 'width' bytes in lower-endian order, with a space after every byte
3210 emit-hex:  # out : (address buffered-file), n : int, width : int -> <void>
3211     # . prolog
3212     55/push-EBP
3213     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
3214     # . save registers
3215     50/push-EAX
3216     51/push-ECX
3217     52/push-EDX
3218     53/push-EBX
3219     57/push-EDI
3220     # EDI = out
3221     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
3222     # EBX = n
3223     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy *(EBP+12) to EBX
3224     # EDX = width
3225     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0x10/disp8      .                 # copy *(EBP+16) to EDX
3226     # var curr/ECX = 0
3227     31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
3228 $emit-hex:loop:
3229     # if (curr >= width) break
3230     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX and EDX
3231     7d/jump-if-greater-or-equal  $emit-hex:end/disp8
3232 #?     # if (EBX == 0) write(out, "00 ") and continue
3233 #?     81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0/imm32           # compare ECX
3234 #?     75/jump-if-not-equal  $emit-hex:print-octet/disp8
3235 #? $emit-hex:pad-zero:
3236 #?     # . write(out, "00 ")
3237 #?     # . . push args
3238 #?     68/push  "00 "/imm32
3239 #?     57/push-EDI
3240 #?     # . . call
3241 #?     e8/call  write/disp32
3242 #?     # . . discard args
3243 #?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
3244 #?     eb/jump  $emit-hex:continue/disp8
3245 #? $emit-hex:print-octet:
3246     # print-byte(out, EBX)
3247     # . . push args
3248     53/push-EBX
3249     57/push-EDI
3250     # . . call
3251     e8/call  print-byte/disp32
3252     # . . discard args
3253     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
3254     # write-byte(out, ' ')
3255     # . . push args
3256     68/push  0x20/imm32/space
3257     57/push-EDI
3258     # . . call
3259     e8/call  write-byte/disp32
3260     # . . discard args
3261     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
3262     # EBX = EBX >> 8
3263     c1/shift    5/subop/logic-right 3/mod/direct    3/rm32/EBX    .           .             .           .           .               8/imm8            # shift EBX right by 8 bits, while padding zeroes
3264 #? $emit-hex:continue:
3265     # ++curr
3266     41/increment-ECX
3267     eb/jump  $emit-hex:loop/disp8
3268 $emit-hex:end:
3269     # . restore registers
3270     5f/pop-to-EDI
3271     5b/pop-to-EBX
3272     5a/pop-to-EDX
3273     59/pop-to-ECX
3274     58/pop-to-EAX
3275     # . epilog
3276     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
3277     5d/pop-to-EBP
3278     c3/return
3279 
3280 test-emit-hex-single-byte:
3281     # setup
3282     # . clear-stream(_test-stream)
3283     # . . push args
3284     68/push  _test-stream/imm32
3285     # . . call
3286     e8/call  clear-stream/disp32
3287     # . . discard args
3288     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3289     # . clear-stream(_test-buffered-file+4)
3290     # . . push args
3291     b8/copy-to-EAX  _test-buffered-file/imm32
3292     05/add-to-EAX  4/imm32
3293     50/push-EAX
3294     # . . call
3295     e8/call  clear-stream/disp32
3296     # . . discard args
3297     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3298     # emit-hex(_test-buffered-file, 0xab, 1)
3299     # . . push args
3300     68/push  1/imm32
3301     68/push  0xab/imm32
3302     68/push  _test-buffered-file/imm32
3303     # . . call
3304     e8/call  emit-hex/disp32
3305     # . . discard args
3306     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3307     # flush(_test-buffered-file)
3308     # . . push args
3309     68/push  _test-buffered-file/imm32
3310     # . . call
3311     e8/call  flush/disp32
3312     # . . discard args
3313     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3314     # check-ints-equal(*_test-stream->data, 'ab ', msg)
3315     # . . push args
3316     68/push  "F - test-emit-hex-single-byte"/imm32
3317     68/push  0x206261/imm32
3318     # . . push *_test-stream->data
3319     b8/copy-to-EAX  _test-stream/imm32
3320     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
3321     # . . call
3322     e8/call  check-ints-equal/disp32
3323     # . . discard args
3324     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3325     # . end
3326     c3/return
3327 
3328 test-emit-hex-multiple-byte:
3329     # setup
3330     # . clear-stream(_test-stream)
3331     # . . push args
3332     68/push  _test-stream/imm32
3333     # . . call
3334     e8/call  clear-stream/disp32
3335     # . . discard args
3336     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3337     # . clear-stream(_test-buffered-file+4)
3338     # . . push args
3339     b8/copy-to-EAX  _test-buffered-file/imm32
3340     05/add-to-EAX  4/imm32
3341     50/push-EAX
3342     # . . call
3343     e8/call  clear-stream/disp32
3344     # . . discard args
3345     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3346     # emit-hex(_test-buffered-file, 0x1234, 2)
3347     # . . push args
3348     68/push  2/imm32
3349     68/push  0x1234/imm32
3350     68/push  _test-buffered-file/imm32
3351     # . . call
3352     e8/call  emit-hex/disp32
3353     # . . discard args
3354     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3355     # flush(_test-buffered-file)
3356     # . . push args
3357     68/push  _test-buffered-file/imm32
3358     # . . call
3359     e8/call  flush/disp32
3360     # . . discard args
3361     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3362     # check-stream-equal(_test-stream, "34 12 ", msg)
3363     # . . push args
3364     68/push  "F - test-emit-hex-multiple-byte/1"/imm32
3365     68/push  "34 12 "/imm32
3366     68/push  _test-stream/imm32
3367     # . . call
3368     e8/call  check-stream-equal/disp32
3369     # . . discard args
3370     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3371     # . end
3372     c3/return
3373 
3374 test-emit-hex-zero-pad:
3375     # setup
3376     # . clear-stream(_test-stream)
3377     # . . push args
3378     68/push  _test-stream/imm32
3379     # . . call
3380     e8/call  clear-stream/disp32
3381     # . . discard args
3382     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3383     # . clear-stream(_test-buffered-file+4)
3384     # . . push args
3385     b8/copy-to-EAX  _test-buffered-file/imm32
3386     05/add-to-EAX  4/imm32
3387     50/push-EAX
3388     # . . call
3389     e8/call  clear-stream/disp32
3390     # . . discard args
3391     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3392     # emit-hex(_test-buffered-file, 0xab, 2)
3393     # . . push args
3394     68/push  2/imm32
3395     68/push  0xab/imm32
3396     68/push  _test-buffered-file/imm32
3397     # . . call
3398     e8/call  emit-hex/disp32
3399     # . . discard args
3400     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3401     # flush(_test-buffered-file)
3402     # . . push args
3403     68/push  _test-buffered-file/imm32
3404     # . . call
3405     e8/call  flush/disp32
3406     # . . discard args
3407     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3408     # check(_test-stream->data == 'ab 00 ')
3409     # . . push args
3410     68/push  "F - test-emit-hex-zero-pad/1"/imm32
3411     68/push  "ab 00 "/imm32
3412     68/push  _test-stream/imm32
3413     # . . call
3414     e8/call  check-stream-equal/disp32
3415     # . . discard args
3416     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3417     # . end
3418     c3/return
3419 
3420 test-emit-hex-negative:
3421     # setup
3422     # . clear-stream(_test-stream)
3423     # . . push args
3424     68/push  _test-stream/imm32
3425     # . . call
3426     e8/call  clear-stream/disp32
3427     # . . discard args
3428     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3429     # . clear-stream(_test-buffered-file+4)
3430     # . . push args
3431     b8/copy-to-EAX  _test-buffered-file/imm32
3432     05/add-to-EAX  4/imm32
3433     50/push-EAX
3434     # . . call
3435     e8/call  clear-stream/disp32
3436     # . . discard args
3437     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3438     # emit-hex(_test-buffered-file, -1, 2)
3439     # . . push args
3440     68/push  2/imm32
3441     68/push  -1/imm32
3442     68/push  _test-buffered-file/imm32
3443     # . . call
3444     e8/call  emit-hex/disp32
3445     # . . discard args
3446     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3447     # flush(_test-buffered-file)
3448     # . . push args
3449     68/push  _test-buffered-file/imm32
3450     # . . call
3451     e8/call  flush/disp32
3452     # . . discard args
3453     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3454     # check-stream-equal(_test-stream == "ff ff ")
3455     # . . push args
3456     68/push  "F - test-emit-hex-negative/1"/imm32
3457     68/push  "ff ff "/imm32
3458     68/push  _test-stream/imm32
3459     # . . call
3460     e8/call  check-stream-equal/disp32
3461     # . . discard args
3462     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3463     # . end
3464     c3/return
3465 
3466 == data
3467 
3468 _test-slice-negative-two:
3469     2d/- 32/2
3470 _test-slice-negative-two-end:
3471     2f/slash 66/f 6f/o 6f/o
3472 _test-slice-negative-two-metadata-end:
3473 
3474 _test-slice-three-zero:
3475     33/3 30/0
3476 _test-slice-three-zero-end:
3477 
3478 _test-slice-non-number-word:
3479     78/x 79/y 7a/z
3480 _test-slice-non-number-word-end:
3481     2f/slash
3482 _test-slice-non-number-word-metadata-end:
3483 
3484 _test-input-stream:
3485     # current write index
3486     0/imm32
3487     # current read index
3488     0/imm32
3489     # length
3490     0x20/imm32
3491     # data
3492     00 00 00 00 00 00 00 00  # 8 bytes
3493     00 00 00 00 00 00 00 00  # 8 bytes
3494     00 00 00 00 00 00 00 00  # 8 bytes
3495     00 00 00 00 00 00 00 00  # 8 bytes
3496 
3497 # a test buffered file for _test-input-stream
3498 _test-input-buffered-file:
3499     # file descriptor or (address stream)
3500     _test-input-stream/imm32
3501     # current write index
3502     0/imm32
3503     # current read index
3504     0/imm32
3505     # length
3506     6/imm32
3507     # data
3508     00 00 00 00 00 00  # 6 bytes
3509 
3510 _test-output-stream:
3511     # current write index
3512     0/imm32
3513     # current read index
3514     0/imm32
3515     # length
3516     0x20/imm32
3517     # data
3518     00 00 00 00 00 00 00 00  # 8 bytes
3519     00 00 00 00 00 00 00 00  # 8 bytes
3520     00 00 00 00 00 00 00 00  # 8 bytes
3521     00 00 00 00 00 00 00 00  # 8 bytes
3522 
3523 # a test buffered file for _test-output-stream
3524 _test-output-buffered-file:
3525     # file descriptor or (address stream)
3526     _test-output-stream/imm32
3527     # current write index
3528     0/imm32
3529     # current read index
3530     0/imm32
3531     # length
3532     6/imm32
3533     # data
3534     00 00 00 00 00 00  # 6 bytes
3535 
3536 _test-slice-hexlike-non-number-word:
3537     61/a 62/b 63/c 64/d
3538 _test-slice-hexlike-non-number-word-end:
3539     2f/slash
3540     78/x 79/y 7a/z
3541 _test-slice-hexlike-non-number-word-metadata-end:
3542 
3543 _test-slice-with-slash-prefix:
3544   2f/slash 30/0 33/3
3545 _test-slice-with-slash-prefix-end:
3546 
3547 # . . vim:nowrap:textwidth=0