https://github.com/akkartik/mu/blob/master/apps/subx-common.subx
   1 # common helpers shared by phases of the SubX translator
   2 
   3 # - some limits on the programs we can translate
   4 == data
   5 
   6 # maximum memory available for allocation
   7 Heap-size:
   8   0x200000/imm32/2MB
   9 
  10 # maximum size of a single segment
  11 Segment-size:
  12   0x80000/imm32/512KB
  13 
  14 # maximum size of input textual stream (spanning all segments)
  15 Input-size:
  16   0x100000/imm32/1MB
  17 
  18 # maximum size of the 'labels' table in survey.subx
  19 Max-labels:
  20   0x10000/imm32/4K-labels/64KB
  21 
  22 == code
  23 #   instruction                     effective address                                                   register    displacement    immediate
  24 # . op          subop               mod             rm32          base        index         scale       r32
  25 # . 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
  26 
  27 # - managing tables
  28 # SubX has rudimentary support for tables.
  29 #
  30 # Each table is a stream of rows.
  31 #
  32 # Each row consists of a 4-byte row (address to a string) and a variable-size
  33 # value.
  34 #
  35 # Accessing the table performs a linear scan for a key string, and always
  36 # requires passing in the row size.
  37 #
  38 # Table primitives:
  39 #   get(stream, string, row-size)
  40 #     aborts if not found
  41 #   get-or-insert(stream, string, row-size)
  42 #     inserts if not found
  43 #   get-slice(stream, slice, row-size)
  44 #     aborts if not found
  45 #   leaky-get-or-insert-slice(stream, slice, row-size)
  46 #     inserts if not found
  47 
  48 # 'table' is a stream of (key, value) rows
  49 # keys are always strings (addresses; size 4 bytes)
  50 # values may be any type, but rows (key+value) always occupy 'row-size' bytes
  51 # scan 'table' for a row with a key 'key' and return the address of the corresponding value
  52 # if no row is found, abort
  53 get:  # table : (address stream {string, _}), key : (address string), row-size : int -> EAX : (address _)
  54     # pseudocode:
  55     #   curr = table->data
  56     #   max = &table->data[table->write]
  57     #   while curr < max
  58     #     if string-equal?(key, *curr)
  59     #       return curr+4
  60     #     curr += row-size
  61     #   abort
  62     #
  63     # . prolog
  64     55/push-EBP
  65     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
  66     # . save registers
  67     51/push-ECX
  68     52/push-EDX
  69     56/push-ESI
  70     # ESI = table
  71     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
  72     # curr/ECX = table->data
  73     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
  74     # max/EDX = table->data + table->write
  75     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
  76     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
  77 $get:search-loop:
  78     # if (curr >= max) abort
  79     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
  80     73/jump-if-greater-or-equal-unsigned  $get:abort/disp8
  81     # if (string-equal?(key, *curr)) return curr+4
  82     # . EAX = string-equal?(key, *curr)
  83     # . . push args
  84     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
  85     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
  86     # . . call
  87     e8/call  string-equal?/disp32
  88     # . . discard args
  89     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
  90     # . if (EAX != 0) return EAX = curr+4
  91     3d/compare-EAX-and  0/imm32
  92     74/jump-if-equal  $get:mismatch/disp8
  93     8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
  94     eb/jump  $get:end/disp8
  95 $get:mismatch:
  96     # curr += row-size
  97     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
  98     # loop
  99     eb/jump  $get:search-loop/disp8
 100 $get:end:
 101     # . restore registers
 102     5e/pop-to-ESI
 103     5a/pop-to-EDX
 104     59/pop-to-ECX
 105     # . epilog
 106     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 107     5d/pop-to-EBP
 108     c3/return
 109 
 110 $get:abort:
 111     # . _write(2/stderr, error)
 112     # . . push args
 113     68/push  "get: key not found: "/imm32
 114     68/push  2/imm32/stderr
 115     # . . call
 116     e8/call  _write/disp32
 117     # . . discard args
 118     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 119     # . _write(2/stderr, key)
 120     # . . push args
 121     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 122     68/push  2/imm32/stderr
 123     # . . call
 124     e8/call  _write/disp32
 125     # . . discard args
 126     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 127     # . _write(2/stderr, "\n")
 128     # . . push args
 129     68/push  "\n"/imm32
 130     68/push  2/imm32/stderr
 131     # . . call
 132     e8/call  _write/disp32
 133     # . . discard args
 134     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 135     # . syscall(exit, 1)
 136     bb/copy-to-EBX  1/imm32
 137     b8/copy-to-EAX  1/imm32/exit
 138     cd/syscall  0x80/imm8
 139     # never gets here
 140 
 141 test-get:
 142     # . prolog
 143     55/push-EBP
 144     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 145     # - setup: create a table with a couple of keys
 146     # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
 147     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
 148     68/push  0x10/imm32/length
 149     68/push  0/imm32/read
 150     68/push  0/imm32/write
 151     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 152     # insert(table, "code", 8 bytes per row)
 153     # . . push args
 154     68/push  8/imm32/row-size
 155     68/push  "code"/imm32
 156     51/push-ECX
 157     # . . call
 158     e8/call  get-or-insert/disp32
 159     # . . discard args
 160     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 161     # insert(table, "data", 8 bytes per row)
 162     # . . push args
 163     68/push  8/imm32/row-size
 164     68/push  "data"/imm32
 165     51/push-ECX
 166     # . . call
 167     e8/call  get-or-insert/disp32
 168     # . . discard args
 169     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 170 $test-get:check1:
 171     # EAX = get(table, "code", 8 bytes per row)
 172     # . . push args
 173     68/push  8/imm32/row-size
 174     68/push  "code"/imm32
 175     51/push-ECX
 176     # . . call
 177     e8/call  get/disp32
 178     # . . discard args
 179     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 180     # check-ints-equal(EAX - table->data, 4, msg)
 181     # . check-ints-equal(EAX - table, 16, msg)
 182     # . . push args
 183     68/push  "F - test-get/0"/imm32
 184     68/push  0x10/imm32
 185     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 186     50/push-EAX
 187     # . . call
 188     e8/call  check-ints-equal/disp32
 189     # . . discard args
 190     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 191 $test-get:check2:
 192     # EAX = get(table, "data", 8 bytes per row)
 193     # . . push args
 194     68/push  8/imm32/row-size
 195     68/push  "data"/imm32
 196     51/push-ECX
 197     # . . call
 198     e8/call  get/disp32
 199     # . . discard args
 200     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 201     # check-ints-equal(EAX - table->data, 12, msg)
 202     # . check-ints-equal(EAX - table, 24, msg)
 203     # . . push args
 204     68/push  "F - test-get/1"/imm32
 205     68/push  0x18/imm32
 206     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 207     50/push-EAX
 208     # . . call
 209     e8/call  check-ints-equal/disp32
 210     # . . discard args
 211     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 212 $test-get:end:
 213     # . epilog
 214     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 215     5d/pop-to-EBP
 216     c3/return
 217 
 218 # 'table' is a stream of (key, value) rows
 219 # keys are always strings (addresses; size 4 bytes)
 220 # values may be any type, but rows (key+value) always occupy 'row-size' bytes
 221 # scan 'table' for a row with a key 'key' and return the address of the corresponding value
 222 # if no row is found, abort
 223 get-slice:  # table : (address stream {string, _}), key : (address slice), row-size : int -> EAX : (address _)
 224     # pseudocode:
 225     #   curr = table->data
 226     #   max = &table->data[table->write]
 227     #   while curr < max
 228     #     if slice-equal?(key, *curr)
 229     #       return curr+4
 230     #     curr += row-size
 231     #   abort
 232     #
 233     # . prolog
 234     55/push-EBP
 235     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 236     # . save registers
 237     51/push-ECX
 238     52/push-EDX
 239     56/push-ESI
 240     # ESI = table
 241     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 242     # curr/ECX = table->data
 243     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
 244     # max/EDX = table->data + table->write
 245     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
 246     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
 247 $get-slice:search-loop:
 248     # if (curr >= max) abort
 249     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
 250     73/jump-if-greater-or-equal-unsigned  $get-slice:abort/disp8
 251     # if (slice-equal?(key, *curr)) return curr+4
 252     # . EAX = slice-equal?(key, *curr)
 253     # . . push args
 254     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 255     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 256     # . . call
 257     e8/call  slice-equal?/disp32
 258     # . . discard args
 259     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 260     # . if (EAX != 0) return EAX = curr+4
 261     3d/compare-EAX-and  0/imm32
 262     74/jump-if-equal  $get-slice:mismatch/disp8
 263     8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
 264     eb/jump  $get-slice:end/disp8
 265 $get-slice:mismatch:
 266     # curr += row-size
 267     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
 268     # loop
 269     eb/jump  $get-slice:search-loop/disp8
 270 $get-slice:end:
 271     # . restore registers
 272     5e/pop-to-ESI
 273     5a/pop-to-EDX
 274     59/pop-to-ECX
 275     # . epilog
 276     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 277     5d/pop-to-EBP
 278     c3/return
 279 
 280 $get-slice:abort:
 281     # . _write(2/stderr, error)
 282     # . . push args
 283     68/push  "get-slice: key not found: "/imm32
 284     68/push  2/imm32/stderr
 285     # . . call
 286     e8/call  _write/disp32
 287     # . . discard args
 288     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 289     # . write-slice-buffered(Stderr, key)
 290     # . . push args
 291     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 292     68/push  Stderr/imm32
 293     # . . call
 294     e8/call  write-slice-buffered/disp32
 295     # . . discard args
 296     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 297     # . flush(Stderr)
 298     # . . push args
 299     68/push  Stderr/imm32
 300     # . . call
 301     e8/call  flush/disp32
 302     # . . discard args
 303     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 304     # . _write(2/stderr, "\n")
 305     # . . push args
 306     68/push  "\n"/imm32
 307     68/push  2/imm32/stderr
 308     # . . call
 309     e8/call  _write/disp32
 310     # . . discard args
 311     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 312     # . syscall(exit, 1)
 313     bb/copy-to-EBX  1/imm32
 314     b8/copy-to-EAX  1/imm32/exit
 315     cd/syscall  0x80/imm8
 316     # never gets here
 317 
 318 test-get-slice:
 319     # . prolog
 320     55/push-EBP
 321     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 322     # - setup: create a table with a couple of keys
 323     # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
 324     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
 325     68/push  0x10/imm32/length
 326     68/push  0/imm32/read
 327     68/push  0/imm32/write
 328     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 329     # insert(table, "code", 8 bytes per row)
 330     # . . push args
 331     68/push  8/imm32/row-size
 332     68/push  "code"/imm32
 333     51/push-ECX
 334     # . . call
 335     e8/call  get-or-insert/disp32
 336     # . . discard args
 337     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 338     # insert(table, "data", 8 bytes per row)
 339     # . . push args
 340     68/push  8/imm32/row-size
 341     68/push  "data"/imm32
 342     51/push-ECX
 343     # . . call
 344     e8/call  get-or-insert/disp32
 345     # . . discard args
 346     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 347 $test-get-slice:check1:
 348     # (EAX..EDX) = "code"
 349     b8/copy-to-EAX  "code"/imm32
 350     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
 351     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  2/index/EDX   .           2/r32/EDX   4/disp8         .                 # copy EAX+EDX+4 to EDX
 352     05/add-to-EAX  4/imm32
 353     # var slice/EDX = {EAX, EDX}
 354     52/push-EDX
 355     50/push-EAX
 356     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
 357     # EAX = get-slice(table, "code", 8 bytes per row)
 358     # . . push args
 359     68/push  8/imm32/row-size
 360     52/push-EDX
 361     51/push-ECX
 362     # . . call
 363     e8/call  get-slice/disp32
 364     # . . discard args
 365     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 366     # check-ints-equal(EAX - table->data, 4, msg)  # first row's value slot returned
 367     # . check-ints-equal(EAX - table, 16, msg)
 368     # . . push args
 369     68/push  "F - test-get-slice/0"/imm32
 370     68/push  0x10/imm32
 371     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 372     50/push-EAX
 373     # . . call
 374     e8/call  check-ints-equal/disp32
 375     # . . discard args
 376     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 377 $test-get-slice:check2:
 378     # (EAX..EDX) = "data"
 379     b8/copy-to-EAX  "data"/imm32
 380     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
 381     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  2/index/EDX   .           2/r32/EDX   4/disp8         .                 # copy EAX+EDX+4 to EDX
 382     05/add-to-EAX  4/imm32
 383     # var slice/EDX = {EAX, EDX}
 384     52/push-EDX
 385     50/push-EAX
 386     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
 387     # EAX = get-slice(table, "data" slice, 8 bytes per row)
 388     # . . push args
 389     68/push  8/imm32/row-size
 390     52/push-EDX
 391     51/push-ECX
 392     # . . call
 393     e8/call  get-slice/disp32
 394     # . . discard args
 395     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 396     # check-ints-equal(EAX - table->data, 12, msg)
 397     # . check-ints-equal(EAX - table, 24, msg)
 398     # . . push args
 399     68/push  "F - test-get-slice/1"/imm32
 400     68/push  0x18/imm32
 401     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 402     50/push-EAX
 403     # . . call
 404     e8/call  check-ints-equal/disp32
 405     # . . discard args
 406     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 407 $test-get-slice:end:
 408     # . epilog
 409     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 410     5d/pop-to-EBP
 411     c3/return
 412 
 413 # 'table' is a stream of (key, value) rows
 414 # keys are always strings (addresses; size 4 bytes)
 415 # values may be any type, but rows (key+value) always occupy 'row-size' bytes
 416 # scan 'table' for a row with a key 'key' and return the address of the corresponding value
 417 # if no row is found, save 'key' to the next available row
 418 # if there are no rows free, abort
 419 # return the address of the value
 420 # Beware: assume keys are immutable; they're inserted by reference
 421 # TODO: pass in an allocation descriptor
 422 get-or-insert:  # table : (address stream {string, _}), key : (address string), row-size : int -> EAX : (address _)
 423     # pseudocode:
 424     #   curr = table->data
 425     #   max = &table->data[table->write]
 426     #   while curr < max
 427     #     if string-equal?(key, *curr)
 428     #       return curr+4
 429     #     curr += row-size
 430     #   if table->write >= table->length
 431     #     abort
 432     #   zero-out(max, row-size)
 433     #   *max = key
 434     #   table->write += row-size
 435     #   return max+4
 436     #
 437     # . prolog
 438     55/push-EBP
 439     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 440     # . save registers
 441     51/push-ECX
 442     52/push-EDX
 443     56/push-ESI
 444     # ESI = table
 445     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 446     # curr/ECX = table->data
 447     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
 448     # max/EDX = table->data + table->write
 449     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
 450     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
 451 $get-or-insert:search-loop:
 452     # if (curr >= max) break
 453     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
 454     73/jump-if-greater-or-equal-unsigned  $get-or-insert:not-found/disp8
 455     # if (string-equal?(key, *curr)) return curr+4
 456     # . EAX = string-equal?(key, *curr)
 457     # . . push args
 458     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 459     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 460     # . . call
 461     e8/call  string-equal?/disp32
 462     # . . discard args
 463     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 464     # . if (EAX != 0) return EAX = curr+4
 465     3d/compare-EAX-and  0/imm32
 466     74/jump-if-equal  $get-or-insert:mismatch/disp8
 467     8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
 468     eb/jump  $get-or-insert:end/disp8
 469 $get-or-insert:mismatch:
 470     # curr += row-size
 471     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
 472     # loop
 473     eb/jump  $get-or-insert:search-loop/disp8
 474 $get-or-insert:not-found:
 475     # result/EAX = 0
 476     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 477     # if (table->write >= table->length) abort
 478     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
 479     3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # compare ECX with *(ESI+8)
 480     73/jump-if-greater-or-equal-unsigned  $get-or-insert:abort/disp8
 481     # zero-out(max, row-size)
 482     # . . push args
 483     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
 484     52/push-EDX
 485     # . . call
 486     e8/call  zero-out/disp32
 487     # . . discard args
 488     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 489     # *max = key
 490     # . EAX = key
 491     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
 492     # . *max = EAX
 493     89/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDX
 494     # table->write += row-size
 495     # . EAX = row-size
 496     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0x10/disp8      .                 # copy *(EBP+16) to EAX
 497     # . table->write += EAX
 498     01/add                          0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # add EAX to *ESI
 499     # return max+4
 500     # . EAX = max
 501     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy EDX to EAX
 502     # . EAX += 4
 503     05/add-to-EAX  4/imm32
 504 $get-or-insert:end:
 505     # . restore registers
 506     5e/pop-to-ESI
 507     5a/pop-to-EDX
 508     59/pop-to-ECX
 509     # . epilog
 510     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 511     5d/pop-to-EBP
 512     c3/return
 513 
 514 $get-or-insert:abort:
 515     # . _write(2/stderr, error)
 516     # . . push args
 517     68/push  "get-or-insert: table is full\n"/imm32
 518     68/push  2/imm32/stderr
 519     # . . call
 520     e8/call  _write/disp32
 521     # . . discard args
 522     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 523     # . syscall(exit, 1)
 524     bb/copy-to-EBX  1/imm32
 525     b8/copy-to-EAX  1/imm32/exit
 526     cd/syscall  0x80/imm8
 527     # never gets here
 528 
 529 test-get-or-insert:
 530     # . prolog
 531     55/push-EBP
 532     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 533     # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
 534     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
 535     68/push  0x10/imm32/length
 536     68/push  0/imm32/read
 537     68/push  0/imm32/write
 538     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 539 $test-get-or-insert:first-call:
 540     # - start with an empty table, insert one key, verify that it was inserted
 541     # EAX = get-or-insert(table, "code", 8 bytes per row)
 542     # . . push args
 543     68/push  8/imm32/row-size
 544     68/push  "code"/imm32
 545     51/push-ECX
 546     # . . call
 547     e8/call  get-or-insert/disp32
 548     # . . discard args
 549     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 550     # check-ints-equal(EAX - table->data, 4, msg)  # first row's value slot returned
 551     # . check-ints-equal(EAX - table, 16, msg)
 552     # . . push args
 553     68/push  "F - test-get-or-insert/0"/imm32
 554     68/push  0x10/imm32
 555     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 556     50/push-EAX
 557     # . . call
 558     e8/call  check-ints-equal/disp32
 559     # . . discard args
 560     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 561 $test-get-or-insert:check2:
 562     # check-ints-equal(table->write, row-size = 8, msg)
 563     # . . push args
 564     68/push  "F - test-get-or-insert/1"/imm32
 565     68/push  8/imm32/row-size
 566     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 567     # . . call
 568     e8/call  check-ints-equal/disp32
 569     # . . discard args
 570     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 571     # check-string-equal(*table->data, "code", msg)
 572     # . . push args
 573     68/push  "F - test-get-or-insert/2"/imm32
 574     68/push  "code"/imm32
 575     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
 576     # . . call
 577     e8/call  check-string-equal/disp32
 578     # . . discard args
 579     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 580 $test-get-or-insert:second-call:
 581     # - insert the same key again, verify that it was reused
 582     # EAX = get-or-insert(table, "code", 8 bytes per row)
 583     # . . push args
 584     68/push  8/imm32/row-size
 585     68/push  "code"/imm32
 586     51/push-ECX
 587     # . . call
 588     e8/call  get-or-insert/disp32
 589     # . . discard args
 590     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 591     # check-ints-equal(EAX - table->data, 4, msg)
 592     # . check-ints-equal(EAX - table, 16, msg)
 593     # . . push args
 594     68/push  "F - test-get-or-insert/3"/imm32
 595     68/push  0x10/imm32
 596     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 597     50/push-EAX
 598     # . . call
 599     e8/call  check-ints-equal/disp32
 600     # . . discard args
 601     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 602     # no new row inserted
 603     # . check-ints-equal(table->write, row-size = 8, msg)
 604     # . . push args
 605     68/push  "F - test-get-or-insert/4"/imm32
 606     68/push  8/imm32/row-size
 607     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 608     # . . call
 609     e8/call  check-ints-equal/disp32
 610     # . . discard args
 611     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 612     # check-string-equal(*table->data, "code", msg)
 613     # . . push args
 614     68/push  "F - test-get-or-insert/5"/imm32
 615     68/push  "code"/imm32
 616     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
 617     # . . call
 618     e8/call  check-string-equal/disp32
 619     # . . discard args
 620     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 621 $test-get-or-insert:third-call:
 622     # - insert a new key, verify that it was inserted
 623     # EAX = get-or-insert(table, "data", 8 bytes per row)
 624     # . . push args
 625     68/push  8/imm32/row-size
 626     68/push  "data"/imm32
 627     51/push-ECX
 628     # . . call
 629     e8/call  get-or-insert/disp32
 630     # . . discard args
 631     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 632     # table gets a new row
 633     # check-ints-equal(EAX - table->data, 12, msg)  # second row's value slot returned
 634     # . check-ints-equal(EAX - table, 24, msg)
 635     # . . push args
 636     68/push  "F - test-get-or-insert/6"/imm32
 637     68/push  0x18/imm32
 638     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 639     50/push-EAX
 640     # . . call
 641     e8/call  check-ints-equal/disp32
 642     # . . discard args
 643     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 644     # check-ints-equal(table->write, 2 rows = 16, msg)
 645     # . . push args
 646     68/push  "F - test-get-or-insert/7"/imm32
 647     68/push  0x10/imm32/two-rows
 648     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 649     # . . call
 650     e8/call  check-ints-equal/disp32
 651     # . . discard args
 652     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 653     # check-string-equal(*table->data+8, "data", msg)
 654     # check-string-equal(*(table+20), "data", msg)
 655     # . . push args
 656     68/push  "F - test-get-or-insert/8"/imm32
 657     68/push  "data"/imm32
 658     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0x14/disp8      .                 # push *(ECX+20)
 659     # . . call
 660     e8/call  check-string-equal/disp32
 661     # . . discard args
 662     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 663 $test-get-or-insert:end:
 664     # . epilog
 665     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 666     5d/pop-to-EBP
 667     c3/return
 668 
 669 # 'table' is a stream of (key, value) rows
 670 # keys are always strings (addresses; size 4 bytes)
 671 # values may be any type, but rows (key+value) always occupy 'row-size' bytes
 672 # scan 'table' for a row with a key 'key' and return the address of the corresponding value
 673 # if no row is found, save 'key' in the next available row
 674 # if there are no rows free, abort
 675 # WARNING: leaks memory
 676 # TODO: pass in an allocation descriptor
 677 leaky-get-or-insert-slice:  # table : (address stream {string, _}), key : (address slice), row-size : int -> EAX : (address _)
 678     # pseudocode:
 679     #   curr = table->data
 680     #   max = &table->data[table->write]
 681     #   while curr < max
 682     #     if slice-equal?(key, *curr)
 683     #       return curr+4
 684     #     curr += row-size
 685     #   if table->write >= table->length
 686     #     abort
 687     #   zero-out(max, row-size)
 688     #   *max = slice-to-string(Heap, key)
 689     #   table->write += row-size
 690     #   return max+4
 691     #
 692     # . prolog
 693     55/push-EBP
 694     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 695     # . save registers
 696     51/push-ECX
 697     52/push-EDX
 698     56/push-ESI
 699     # ESI = table
 700     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 701     # curr/ECX = table->data
 702     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
 703     # max/EDX = table->data + table->write
 704     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
 705     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
 706 $leaky-get-or-insert-slice:search-loop:
 707     # if (curr >= max) break
 708     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
 709     73/jump-if-greater-or-equal-unsigned  $leaky-get-or-insert-slice:not-found/disp8
 710     # if (slice-equal?(key, *curr)) return curr+4
 711     # . EAX = slice-equal?(key, *curr)
 712     # . . push args
 713     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 714     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 715     # . . call
 716     e8/call  slice-equal?/disp32
 717     # . . discard args
 718     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 719     # . if (EAX != 0) return EAX = curr+4
 720     3d/compare-EAX-and  0/imm32
 721     74/jump-if-equal  $leaky-get-or-insert-slice:mismatch/disp8
 722     8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
 723     eb/jump  $leaky-get-or-insert-slice:end/disp8
 724 $leaky-get-or-insert-slice:mismatch:
 725     # curr += row-size
 726     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
 727     # loop
 728     eb/jump  $leaky-get-or-insert-slice:search-loop/disp8
 729 $leaky-get-or-insert-slice:not-found:
 730     # result/EAX = 0
 731     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 732     # if (table->write >= table->length) abort
 733     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
 734     3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # compare ECX with *(ESI+8)
 735     7d/jump-if-greater-or-equal  $leaky-get-or-insert-slice:abort/disp8
 736     # zero-out(max, row-size)
 737     # . . push args
 738     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
 739     52/push-EDX
 740     # . . call
 741     e8/call  zero-out/disp32
 742     # . . discard args
 743     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 744     # *max = slice-to-string(Heap, key)
 745     # . EAX = slice-to-string(Heap, key)
 746     # . . push args
 747     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 748     68/push  Heap/imm32
 749     # . . call
 750     e8/call  slice-to-string/disp32
 751     # . . discard args
 752     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 753     # . *max = EAX
 754     89/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDX
 755     # table->write += row-size
 756     # . EAX = row-size
 757     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0x10/disp8      .                 # copy *(EBP+16) to EAX
 758     # . table->write += EAX
 759     01/add                          0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # add EAX to *ESI
 760     # return max+4
 761     # . EAX = max
 762     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy EDX to EAX
 763     # . EAX += 4
 764     05/add-to-EAX  4/imm32
 765 $leaky-get-or-insert-slice:end:
 766     # . restore registers
 767     5e/pop-to-ESI
 768     5a/pop-to-EDX
 769     59/pop-to-ECX
 770     # . epilog
 771     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 772     5d/pop-to-EBP
 773     c3/return
 774 
 775 $leaky-get-or-insert-slice:abort:
 776     # . _write(2/stderr, error)
 777     # . . push args
 778     68/push  "leaky-get-or-insert-slice: table is full\n"/imm32
 779     68/push  2/imm32/stderr
 780     # . . call
 781     e8/call  _write/disp32
 782     # . . discard args
 783     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 784     # . syscall(exit, 1)
 785     bb/copy-to-EBX  1/imm32
 786     b8/copy-to-EAX  1/imm32/exit
 787     cd/syscall  0x80/imm8
 788     # never gets here
 789 
 790 test-leaky-get-or-insert-slice:
 791     # . prolog
 792     55/push-EBP
 793     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 794     # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
 795     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
 796     68/push  0x10/imm32/length
 797     68/push  0/imm32/read
 798     68/push  0/imm32/write
 799     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 800     # (EAX..EDX) = "code"
 801     b8/copy-to-EAX  "code"/imm32
 802     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
 803     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  2/index/EDX   .           2/r32/EDX   4/disp8         .                 # copy EAX+EDX+4 to EDX
 804     05/add-to-EAX  4/imm32
 805     # var slice/EDX = {EAX, EDX}
 806     52/push-EDX
 807     50/push-EAX
 808     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
 809 $test-leaky-get-or-insert-slice:first-call:
 810     # - start with an empty table, insert one key, verify that it was inserted
 811     # EAX = leaky-get-or-insert-slice(table, "code" slice, 8 bytes per row)
 812     # . . push args
 813     68/push  8/imm32/row-size
 814     52/push-EDX
 815     51/push-ECX
 816     # . . call
 817     e8/call  leaky-get-or-insert-slice/disp32
 818     # . . discard args
 819     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 820     # check-ints-equal(EAX - table->data, 4, msg)  # first row's value slot returned
 821     # . check-ints-equal(EAX - table, 16, msg)
 822     # . . push args
 823     68/push  "F - test-leaky-get-or-insert-slice/0"/imm32
 824     68/push  0x10/imm32
 825     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 826     50/push-EAX
 827     # . . call
 828     e8/call  check-ints-equal/disp32
 829     # . . discard args
 830     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 831 $test-leaky-get-or-insert-slice:check2:
 832     # check-ints-equal(table->write, row-size = 8, msg)
 833     # . . push args
 834     68/push  "F - test-leaky-get-or-insert-slice/1"/imm32
 835     68/push  8/imm32/row-size
 836     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 837     # . . call
 838     e8/call  check-ints-equal/disp32
 839     # . . discard args
 840     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 841     # check-string-equal(*table->data, "code", msg)
 842     # . . push args
 843     68/push  "F - test-leaky-get-or-insert-slice/2"/imm32
 844     68/push  "code"/imm32
 845     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
 846     # . . call
 847     e8/call  check-string-equal/disp32
 848     # . . discard args
 849     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 850 $test-leaky-get-or-insert-slice:second-call:
 851     # - insert the same key again, verify that it was reused
 852     # EAX = leaky-get-or-insert-slice(table, "code" slice, 8 bytes per row)
 853     # . . push args
 854     68/push  8/imm32/row-size
 855     52/push-EDX
 856     51/push-ECX
 857     # . . call
 858     e8/call  leaky-get-or-insert-slice/disp32
 859     # . . discard args
 860     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 861     # check-ints-equal(EAX - table->data, 4, msg)
 862     # . check-ints-equal(EAX - table, 16, msg)
 863     # . . push args
 864     68/push  "F - test-leaky-get-or-insert-slice/3"/imm32
 865     68/push  0x10/imm32
 866     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 867     50/push-EAX
 868     # . . call
 869     e8/call  check-ints-equal/disp32
 870     # . . discard args
 871     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 872     # no new row inserted
 873     # . check-ints-equal(table->write, row-size = 8, msg)
 874     # . . push args
 875     68/push  "F - test-leaky-get-or-insert-slice/4"/imm32
 876     68/push  8/imm32/row-size
 877     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 878     # . . call
 879     e8/call  check-ints-equal/disp32
 880     # . . discard args
 881     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 882     # check-string-equal(*table->data, "code", msg)
 883     # . . push args
 884     68/push  "F - test-leaky-get-or-insert-slice/5"/imm32
 885     68/push  "code"/imm32
 886     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
 887     # . . call
 888     e8/call  check-string-equal/disp32
 889     # . . discard args
 890     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 891 $test-leaky-get-or-insert-slice:third-call:
 892     # - insert a new key, verify that it was inserted
 893     # (EAX..EDX) = "data"
 894     b8/copy-to-EAX  "data"/imm32
 895     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
 896     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  2/index/EDX   .           2/r32/EDX   4/disp8         .                 # copy EAX+EDX+4 to EDX
 897     05/add-to-EAX  4/imm32
 898     # var slice/EDX = {EAX, EDX}
 899     52/push-EDX
 900     50/push-EAX
 901     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
 902     # EAX = leaky-get-or-insert-slice(table, "data" slice, 8 bytes per row)
 903     # . . push args
 904     68/push  8/imm32/row-size
 905     52/push-EDX
 906     51/push-ECX
 907     # . . call
 908     e8/call  leaky-get-or-insert-slice/disp32
 909     # . . discard args
 910     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 911     # table gets a new row
 912     # check-ints-equal(EAX - table->data, 12, msg)  # second row's value slot returned
 913     # . check-ints-equal(EAX - table, 24, msg)
 914     # . . push args
 915     68/push  "F - test-leaky-get-or-insert-slice/6"/imm32
 916     68/push  0x18/imm32
 917     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 918     50/push-EAX
 919     # . . call
 920     e8/call  check-ints-equal/disp32
 921     # . . discard args
 922     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 923     # check-ints-equal(table->write, 2 rows = 16, msg)
 924     # . . push args
 925     68/push  "F - test-leaky-get-or-insert-slice/7"/imm32
 926     68/push  0x10/imm32/two-rows
 927     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 928     # . . call
 929     e8/call  check-ints-equal/disp32
 930     # . . discard args
 931     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 932     # check-string-equal(*table->data+8, "data", msg)
 933     # check-string-equal(*(table+20), "data", msg)
 934     # . . push args
 935     68/push  "F - test-leaky-get-or-insert-slice/8"/imm32
 936     68/push  "data"/imm32
 937     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0x14/disp8      .                 # push *(ECX+20)
 938     # . . call
 939     e8/call  check-string-equal/disp32
 940     # . . discard args
 941     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 942 $test-leaky-get-or-insert-slice:end:
 943     # . epilog
 944     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 945     5d/pop-to-EBP
 946     c3/return
 947 
 948 # (re)compute the bounds of the next word in the line
 949 # return empty string on reaching end of file
 950 next-word:  # line : (address stream byte), out : (address slice)
 951     # . prolog
 952     55/push-EBP
 953     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 954     # . save registers
 955     50/push-EAX
 956     51/push-ECX
 957     56/push-ESI
 958     57/push-EDI
 959     # ESI = line
 960     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 961     # EDI = out
 962     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
 963     # skip-chars-matching(line, ' ')
 964     # . . push args
 965     68/push  0x20/imm32/space
 966     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
 967     # . . call
 968     e8/call  skip-chars-matching/disp32
 969     # . . discard args
 970     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 971 $next-word:check0:
 972     # if (line->read >= line->write) clear out and return
 973     # . EAX = line->read
 974     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) to EAX
 975     # . if (EAX < line->write) goto next check
 976     3b/compare                      0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # compare EAX with *ESI
 977     7c/jump-if-lesser  $next-word:check-for-comment/disp8
 978     # . return out = {0, 0}
 979     c7          0/subop/copy        0/mod/direct    7/rm32/EDI    .           .             .           .           .               0/imm32           # copy to *EDI
 980     c7          0/subop/copy        1/mod/*+disp8   7/rm32/EDI    .           .             .           .           4/disp8         0/imm32           # copy to *(EDI+4)
 981     eb/jump  $next-word:end/disp8
 982 $next-word:check-for-comment:
 983     # out->start = &line->data[line->read]
 984     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
 985     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
 986     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDI
 987     # if (line->data[line->read] == '#') out->end = &line->data[line->write]), skip rest of stream and return
 988     # . EAX = line->data[line->read]
 989     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 990     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
 991     # . compare
 992     3d/compare-EAX-and  0x23/imm32/pound
 993     75/jump-if-not-equal  $next-word:regular-word/disp8
 994 $next-word:comment:
 995     # . out->end = &line->data[line->write]
 996     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
 997     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
 998     89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDI+4)
 999     # . line->read = line->write
1000     89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(ESI+4)
1001     # . return
1002     eb/jump  $next-word:end/disp8
1003 $next-word:regular-word:
1004     # otherwise skip-chars-not-matching-whitespace(line)  # including trailing newline
1005     # . . push args
1006     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1007     # . . call
1008     e8/call  skip-chars-not-matching-whitespace/disp32
1009     # . . discard args
1010     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1011     # out->end = &line->data[line->read]
1012     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
1013     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
1014     89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDI+4)
1015 $next-word:end:
1016     # . restore registers
1017     5f/pop-to-EDI
1018     5e/pop-to-ESI
1019     59/pop-to-ECX
1020     58/pop-to-EAX
1021     # . epilog
1022     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1023     5d/pop-to-EBP
1024     c3/return
1025 
1026 test-next-word:
1027     # . prolog
1028     55/push-EBP
1029     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1030     # setup
1031     # . clear-stream(_test-stream)
1032     # . . push args
1033     68/push  _test-stream/imm32
1034     # . . call
1035     e8/call  clear-stream/disp32
1036     # . . discard args
1037     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1038     # var slice/ECX = {0, 0}
1039     68/push  0/imm32/end
1040     68/push  0/imm32/start
1041     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1042     # write(_test-stream, "  ab")
1043     # . . push args
1044     68/push  "  ab"/imm32
1045     68/push  _test-stream/imm32
1046     # . . call
1047     e8/call  write/disp32
1048     # . . discard args
1049     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1050     # next-word(_test-stream, slice)
1051     # . . push args
1052     51/push-ECX
1053     68/push  _test-stream/imm32
1054     # . . call
1055     e8/call  next-word/disp32
1056     # . . discard args
1057     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1058     # check-ints-equal(slice->start - _test-stream->data, 2, msg)
1059     # . check-ints-equal(slice->start - _test-stream, 14, msg)
1060     # . . push args
1061     68/push  "F - test-next-word: start"/imm32
1062     68/push  0xe/imm32
1063     # . . push slice->start - _test-stream
1064     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
1065     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
1066     50/push-EAX
1067     # . . call
1068     e8/call  check-ints-equal/disp32
1069     # . . discard args
1070     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1071     # check-ints-equal(slice->end - _test-stream->data, 4, msg)
1072     # . check-ints-equal(slice->end - _test-stream, 16, msg)
1073     # . . push args
1074     68/push  "F - test-next-word: end"/imm32
1075     68/push  0x10/imm32
1076     # . . push slice->end - _test-stream
1077     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
1078     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
1079     50/push-EAX
1080     # . . call
1081     e8/call  check-ints-equal/disp32
1082     # . . discard args
1083     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1084     # . epilog
1085     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1086     5d/pop-to-EBP
1087     c3/return
1088 
1089 test-next-word-returns-whole-comment:
1090     # . prolog
1091     55/push-EBP
1092     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1093     # setup
1094     # . clear-stream(_test-stream)
1095     # . . push args
1096     68/push  _test-stream/imm32
1097     # . . call
1098     e8/call  clear-stream/disp32
1099     # . . discard args
1100     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1101     # var slice/ECX = {0, 0}
1102     68/push  0/imm32/end
1103     68/push  0/imm32/start
1104     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1105     # write(_test-stream, "  # a")
1106     # . . push args
1107     68/push  "  # a"/imm32
1108     68/push  _test-stream/imm32
1109     # . . call
1110     e8/call  write/disp32
1111     # . . discard args
1112     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1113     # next-word(_test-stream, slice)
1114     # . . push args
1115     51/push-ECX
1116     68/push  _test-stream/imm32
1117     # . . call
1118     e8/call  next-word/disp32
1119     # . . discard args
1120     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1121     # check-ints-equal(slice->start - _test-stream->data, 2, msg)
1122     # . check-ints-equal(slice->start - _test-stream, 14, msg)
1123     # . . push args
1124     68/push  "F - test-next-word-returns-whole-comment: start"/imm32
1125     68/push  0xe/imm32
1126     # . . push slice->start - _test-stream
1127     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
1128     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
1129     50/push-EAX
1130     # . . call
1131     e8/call  check-ints-equal/disp32
1132     # . . discard args
1133     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1134     # check-ints-equal(slice->end - _test-stream->data, 5, msg)
1135     # . check-ints-equal(slice->end - _test-stream, 17, msg)
1136     # . . push args
1137     68/push  "F - test-next-word-returns-whole-comment: end"/imm32
1138     68/push  0x11/imm32
1139     # . . push slice->end - _test-stream
1140     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
1141     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
1142     50/push-EAX
1143     # . . call
1144     e8/call  check-ints-equal/disp32
1145     # . . discard args
1146     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1147     # . epilog
1148     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1149     5d/pop-to-EBP
1150     c3/return
1151 
1152 test-next-word-returns-empty-string-on-eof:
1153     # . prolog
1154     55/push-EBP
1155     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1156     # setup
1157     # . clear-stream(_test-stream)
1158     # . . push args
1159     68/push  _test-stream/imm32
1160     # . . call
1161     e8/call  clear-stream/disp32
1162     # . . discard args
1163     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1164     # var slice/ECX = {0, 0}
1165     68/push  0/imm32/end
1166     68/push  0/imm32/start
1167     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1168     # write nothing to _test-stream
1169     # next-word(_test-stream, slice)
1170     # . . push args
1171     51/push-ECX
1172     68/push  _test-stream/imm32
1173     # . . call
1174     e8/call  next-word/disp32
1175     # . . discard args
1176     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1177     # check-ints-equal(slice->end - slice->start, 0, msg)
1178     # . . push args
1179     68/push  "F - test-next-word-returns-empty-string-on-eof"/imm32
1180     68/push  0/imm32
1181     # . . push slice->end - slice->start
1182     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
1183     2b/subtract                     0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # subtract *ECX from EAX
1184     50/push-EAX
1185     # . . call
1186     e8/call  check-ints-equal/disp32
1187     # . . discard args
1188     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1189     # . epilog
1190     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1191     5d/pop-to-EBP
1192     c3/return
1193 
1194 # write an entire stream's contents to a buffered-file
1195 # ways to do this:
1196 #   - construct a 'maximal slice' and pass it to write-slice-buffered
1197 #   - flush the buffered-file and pass the stream directly to its fd (disabling buffering)
1198 # we'll go with the first way for now
1199 write-stream-data:  # f : (address buffered-file), s : (address stream) -> <void>
1200     # . prolog
1201     55/push-EBP
1202     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1203     # . save registers
1204     50/push-EAX
1205     51/push-ECX
1206     56/push-ESI
1207     # ESI = s
1208     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
1209     # var slice/ECX = {s->data, s->data + s->write}
1210     # . push s->data + s->write
1211     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
1212     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
1213     50/push-EAX
1214     # . push s->data
1215     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy ESI+12 to EAX
1216     50/push-EAX
1217     # . ECX = ESP
1218     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1219     # write-slice-buffered(f, slice)
1220     # . . push args
1221     51/push-ECX
1222     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1223     # . . call
1224     e8/call  write-slice-buffered/disp32
1225     # . . discard args
1226     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1227 $write-stream-data:end:
1228     # . restore locals
1229     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1230     # . restore registers
1231     5e/pop-to-ESI
1232     59/pop-to-ECX
1233     58/pop-to-EAX
1234     # . epilog
1235     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1236     5d/pop-to-EBP
1237     c3/return
1238 
1239 test-write-stream-data:
1240     # . prolog
1241     55/push-EBP
1242     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1243     # setup
1244     # . clear-stream(_test-output-stream)
1245     # . . push args
1246     68/push  _test-output-stream/imm32
1247     # . . call
1248     e8/call  clear-stream/disp32
1249     # . . discard args
1250     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1251     # . clear-stream(_test-output-buffered-file+4)
1252     # . . push args
1253     b8/copy-to-EAX  _test-output-buffered-file/imm32
1254     05/add-to-EAX  4/imm32
1255     50/push-EAX
1256     # . . call
1257     e8/call  clear-stream/disp32
1258     # . . discard args
1259     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1260     # . clear-stream(_test-input-stream)
1261     # . . push args
1262     68/push  _test-input-stream/imm32
1263     # . . call
1264     e8/call  clear-stream/disp32
1265     # . . discard args
1266     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1267     # initialize input
1268     # . write(_test-input-stream, "abcd")
1269     # . . push args
1270     68/push  "abcd"/imm32
1271     68/push  _test-input-stream/imm32
1272     # . . call
1273     e8/call  write/disp32
1274     # . . discard args
1275     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1276     # write-stream-data(_test-output-buffered-file, _test-input-stream)
1277     # . . push args
1278     68/push  _test-input-stream/imm32
1279     68/push  _test-output-buffered-file/imm32
1280     # . . call
1281     e8/call  write-stream-data/disp32
1282     # . . discard args
1283     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1284     # check that the write happened as expected
1285     # . flush(_test-output-buffered-file)
1286     # . . push args
1287     68/push  _test-output-buffered-file/imm32
1288     # . . call
1289     e8/call  flush/disp32
1290     # . . discard args
1291     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1292     # . check-stream-equal(_test-output-stream, "abcd", msg)
1293     # . . push args
1294     68/push  "F - test-write-stream-data"/imm32
1295     68/push  "abcd"/imm32
1296     68/push  _test-output-stream/imm32
1297     # . . call
1298     e8/call  check-stream-equal/disp32
1299     # . . discard args
1300     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1301     # . epilog
1302     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1303     5d/pop-to-EBP
1304     c3/return
1305 
1306 has-metadata?:  # word : (address slice), s : (address string) -> EAX : boolean
1307     # pseudocode:
1308     #   var twig : &slice = next-token-from-slice(word->start, word->end, '/')  # skip name
1309     #   curr = twig->end
1310     #   while true
1311     #     twig = next-token-from-slice(curr, word->end, '/')
1312     #     if (twig.empty()) break
1313     #     if (slice-equal?(twig, s)) return true
1314     #     curr = twig->end
1315     #   return false
1316     # . prolog
1317     55/push-EBP
1318     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1319     # . save registers
1320     51/push-ECX
1321     52/push-EDX
1322     56/push-ESI
1323     57/push-EDI
1324     # ESI = word
1325     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
1326     # EDX = word->end
1327     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ESI+4) to EDX
1328     # var twig/EDI : (address slice) = {0, 0}
1329     68/push  0/imm32/end
1330     68/push  0/imm32/start
1331     89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDI
1332     # next-token-from-slice(word->start, word->end, '/', twig)
1333     # . . push args
1334     57/push-EDI
1335     68/push  0x2f/imm32/slash
1336     52/push-EDX
1337     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
1338     # . . call
1339     e8/call  next-token-from-slice/disp32
1340     # . . discard args
1341     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
1342     # curr/ECX = twig->end
1343     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EDI+4) to ECX
1344 $has-metadata?:loop:
1345     # next-token-from-slice(curr, word->end, '/', twig)
1346     # . . push args
1347     57/push-EDI
1348     68/push  0x2f/imm32/slash
1349     52/push-EDX
1350     51/push-ECX
1351     # . . call
1352     e8/call  next-token-from-slice/disp32
1353     # . . discard args
1354     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
1355     # if (slice-empty?(twig)) return false
1356     # . EAX = slice-empty?(twig)
1357     # . . push args
1358     57/push-EDI
1359     # . . call
1360     e8/call  slice-empty?/disp32
1361     # . . discard args
1362     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1363     # . if (EAX != 0) return false
1364     3d/compare-EAX-and  0/imm32
1365     75/jump-if-not-equal  $has-metadata?:false/disp8
1366     # if (slice-equal?(twig, s)) return true
1367     # . EAX = slice-equal?(twig, s)
1368     # . . push args
1369     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
1370     57/push-EDI
1371     # . . call
1372     e8/call  slice-equal?/disp32
1373     # . . discard args
1374     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1375     # . if (EAX != 0) return true
1376     3d/compare-EAX-and  0/imm32
1377     75/jump-if-not-equal  $has-metadata?:true/disp8
1378     # curr = twig->end
1379     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EDI+4) to ECX
1380     eb/jump  $has-metadata?:loop/disp8
1381 $has-metadata?:true:
1382     b8/copy-to-EAX  1/imm32/true
1383     eb/jump  $has-metadata?:end/disp8
1384 $has-metadata?:false:
1385     b8/copy-to-EAX  0/imm32/false
1386 $has-metadata?:end:
1387     # . reclaim locals
1388     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1389     # . restore registers
1390     5f/pop-to-EDI
1391     5e/pop-to-ESI
1392     5a/pop-to-EDX
1393     59/pop-to-ECX
1394     # . epilog
1395     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1396     5d/pop-to-EBP
1397     c3/return
1398 
1399 test-has-metadata-true:
1400     # . prolog
1401     55/push-EBP
1402     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1403     # (EAX..ECX) = "ab/imm32"
1404     b8/copy-to-EAX  "ab/imm32"/imm32
1405     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
1406     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
1407     05/add-to-EAX  4/imm32
1408     # var in/ESI : (address slice) = {EAX, ECX}
1409     51/push-ECX
1410     50/push-EAX
1411     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
1412     # EAX = has-metadata?(ESI, "imm32")
1413     # . . push args
1414     68/push  "imm32"/imm32
1415     56/push-ESI
1416     # . . call
1417     e8/call  has-metadata?/disp32
1418     # . . discard args
1419     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1420     # check-ints-equal(EAX, 1, msg)
1421     # . . push args
1422     68/push  "F - test-has-metadata-true"/imm32
1423     68/push  1/imm32/true
1424     50/push-EAX
1425     # . . call
1426     e8/call  check-ints-equal/disp32
1427     # . . discard args
1428     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1429     # . epilog
1430     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1431     5d/pop-to-EBP
1432     c3/return
1433 
1434 test-has-metadata-false:
1435     # . prolog
1436     55/push-EBP
1437     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1438     # (EAX..ECX) = "ab/c"
1439     b8/copy-to-EAX  "ab/c"/imm32
1440     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
1441     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
1442     05/add-to-EAX  4/imm32
1443     # var in/ESI : (address slice) = {EAX, ECX}
1444     51/push-ECX
1445     50/push-EAX
1446     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
1447     # EAX = has-metadata?(ESI, "d")
1448     # . . push args
1449     68/push  "d"/imm32
1450     56/push-ESI
1451     # . . call
1452     e8/call  has-metadata?/disp32
1453     # . . discard args
1454     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1455     # check-ints-equal(EAX, 0, msg)
1456     # . . push args
1457     68/push  "F - test-has-metadata-false"/imm32
1458     68/push  0/imm32/false
1459     50/push-EAX
1460     # . . call
1461     e8/call  check-ints-equal/disp32
1462     # . . discard args
1463     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1464     # . epilog
1465     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1466     5d/pop-to-EBP
1467     c3/return
1468 
1469 test-has-metadata-ignore-name:
1470     # . prolog
1471     55/push-EBP
1472     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1473     # (EAX..ECX) = "a/b"
1474     b8/copy-to-EAX  "a/b"/imm32
1475     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
1476     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
1477     05/add-to-EAX  4/imm32
1478     # var in/ESI : (address slice) = {EAX, ECX}
1479     51/push-ECX
1480     50/push-EAX
1481     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
1482     # EAX = has-metadata?(ESI, "a")
1483     # . . push args
1484     68/push  "a"/imm32
1485     56/push-ESI
1486     # . . call
1487     e8/call  has-metadata?/disp32
1488     # . . discard args
1489     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1490     # check-ints-equal(EAX, 0, msg)
1491     # . . push args
1492     68/push  "F - test-has-metadata-ignore-name"/imm32
1493     68/push  0/imm32/false
1494     50/push-EAX
1495     # . . call
1496     e8/call  check-ints-equal/disp32
1497     # . . discard args
1498     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1499     # . epilog
1500     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1501     5d/pop-to-EBP
1502     c3/return
1503 
1504 test-has-metadata-multiple-true:
1505     # . prolog
1506     55/push-EBP
1507     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1508     # (EAX..ECX) = "a/b/c"
1509     b8/copy-to-EAX  "a/b/c"/imm32
1510     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
1511     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
1512     05/add-to-EAX  4/imm32
1513     # var in/ESI : (address slice) = {EAX, ECX}
1514     51/push-ECX
1515     50/push-EAX
1516     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
1517     # EAX = has-metadata?(ESI, "c")
1518     # . . push args
1519     68/push  "c"/imm32
1520     56/push-ESI
1521     # . . call
1522     e8/call  has-metadata?/disp32
1523     # . . discard args
1524     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1525     # check-ints-equal(EAX, 1, msg)
1526     # . . push args
1527     68/push  "F - test-has-metadata-multiple-true"/imm32
1528     68/push  1/imm32/true
1529     50/push-EAX
1530     # . . call
1531     e8/call  check-ints-equal/disp32
1532     # . . discard args
1533     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1534     # . epilog
1535     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1536     5d/pop-to-EBP
1537     c3/return
1538 
1539 test-has-metadata-multiple-false:
1540     # . prolog
1541     55/push-EBP
1542     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1543     # (EAX..ECX) = "a/b/c"
1544     b8/copy-to-EAX  "a/b/c"/imm32
1545     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
1546     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
1547     05/add-to-EAX  4/imm32
1548     # var in/ESI : (address slice) = {EAX, ECX}
1549     51/push-ECX
1550     50/push-EAX
1551     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
1552     # EAX = has-metadata?(ESI, "d")
1553     # . . push args
1554     68/push  "d"/imm32
1555     56/push-ESI
1556     # . . call
1557     e8/call  has-metadata?/disp32
1558     # . . discard args
1559     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1560     # check-ints-equal(EAX, 0, msg)
1561     # . . push args
1562     68/push  "F - test-has-metadata-multiple-false"/imm32
1563     68/push  0/imm32/false
1564     50/push-EAX
1565     # . . call
1566     e8/call  check-ints-equal/disp32
1567     # . . discard args
1568     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1569     # . epilog
1570     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1571     5d/pop-to-EBP
1572     c3/return
1573 
1574 # If datum of 'word' is not a valid name, it must be a hex int. Parse and print
1575 # it in 'width' bytes of hex, least significant first.
1576 # Otherwise just print the entire word including metadata.
1577 # Always print a trailing space.
1578 emit:  # out : (address buffered-file), word : (address slice), width : int -> <void>
1579     # . prolog
1580     55/push-EBP
1581     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1582     # . save registers
1583     50/push-EAX
1584     56/push-ESI
1585     57/push-EDI
1586     # ESI = word
1587     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
1588     # var name/EDI : (address slice) = {0, 0}
1589     68/push  0/imm32/end
1590     68/push  0/imm32/start
1591     89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDI
1592     # datum = next-token-from-slice(word->start, word->end, '/')
1593     # . . push args
1594     57/push-EDI
1595     68/push  0x2f/imm32/slash
1596     ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           4/disp8         .                 # push *(ESI+4)
1597     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
1598     # . . call
1599     e8/call  next-token-from-slice/disp32
1600     # . . discard args
1601     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
1602     # if (is-valid-name?(datum)) write-slice-buffered(out, word) and return
1603     # . EAX = is-valid-name?(name)
1604     # . . push args
1605     57/push-EDI
1606     # . . call
1607     e8/call  is-valid-name?/disp32
1608     # . . discard args
1609     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1610     # . if (EAX != 0)
1611     3d/compare-EAX-and  0/imm32
1612     74/jump-if-equal  $emit:hex-int/disp8
1613 $emit:name:
1614     # . write-slice-buffered(out, word)
1615     # . . push args
1616     56/push-ESI
1617     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1618     # . . call
1619     e8/call  write-slice-buffered/disp32
1620     # . . discard args
1621     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1622     # . write-buffered(out, " ")
1623     # . . push args
1624     68/push  " "/imm32
1625     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1626     # . . call
1627     e8/call  write-buffered/disp32
1628     # . . discard args
1629     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1630     # . return
1631     eb/jump  $emit:end/disp8
1632     # otherwise emit-hex(out, parse-hex-int(datum), width)
1633     #   (Weird shit can happen here if the datum of 'word' isn't either a valid
1634     #   name or a hex number, but we're only going to be passing in real legal
1635     #   programs. We just want to make sure that valid names aren't treated as
1636     #   (valid) hex numbers.)
1637 $emit:hex-int:
1638     # . value/EAX = parse-hex-int(datum)
1639     # . . push args
1640     57/push-EDI
1641     # . . call
1642     e8/call  parse-hex-int/disp32
1643     # . . discard args
1644     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1645     # . emit-hex(out, value, width)
1646     # . . push args
1647     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
1648     50/push-EAX
1649     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1650     # . . call
1651     e8/call  emit-hex/disp32
1652     # . . discard args
1653     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1654 $emit:end:
1655     # . reclaim locals
1656     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1657     # . restore registers
1658     5f/pop-to-EDI
1659     5e/pop-to-ESI
1660     58/pop-to-EAX
1661     # . epilog
1662     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1663     5d/pop-to-EBP
1664     c3/return
1665 
1666 test-emit-number:
1667     # . prolog
1668     55/push-EBP
1669     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1670     # setup
1671     # . clear-stream(_test-output-stream)
1672     # . . push args
1673     68/push  _test-output-stream/imm32
1674     # . . call
1675     e8/call  clear-stream/disp32
1676     # . . discard args
1677     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1678     # . clear-stream(_test-output-buffered-file+4)
1679     # . . push args
1680     b8/copy-to-EAX  _test-output-buffered-file/imm32
1681     05/add-to-EAX  4/imm32
1682     50/push-EAX
1683     # . . call
1684     e8/call  clear-stream/disp32
1685     # . . discard args
1686     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1687     # (EAX..ECX) = "30"
1688     b8/copy-to-EAX  "30"/imm32
1689     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
1690     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
1691     05/add-to-EAX  4/imm32
1692     # var slice/ECX = {EAX, ECX}
1693     51/push-ECX
1694     50/push-EAX
1695     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1696     # emit(_test-output-buffered-file, slice, 1)
1697     # . . push args
1698     68/push  1/imm32
1699     51/push-ECX
1700     68/push  _test-output-buffered-file/imm32
1701     # . . call
1702     e8/call  emit/disp32
1703     # . . discard args
1704     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1705     # flush(_test-output-buffered-file)
1706     # . . push args
1707     68/push  _test-output-buffered-file/imm32
1708     # . . call
1709     e8/call  flush/disp32
1710     # . . discard args
1711     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1712     # check-stream-equal(_test-output-stream, "30 ", msg)
1713     # . . push args
1714     68/push  "F - test-emit-number/1"/imm32
1715     68/push  "30 "/imm32
1716     68/push  _test-output-stream/imm32
1717     # . . call
1718     e8/call  check-stream-equal/disp32
1719     # . . discard args
1720     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1721     # . epilog
1722     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1723     5d/pop-to-EBP
1724     c3/return
1725 
1726 test-emit-negative-number:
1727     # test support for sign-extending negative numbers
1728     # . prolog
1729     55/push-EBP
1730     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1731     # setup
1732     # . clear-stream(_test-output-stream)
1733     # . . push args
1734     68/push  _test-output-stream/imm32
1735     # . . call
1736     e8/call  clear-stream/disp32
1737     # . . discard args
1738     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1739     # . clear-stream(_test-output-buffered-file+4)
1740     # . . push args
1741     b8/copy-to-EAX  _test-output-buffered-file/imm32
1742     05/add-to-EAX  4/imm32
1743     50/push-EAX
1744     # . . call
1745     e8/call  clear-stream/disp32
1746     # . . discard args
1747     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1748     # (EAX..ECX) = "-2"
1749     b8/copy-to-EAX  "-2"/imm32
1750     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
1751     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
1752     05/add-to-EAX  4/imm32
1753     # var slice/ECX = {EAX, ECX}
1754     51/push-ECX
1755     50/push-EAX
1756     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1757     # emit(_test-output-buffered-file, slice, 2)
1758     # . . push args
1759     68/push  2/imm32
1760     51/push-ECX
1761     68/push  _test-output-buffered-file/imm32
1762     # . . call
1763     e8/call  emit/disp32
1764     # . . discard args
1765     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1766     # flush(_test-output-buffered-file)
1767     # . . push args
1768     68/push  _test-output-buffered-file/imm32
1769     # . . call
1770     e8/call  flush/disp32
1771     # . . discard args
1772     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1773     # check-stream-equal(_test-output-stream, "fe ff ", msg)
1774     # . . push args
1775     68/push  "F - test-emit-number/1"/imm32
1776     68/push  "fe ff "/imm32
1777     68/push  _test-output-stream/imm32
1778     # . . call
1779     e8/call  check-stream-equal/disp32
1780     # . . discard args
1781     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
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 test-emit-number-with-metadata:
1788     # . prolog
1789     55/push-EBP
1790     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1791     # setup
1792     # . clear-stream(_test-output-stream)
1793     # . . push args
1794     68/push  _test-output-stream/imm32
1795     # . . call
1796     e8/call  clear-stream/disp32
1797     # . . discard args
1798     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1799     # . clear-stream(_test-output-buffered-file+4)
1800     # . . push args
1801     b8/copy-to-EAX  _test-output-buffered-file/imm32
1802     05/add-to-EAX  4/imm32
1803     50/push-EAX
1804     # . . call
1805     e8/call  clear-stream/disp32
1806     # . . discard args
1807     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1808     # (EAX..ECX) = "-2/foo"
1809     b8/copy-to-EAX  "-2/foo"/imm32
1810     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
1811     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
1812     05/add-to-EAX  4/imm32
1813     # var slice/ECX = {EAX, ECX}
1814     51/push-ECX
1815     50/push-EAX
1816     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1817     # emit(_test-output-buffered-file, slice, 2)
1818     # . . push args
1819     68/push  2/imm32
1820     51/push-ECX
1821     68/push  _test-output-buffered-file/imm32
1822     # . . call
1823     e8/call  emit/disp32
1824     # . . discard args
1825     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1826     # flush(_test-output-buffered-file)
1827     # . . push args
1828     68/push  _test-output-buffered-file/imm32
1829     # . . call
1830     e8/call  flush/disp32
1831     # . . discard args
1832     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1833     # the '/foo' will have no impact on the output
1834     # check-stream-equal(_test-output-stream, "fe ff ", msg)
1835     # . . push args
1836     68/push  "F - test-emit-number-with-metadata"/imm32
1837     68/push  "fe ff "/imm32
1838     68/push  _test-output-stream/imm32
1839     # . . call
1840     e8/call  check-stream-equal/disp32
1841     # . . discard args
1842     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1843     # . epilog
1844     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1845     5d/pop-to-EBP
1846     c3/return
1847 
1848 test-emit-non-number:
1849     # . prolog
1850     55/push-EBP
1851     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1852     # setup
1853     # . clear-stream(_test-output-stream)
1854     # . . push args
1855     68/push  _test-output-stream/imm32
1856     # . . call
1857     e8/call  clear-stream/disp32
1858     # . . discard args
1859     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1860     # . clear-stream(_test-output-buffered-file+4)
1861     # . . push args
1862     b8/copy-to-EAX  _test-output-buffered-file/imm32
1863     05/add-to-EAX  4/imm32
1864     50/push-EAX
1865     # . . call
1866     e8/call  clear-stream/disp32
1867     # . . discard args
1868     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1869     # (EAX..ECX) = "xyz"
1870     b8/copy-to-EAX  "xyz"/imm32
1871     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
1872     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
1873     05/add-to-EAX  4/imm32
1874     # var slice/ECX = {EAX, ECX}
1875     51/push-ECX
1876     50/push-EAX
1877     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1878     # emit(_test-output-buffered-file, slice, 2)
1879     # . . push args
1880     68/push  2/imm32
1881     51/push-ECX
1882     68/push  _test-output-buffered-file/imm32
1883     # . . call
1884     e8/call  emit/disp32
1885     # . . discard args
1886     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1887     # flush(_test-output-buffered-file)
1888     # . . push args
1889     68/push  _test-output-buffered-file/imm32
1890     # . . call
1891     e8/call  flush/disp32
1892     # . . discard args
1893     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1894     # check-stream-equal(_test-output-stream, "xyz", msg)
1895     # . . push args
1896     68/push  "F - test-emit-non-number"/imm32
1897     68/push  "xyz "/imm32
1898     68/push  _test-output-stream/imm32
1899     # . . call
1900     e8/call  check-stream-equal/disp32
1901     # . . discard args
1902     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1903     # . epilog
1904     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1905     5d/pop-to-EBP
1906     c3/return
1907 
1908 test-emit-non-number-with-metadata:
1909     # . prolog
1910     55/push-EBP
1911     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1912     # setup
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-output-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     # (EAX..ECX) = "xyz/"
1930     b8/copy-to-EAX  "xyz/"/imm32
1931     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
1932     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
1933     05/add-to-EAX  4/imm32
1934     # var slice/ECX = {EAX, ECX}
1935     51/push-ECX
1936     50/push-EAX
1937     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1938     # emit(_test-output-buffered-file, slice, 2)
1939     # . . push args
1940     68/push  2/imm32
1941     51/push-ECX
1942     68/push  _test-output-buffered-file/imm32
1943     # . . call
1944     e8/call  emit/disp32
1945     # . . discard args
1946     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
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, "xyz/", msg)
1955     # . . push args
1956     68/push  "F - test-emit-non-number-with-metadata"/imm32
1957     68/push  "xyz/ "/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-emit-non-number-with-all-hex-digits-and-metadata:
1969     # . prolog
1970     55/push-EBP
1971     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1972     # setup
1973     # . clear-stream(_test-output-stream)
1974     # . . push args
1975     68/push  _test-output-stream/imm32
1976     # . . call
1977     e8/call  clear-stream/disp32
1978     # . . discard args
1979     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1980     # . clear-stream(_test-output-buffered-file+4)
1981     # . . push args
1982     b8/copy-to-EAX  _test-output-buffered-file/imm32
1983     05/add-to-EAX  4/imm32
1984     50/push-EAX
1985     # . . call
1986     e8/call  clear-stream/disp32
1987     # . . discard args
1988     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1989     # (EAX..ECX) = "abcd/xyz"
1990     b8/copy-to-EAX  "abcd/xyz"/imm32
1991     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
1992     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
1993     05/add-to-EAX  4/imm32
1994     # var slice/ECX = {EAX, ECX}
1995     51/push-ECX
1996     50/push-EAX
1997     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1998     # emit(_test-output-buffered-file, slice, 2)
1999     # . . push args
2000     68/push  2/imm32
2001     51/push-ECX
2002     68/push  _test-output-buffered-file/imm32
2003     # . . call
2004     e8/call  emit/disp32
2005     # . . discard args
2006     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2007     # flush(_test-output-buffered-file)
2008     # . . push args
2009     68/push  _test-output-buffered-file/imm32
2010     # . . call
2011     e8/call  flush/disp32
2012     # . . discard args
2013     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2014 +-- 26 lines: #?     # dump output ---------------------------------------------------------------------------------------------------------------------------
2040     # check-stream-equal(_test-output-stream, "abcd/xyz")
2041     # . . push args
2042     68/push  "F - test-emit-non-number-with-all-hex-digits"/imm32
2043     68/push  "abcd/xyz "/imm32
2044     68/push  _test-output-stream/imm32
2045     # . . call
2046     e8/call  check-stream-equal/disp32
2047     # . . discard args
2048     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2049     # . epilog
2050     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2051     5d/pop-to-EBP
2052     c3/return
2053 
2054 # conditions for 'valid' names that are not at risk of looking like hex numbers
2055 # keep in sync with the rules in labels.cc
2056 #: - if it starts with a digit, it's treated as a number. If it can't be
2057 #:   parsed as hex it will raise an error.
2058 #: - if it starts with '-' it's treated as a number.
2059 #: - if it starts with '0x' it's treated as a number. (redundant)
2060 #: - if it's two characters long, it can't be a name. Either it's a hex
2061 #:   byte, or it raises an error.
2062 is-valid-name?:  # in : (address slice) -> EAX : boolean
2063     # . prolog
2064     55/push-EBP
2065     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2066     # . save registers
2067     51/push-ECX
2068     56/push-ESI
2069     # ESI = in
2070     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
2071     # start/ECX = in->start
2072     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
2073     # end/EAX = in->end
2074     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) to EAX
2075 $is-valid-name?:check0:
2076     # if (start >= end) return false
2077     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # compare ECX with EAX
2078     73/jump-if-greater-or-equal-unsigned  $is-valid-name?:false/disp8
2079 $is-valid-name?:check1:
2080     # EAX -= ECX
2081     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
2082     # if (EAX == 2) return false
2083     3d/compare-EAX-and  2/imm32
2084     74/jump-if-equal  $is-valid-name?:false/disp8
2085 $is-valid-name?:check2:
2086     # c/EAX = *ECX
2087     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
2088     8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
2089     # if (c == "-") return false
2090     3d/compare-EAX-and  2d/imm32/-
2091     74/jump-if-equal  $is-valid-name?:false/disp8
2092 $is-valid-name?:check3a:
2093     # if (c < "0") return true
2094     3d/compare-EAX-with  30/imm32/0
2095     7c/jump-if-lesser  $is-valid-name?:true/disp8
2096 $is-valid-name?:check3b:
2097     # if (c > "9") return true
2098     3d/compare-EAX-with  39/imm32/9
2099     7f/jump-if-greater  $is-valid-name?:true/disp8
2100 $is-valid-name?:false:
2101     # return false
2102     b8/copy-to-EAX  0/imm32/false
2103     eb/jump  $is-valid-name?:end/disp8
2104 $is-valid-name?:true:
2105     # return true
2106     b8/copy-to-EAX  1/imm32/true
2107 $is-valid-name?:end:
2108     # . restore registers
2109     5e/pop-to-ESI
2110     59/pop-to-ECX
2111     # . epilog
2112     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2113     5d/pop-to-EBP
2114     c3/return
2115 
2116 test-is-valid-name-digit-prefix:
2117     # . prolog
2118     55/push-EBP
2119     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2120     # (EAX..ECX) = "34"
2121     b8/copy-to-EAX  "34"/imm32
2122     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2123     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
2124     05/add-to-EAX  4/imm32
2125     # var slice/ECX = {EAX, ECX}
2126     51/push-ECX
2127     50/push-EAX
2128     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2129     # EAX = is-valid-name?(slice)
2130     # . . push args
2131     51/push-ECX
2132     # . . call
2133     e8/call  is-valid-name?/disp32
2134     # . . discard args
2135     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2136     # check-ints-equal(EAX, 0, msg)
2137     # . . push args
2138     68/push  "F - test-is-valid-name-digit-prefix"/imm32
2139     68/push  0/imm32/false
2140     50/push-EAX
2141     # . . call
2142     e8/call  check-ints-equal/disp32
2143     # . . discard args
2144     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2145     # . epilog
2146     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2147     5d/pop-to-EBP
2148     c3/return
2149 
2150 test-is-valid-name-negative-prefix:
2151     # . prolog
2152     55/push-EBP
2153     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2154     # (EAX..ECX) = "-0x34"
2155     b8/copy-to-EAX  "-0x34"/imm32
2156     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2157     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
2158     05/add-to-EAX  4/imm32
2159     # var slice/ECX = {EAX, ECX}
2160     51/push-ECX
2161     50/push-EAX
2162     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2163     # EAX = is-valid-name?(slice)
2164     # . . push args
2165     51/push-ECX
2166     # . . call
2167     e8/call  is-valid-name?/disp32
2168     # . . discard args
2169     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2170     # check-ints-equal(EAX, 0, msg)
2171     # . . push args
2172     68/push  "F - test-is-valid-name-negative-prefix"/imm32
2173     68/push  0/imm32/false
2174     50/push-EAX
2175     # . . call
2176     e8/call  check-ints-equal/disp32
2177     # . . discard args
2178     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2179     # . epilog
2180     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2181     5d/pop-to-EBP
2182     c3/return
2183 
2184 test-is-valid-name-0x-prefix:
2185     # . prolog
2186     55/push-EBP
2187     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2188     # (EAX..ECX) = "0x34"
2189     b8/copy-to-EAX  "0x34"/imm32
2190     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2191     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
2192     05/add-to-EAX  4/imm32
2193     # var slice/ECX = {EAX, ECX}
2194     51/push-ECX
2195     50/push-EAX
2196     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2197     # EAX = is-valid-name?(slice)
2198     # . . push args
2199     51/push-ECX
2200     # . . call
2201     e8/call  is-valid-name?/disp32
2202     # . . discard args
2203     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2204     # check-ints-equal(EAX, 0, msg)
2205     # . . push args
2206     68/push  "F - test-is-valid-name-0x-prefix"/imm32
2207     68/push  0/imm32/false
2208     50/push-EAX
2209     # . . call
2210     e8/call  check-ints-equal/disp32
2211     # . . discard args
2212     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2213     # . epilog
2214     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2215     5d/pop-to-EBP
2216     c3/return
2217 
2218 test-is-valid-name-starts-with-pre-digit:
2219     # . prolog
2220     55/push-EBP
2221     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2222     # (EAX..ECX) = "/03"
2223     b8/copy-to-EAX  "/03"/imm32
2224     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2225     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
2226     05/add-to-EAX  4/imm32
2227     # var slice/ECX = {EAX, ECX}
2228     51/push-ECX
2229     50/push-EAX
2230     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2231     # EAX = is-valid-name?(slice)
2232     # . . push args
2233     51/push-ECX
2234     # . . call
2235     e8/call  is-valid-name?/disp32
2236     # . . discard args
2237     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2238     # check-ints-equal(EAX, 1, msg)
2239     # . . push args
2240     68/push  "F - test-is-valid-name-starts-with-pre-digit"/imm32
2241     68/push  1/imm32/true
2242     50/push-EAX
2243     # . . call
2244     e8/call  check-ints-equal/disp32
2245     # . . discard args
2246     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2247     # . epilog
2248     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2249     5d/pop-to-EBP
2250     c3/return
2251 
2252 test-is-valid-name-starts-with-post-digit:
2253     # . prolog
2254     55/push-EBP
2255     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2256     # (EAX..ECX) = "q34"
2257     b8/copy-to-EAX  "q34"/imm32
2258     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2259     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
2260     05/add-to-EAX  4/imm32
2261     # var slice/ECX = {EAX, ECX}
2262     51/push-ECX
2263     50/push-EAX
2264     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2265     # EAX = is-valid-name?(slice)
2266     # . . push args
2267     51/push-ECX
2268     # . . call
2269     e8/call  is-valid-name?/disp32
2270     # . . discard args
2271     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2272     # check-ints-equal(EAX, 1, msg)
2273     # . . push args
2274     68/push  "F - test-is-valid-name-starts-with-post-digit"/imm32
2275     68/push  1/imm32/true
2276     50/push-EAX
2277     # . . call
2278     e8/call  check-ints-equal/disp32
2279     # . . discard args
2280     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2281     # . epilog
2282     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2283     5d/pop-to-EBP
2284     c3/return
2285 
2286 test-is-valid-name-starts-with-digit:
2287     # . prolog
2288     55/push-EBP
2289     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2290     # (EAX..ECX) = "0x34"
2291     b8/copy-to-EAX  "0x34"/imm32
2292     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2293     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
2294     05/add-to-EAX  4/imm32
2295     # var slice/ECX = {EAX, ECX}
2296     51/push-ECX
2297     50/push-EAX
2298     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2299     # EAX = is-valid-name?(slice)
2300     # . . push args
2301     51/push-ECX
2302     # . . call
2303     e8/call  is-valid-name?/disp32
2304     # . . discard args
2305     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2306     # check-ints-equal(EAX, 0, msg)
2307     # . . push args
2308     68/push  "F - test-is-valid-name-starts-with-digit"/imm32
2309     68/push  0/imm32/false
2310     50/push-EAX
2311     # . . call
2312     e8/call  check-ints-equal/disp32
2313     # . . discard args
2314     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2315     # . epilog
2316     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2317     5d/pop-to-EBP
2318     c3/return
2319 
2320 # print 'n' in hex in 'width' bytes in lower-endian order, with a space after every byte
2321 emit-hex:  # out : (address buffered-file), n : int, width : int -> <void>
2322     # . prolog
2323     55/push-EBP
2324     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2325     # . save registers
2326     50/push-EAX
2327     51/push-ECX
2328     52/push-EDX
2329     53/push-EBX
2330     57/push-EDI
2331     # EDI = out
2332     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
2333     # EBX = n
2334     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy *(EBP+12) to EBX
2335     # EDX = width
2336     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0x10/disp8      .                 # copy *(EBP+16) to EDX
2337     # var curr/ECX = 0
2338     31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
2339 $emit-hex:loop:
2340     # if (curr >= width) break
2341     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
2342     7d/jump-if-greater-or-equal  $emit-hex:end/disp8
2343     # print-byte-buffered(out, EBX)
2344     # . . push args
2345     53/push-EBX
2346     57/push-EDI
2347     # . . call
2348     e8/call  print-byte-buffered/disp32
2349     # . . discard args
2350     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2351     # write-byte-buffered(out, ' ')
2352     # . . push args
2353     68/push  0x20/imm32/space
2354     57/push-EDI
2355     # . . call
2356     e8/call  write-byte-buffered/disp32
2357     # . . discard args
2358     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2359     # EBX = EBX >> 8
2360     c1/shift    5/subop/logic-right 3/mod/direct    3/rm32/EBX    .           .             .           .           .               8/imm8            # shift EBX right by 8 bits, while padding zeroes
2361 $emit-hex:continue:
2362     # ++curr
2363     41/increment-ECX
2364     eb/jump  $emit-hex:loop/disp8
2365 $emit-hex:end:
2366     # . restore registers
2367     5f/pop-to-EDI
2368     5b/pop-to-EBX
2369     5a/pop-to-EDX
2370     59/pop-to-ECX
2371     58/pop-to-EAX
2372     # . epilog
2373     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2374     5d/pop-to-EBP
2375     c3/return
2376 
2377 test-emit-hex-single-byte:
2378     # setup
2379     # . clear-stream(_test-output-stream)
2380     # . . push args
2381     68/push  _test-output-stream/imm32
2382     # . . call
2383     e8/call  clear-stream/disp32
2384     # . . discard args
2385     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2386     # . clear-stream(_test-output-buffered-file+4)
2387     # . . push args
2388     b8/copy-to-EAX  _test-output-buffered-file/imm32
2389     05/add-to-EAX  4/imm32
2390     50/push-EAX
2391     # . . call
2392     e8/call  clear-stream/disp32
2393     # . . discard args
2394     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2395     # emit-hex(_test-output-buffered-file, 0xab, 1)
2396     # . . push args
2397     68/push  1/imm32
2398     68/push  0xab/imm32
2399     68/push  _test-output-buffered-file/imm32
2400     # . . call
2401     e8/call  emit-hex/disp32
2402     # . . discard args
2403     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2404     # flush(_test-output-buffered-file)
2405     # . . push args
2406     68/push  _test-output-buffered-file/imm32
2407     # . . call
2408     e8/call  flush/disp32
2409     # . . discard args
2410     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2411     # check-ints-equal(*_test-output-stream->data, 'ab ', msg)
2412     # . . push args
2413     68/push  "F - test-emit-hex-single-byte"/imm32
2414     68/push  0x206261/imm32
2415     # . . push *_test-output-stream->data
2416     b8/copy-to-EAX  _test-output-stream/imm32
2417     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
2418     # . . call
2419     e8/call  check-ints-equal/disp32
2420     # . . discard args
2421     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2422     # . end
2423     c3/return
2424 
2425 test-emit-hex-multiple-byte:
2426     # setup
2427     # . clear-stream(_test-output-stream)
2428     # . . push args
2429     68/push  _test-output-stream/imm32
2430     # . . call
2431     e8/call  clear-stream/disp32
2432     # . . discard args
2433     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2434     # . clear-stream(_test-output-buffered-file+4)
2435     # . . push args
2436     b8/copy-to-EAX  _test-output-buffered-file/imm32
2437     05/add-to-EAX  4/imm32
2438     50/push-EAX
2439     # . . call
2440     e8/call  clear-stream/disp32
2441     # . . discard args
2442     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2443     # emit-hex(_test-output-buffered-file, 0x1234, 2)
2444     # . . push args
2445     68/push  2/imm32
2446     68/push  0x1234/imm32
2447     68/push  _test-output-buffered-file/imm32
2448     # . . call
2449     e8/call  emit-hex/disp32
2450     # . . discard args
2451     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2452     # flush(_test-output-buffered-file)
2453     # . . push args
2454     68/push  _test-output-buffered-file/imm32
2455     # . . call
2456     e8/call  flush/disp32
2457     # . . discard args
2458     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2459     # check-stream-equal(_test-output-stream, "34 12 ", msg)
2460     # . . push args
2461     68/push  "F - test-emit-hex-multiple-byte/1"/imm32
2462     68/push  "34 12 "/imm32
2463     68/push  _test-output-stream/imm32
2464     # . . call
2465     e8/call  check-stream-equal/disp32
2466     # . . discard args
2467     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2468     # . end
2469     c3/return
2470 
2471 test-emit-hex-zero-pad:
2472     # setup
2473     # . clear-stream(_test-output-stream)
2474     # . . push args
2475     68/push  _test-output-stream/imm32
2476     # . . call
2477     e8/call  clear-stream/disp32
2478     # . . discard args
2479     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2480     # . clear-stream(_test-output-buffered-file+4)
2481     # . . push args
2482     b8/copy-to-EAX  _test-output-buffered-file/imm32
2483     05/add-to-EAX  4/imm32
2484     50/push-EAX
2485     # . . call
2486     e8/call  clear-stream/disp32
2487     # . . discard args
2488     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2489     # emit-hex(_test-output-buffered-file, 0xab, 2)
2490     # . . push args
2491     68/push  2/imm32
2492     68/push  0xab/imm32
2493     68/push  _test-output-buffered-file/imm32
2494     # . . call
2495     e8/call  emit-hex/disp32
2496     # . . discard args
2497     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2498     # flush(_test-output-buffered-file)
2499     # . . push args
2500     68/push  _test-output-buffered-file/imm32
2501     # . . call
2502     e8/call  flush/disp32
2503     # . . discard args
2504     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2505     # check(_test-output-stream->data == 'ab 00 ')
2506     # . . push args
2507     68/push  "F - test-emit-hex-zero-pad/1"/imm32
2508     68/push  "ab 00 "/imm32
2509     68/push  _test-output-stream/imm32
2510     # . . call
2511     e8/call  check-stream-equal/disp32
2512     # . . discard args
2513     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2514     # . end
2515     c3/return
2516 
2517 test-emit-hex-negative:
2518     # setup
2519     # . clear-stream(_test-output-stream)
2520     # . . push args
2521     68/push  _test-output-stream/imm32
2522     # . . call
2523     e8/call  clear-stream/disp32
2524     # . . discard args
2525     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2526     # . clear-stream(_test-output-buffered-file+4)
2527     # . . push args
2528     b8/copy-to-EAX  _test-output-buffered-file/imm32
2529     05/add-to-EAX  4/imm32
2530     50/push-EAX
2531     # . . call
2532     e8/call  clear-stream/disp32
2533     # . . discard args
2534     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2535     # emit-hex(_test-output-buffered-file, -1, 2)
2536     # . . push args
2537     68/push  2/imm32
2538     68/push  -1/imm32
2539     68/push  _test-output-buffered-file/imm32
2540     # . . call
2541     e8/call  emit-hex/disp32
2542     # . . discard args
2543     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2544     # flush(_test-output-buffered-file)
2545     # . . push args
2546     68/push  _test-output-buffered-file/imm32
2547     # . . call
2548     e8/call  flush/disp32
2549     # . . discard args
2550     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2551     # check-stream-equal(_test-output-stream == "ff ff ")
2552     # . . push args
2553     68/push  "F - test-emit-hex-negative/1"/imm32
2554     68/push  "ff ff "/imm32
2555     68/push  _test-output-stream/imm32
2556     # . . call
2557     e8/call  check-stream-equal/disp32
2558     # . . discard args
2559     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2560     # . end
2561     c3/return
2562 
2563 # print 'arr' in hex with a space after every byte
2564 emit-hex-array:  # out : (address buffered-file), arr : (address array byte) -> <void>
2565     # . prolog
2566     55/push-EBP
2567     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2568     # . save registers
2569     50/push-EAX
2570     51/push-ECX
2571     52/push-EDX
2572     57/push-EDI
2573     # EDI = out
2574     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
2575     # EDX = arr  # <== 0xbdffffe4
2576     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0xc/disp8       .                 # copy *(EBP+12) to EDX
2577     # curr/ECX = arr->data
2578     8d/copy-address                 1/mod/*+disp8   2/rm32/EDX    .           .             .           1/r32/ECX   4/disp8         .                 # copy EDX+4 to ECX
2579     # max/EDX = arr->data + arr->length
2580     8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # copy *EDX to EDX
2581     01/add                          3/mod/direct    2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # add ECX to EDX
2582     # EAX = 0
2583     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
2584 $emit-hex-array:loop:
2585     # if (curr >= width) break
2586     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
2587     73/jump-if-greater-or-equal-unsigned  $emit-hex-array:end/disp8
2588     # emit-hex(out, *curr, width=1)
2589     # . . push args
2590     68/push  1/imm32/width
2591     8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
2592     50/push-EAX
2593     57/push-EDI
2594     # . . call
2595     e8/call  emit-hex/disp32
2596     # . . discard args
2597     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2598     # ++curr
2599     41/increment-ECX
2600     eb/jump  $emit-hex-array:loop/disp8
2601 $emit-hex-array:end:
2602     # . restore registers
2603     5f/pop-to-EDI
2604     5a/pop-to-EDX
2605     59/pop-to-ECX
2606     58/pop-to-EAX
2607     # . epilog
2608     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2609     5d/pop-to-EBP
2610     c3/return
2611 
2612 test-emit-hex-array:
2613     # . prolog
2614     55/push-EBP
2615     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2616     # setup
2617     # . clear-stream(_test-output-stream)
2618     # . . push args
2619     68/push  _test-output-stream/imm32
2620     # . . call
2621     e8/call  clear-stream/disp32
2622     # . . discard args
2623     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2624     # . clear-stream(_test-output-buffered-file+4)
2625     # . . push args
2626     b8/copy-to-EAX  _test-output-buffered-file/imm32
2627     05/add-to-EAX  4/imm32
2628     50/push-EAX
2629     # . . call
2630     e8/call  clear-stream/disp32
2631     # . . discard args
2632     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2633     # var arr/ECX (address array byte) = [01, 02, 03]
2634     68/push  0x00030201/imm32  # bytes 01 02 03
2635     68/push  3/imm32/length
2636     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2637     # emit-hex-array(_test-output-buffered-file, arr)
2638     # . . push args
2639     51/push-ECX
2640     68/push  _test-output-buffered-file/imm32
2641     # . . call
2642     e8/call  emit-hex-array/disp32
2643     # . . discard args
2644     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2645     # . flush(_test-output-buffered-file)
2646     # . . push args
2647     68/push  _test-output-buffered-file/imm32
2648     # . . call
2649     e8/call  flush/disp32
2650     # . . discard args
2651     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2652 +-- 33 lines: #?     # dump output ---------------------------------------------------------------------------------------------------------------------------
2685     # check-next-stream-line-equal(_test-output-stream, "01 02 03 ", msg)
2686     # . . push args
2687     68/push  "F - test-emit-hex-array"/imm32
2688     68/push  "01 02 03 "/imm32
2689     68/push  _test-output-stream/imm32
2690     # . . call
2691     e8/call  check-next-stream-line-equal/disp32
2692     # . . discard args
2693     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2694     # . epilog
2695     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2696     5d/pop-to-EBP
2697     c3/return
2698 
2699 compute-width: # word : (address array byte) -> EAX : int
2700     # . prolog
2701     55/push-EBP
2702     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2703     # . save registers
2704     51/push-ECX
2705     # EAX = word
2706     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to ECX
2707     # ECX = word + word->length
2708     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2709     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
2710     # EAX = word->data
2711     05/add-to-EAX  4/imm32
2712     # var in/ECX : (address slice) = {EAX, ECX}
2713     51/push-ECX
2714     50/push-EAX
2715     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2716     # return compute-width-of-slice(ECX)
2717     # . . push args
2718     51/push-ECX
2719     # . . call
2720     e8/call  compute-width-of-slice/disp32
2721     # . . discard args
2722     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2723 $compute-width:end:
2724     # . reclaim locals
2725     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2726     # . restore registers
2727     59/pop-to-ECX
2728     # . epilog
2729     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2730     5d/pop-to-EBP
2731     c3/return
2732 
2733 compute-width-of-slice: # s : (address slice) -> EAX : int
2734     # . prolog
2735     55/push-EBP
2736     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2737     # . save registers
2738     51/push-ECX
2739     # ECX = s
2740     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
2741     # if (has-metadata?(word, "imm32")) return 4
2742     # . EAX = has-metadata?(word, "imm32")
2743     # . . push args
2744     68/push  "imm32"/imm32
2745     51/push-ECX
2746     # . . call
2747     e8/call  has-metadata?/disp32
2748     # . . discard args
2749     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2750     # . if (EAX != 0) return 4
2751     3d/compare-EAX-and  0/imm32
2752     b8/copy-to-EAX  4/imm32         # ZF is set, so we can overwrite EAX now
2753     75/jump-if-not-equal  $compute-width-of-slice:end/disp8
2754     # if (has-metadata?(word, "disp32")) return 4
2755     # . EAX = has-metadata?(word, "disp32")
2756     # . . push args
2757     68/push  "disp32"/imm32
2758     51/push-ECX
2759     # . . call
2760     e8/call  has-metadata?/disp32
2761     # . . discard args
2762     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2763     # . if (EAX != 0) return 4
2764     3d/compare-EAX-and  0/imm32
2765     b8/copy-to-EAX  4/imm32         # ZF is set, so we can overwrite EAX now
2766     75/jump-if-not-equal  $compute-width-of-slice:end/disp8
2767     # if (has-metadata?(word, "imm16")) return 2
2768     # . EAX = has-metadata?(word, "imm16")
2769     # . . push args
2770     68/push  "imm16"/imm32
2771     51/push-ECX
2772     # . . call
2773     e8/call  has-metadata?/disp32
2774     # . . discard args
2775     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2776     # . if (EAX != 0) return 2
2777     3d/compare-EAX-and  0/imm32
2778     b8/copy-to-EAX  2/imm32         # ZF is set, so we can overwrite EAX now
2779     75/jump-if-not-equal  $compute-width-of-slice:end/disp8
2780     # if (has-metadata?(word, "disp16")) return 2
2781     # . EAX = has-metadata?(word, "disp16")
2782     # . . push args
2783     68/push  "disp16"/imm32
2784     51/push-ECX
2785     # . . call
2786     e8/call  has-metadata?/disp32
2787     # . . discard args
2788     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
2789     # . if (EAX != 0) return 2
2790     3d/compare-EAX-and  0/imm32
2791     b8/copy-to-EAX  2/imm32         # ZF is set, so we can overwrite EAX now
2792     75/jump-if-not-equal  $compute-width-of-slice:end/disp8
2793     # otherwise return 1
2794     b8/copy-to-EAX  1/imm32
2795 $compute-width-of-slice:end:
2796     # . restore registers
2797     59/pop-to-ECX
2798     # . epilog
2799     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2800     5d/pop-to-EBP
2801     c3/return
2802 
2803 test-compute-width:
2804     # . prolog
2805     55/push-EBP
2806     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2807 $test-compute-width:imm8:
2808     # EAX = compute-width("0x2/imm8")
2809     # . . push args
2810     68/push  "0x2/imm8"/imm32
2811     # . . call
2812     e8/call  compute-width/disp32
2813     # . . discard args
2814     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2815     # check-ints-equal(EAX, 1, msg)
2816     # . . push args
2817     68/push  "F - test-compute-width: 0x2/imm8"/imm32
2818     50/push-EAX
2819     68/push  1/imm32
2820     # . . call
2821     e8/call  check-ints-equal/disp32
2822     # . . discard args
2823     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2824 $test-compute-width:imm16:
2825     # EAX = compute-width("4/imm16")
2826     # . . push args
2827     68/push  "4/imm16"/imm32
2828     # . . call
2829     e8/call  compute-width/disp32
2830     # . . discard args
2831     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2832     # check-ints-equal(EAX, 2, msg)
2833     # . . push args
2834     68/push  "F - test-compute-width: 4/imm16"/imm32
2835     50/push-EAX
2836     68/push  2/imm32
2837     # . . call
2838     e8/call  check-ints-equal/disp32
2839     # . . discard args
2840     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2841 $test-compute-width:imm32:
2842     # EAX = compute-width("4/imm32")
2843     # . . push args
2844     68/push  "4/imm32"/imm32
2845     # . . call
2846     e8/call  compute-width/disp32
2847     # . . discard args
2848     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2849     # check-ints-equal(EAX, 4, msg)
2850     # . . push args
2851     68/push  "F - test-compute-width: 4/imm32"/imm32
2852     50/push-EAX
2853     68/push  4/imm32
2854     # . . call
2855     e8/call  check-ints-equal/disp32
2856     # . . discard args
2857     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2858 $test-compute-width:disp8:
2859     # EAX = compute-width("foo/disp8")
2860     # . . push args
2861     68/push  "foo/disp8"/imm32
2862     # . . call
2863     e8/call  compute-width/disp32
2864     # . . discard args
2865     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2866     # check-ints-equal(EAX, 1, msg)
2867     # . . push args
2868     68/push  "F - test-compute-width: foo/disp8"/imm32
2869     50/push-EAX
2870     68/push  1/imm32
2871     # . . call
2872     e8/call  check-ints-equal/disp32
2873     # . . discard args
2874     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2875 $test-compute-width:disp16:
2876     # EAX = compute-width("foo/disp16")
2877     # . . push args
2878     68/push  "foo/disp16"/imm32
2879     # . . call
2880     e8/call  compute-width/disp32
2881     # . . discard args
2882     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2883     # check-ints-equal(EAX, 2, msg)
2884     # . . push args
2885     68/push  "F - test-compute-width: foo/disp16"/imm32
2886     50/push-EAX
2887     68/push  2/imm32
2888     # . . call
2889     e8/call  check-ints-equal/disp32
2890     # . . discard args
2891     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2892 $test-compute-width:disp32:
2893     # EAX = compute-width("foo/disp32")
2894     # . . push args
2895     68/push  "foo/disp32"/imm32
2896     # . . call
2897     e8/call  compute-width/disp32
2898     # . . discard args
2899     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2900     # check-ints-equal(EAX, 4, msg)
2901     # . . push args
2902     68/push  "F - test-compute-width: foo/disp32"/imm32
2903     50/push-EAX
2904     68/push  4/imm32
2905     # . . call
2906     e8/call  check-ints-equal/disp32
2907     # . . discard args
2908     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2909 $test-compute-width:no-metadata:
2910     # EAX = compute-width("45")
2911     # . . push args
2912     68/push  "45"/imm32
2913     # . . call
2914     e8/call  compute-width/disp32
2915     # . . discard args
2916     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2917     # check-ints-equal(EAX, 1, msg)
2918     # . . push args
2919     68/push  "F - test-compute-width: 45 (no metadata)"/imm32
2920     50/push-EAX
2921     68/push  1/imm32
2922     # . . call
2923     e8/call  check-ints-equal/disp32
2924     # . . discard args
2925     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2926     # . epilog
2927     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2928     5d/pop-to-EBP
2929     c3/return
2930 
2931 is-label?: # word : (address slice) -> EAX : boolean
2932     # . prolog
2933     55/push-EBP
2934     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2935     # . save registers
2936     51/push-ECX
2937     # ECX = word
2938     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
2939     # ECX = word->end
2940     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ECX+4) to ECX
2941     # return *(word->end - 1) == ':'
2942     # . EAX = 0
2943     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
2944     # . EAX = *((char *) word->end - 1)
2945     8a/copy-byte                    1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/AL    -1/disp8         .                 # copy byte at *(ECX-1) to AL
2946     # . return (EAX == ':')
2947     3d/compare-EAX-and  0x3a/imm32/colon
2948     b8/copy-to-EAX  1/imm32/true
2949     74/jump-if-equal  $is-label?:end/disp8
2950     b8/copy-to-EAX  0/imm32/false
2951 $is-label?:end:
2952     # . restore registers
2953     59/pop-to-ECX
2954     # . epilog
2955     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
2956     5d/pop-to-EBP
2957     c3/return
2958 
2959 test-is-label?:
2960     # . prolog
2961     55/push-EBP
2962     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
2963 $test-is-label?:true:
2964     # (EAX..ECX) = "AAA:"
2965     b8/copy-to-EAX  "AAA:"/imm32
2966     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2967     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
2968     05/add-to-EAX  4/imm32
2969     # var slice/ECX = {EAX, ECX}
2970     51/push-ECX
2971     50/push-EAX
2972     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2973     # is-label?(slice/ECX)
2974     # . . push args
2975     51/push-ECX
2976     # . . call
2977     e8/call  is-label?/disp32
2978     # . . discard args
2979     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
2980     # check-ints-equal(EAX, 1, msg)
2981     # . . push args
2982     68/push  "F - test-is-label?:true"/imm32
2983     68/push  1/imm32
2984     50/push-EAX
2985     # . . call
2986     e8/call  check-ints-equal/disp32
2987     # . . discard args
2988     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
2989 $test-is-label?:false:
2990     # (EAX..ECX) = "AAA"
2991     b8/copy-to-EAX  "AAA"/imm32
2992     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
2993     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
2994     05/add-to-EAX  4/imm32
2995     # var slice/ECX = {EAX, ECX}
2996     51/push-ECX
2997     50/push-EAX
2998     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
2999     # is-label?(slice/ECX)
3000     # . . push args
3001     51/push-ECX
3002     # . . call
3003     e8/call  is-label?/disp32
3004     # . . discard args
3005     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
3006     # check-ints-equal(EAX, 0, msg)
3007     # . . push args
3008     68/push  "F - test-is-label?:false"/imm32
3009     68/push  0/imm32
3010     50/push-EAX
3011     # . . call
3012     e8/call  check-ints-equal/disp32
3013     # . . discard args
3014     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
3015     # . epilog
3016     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
3017     5d/pop-to-EBP
3018     c3/return
3019 
3020 == data
3021 
3022 _test-input-stream:
3023     # current write index
3024     0/imm32
3025     # current read index
3026     0/imm32
3027     # length
3028     0x100/imm32  # 256 bytes
3029     # data (16 lines x 16 bytes/line)
3030     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3031     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3032     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3033     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3034     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3035     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3036     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3037     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3038     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3039     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3040     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3041     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3042     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3043     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3044     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3045     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3046 
3047 # a test buffered file for _test-input-stream
3048 _test-input-buffered-file:
3049     # file descriptor or (address stream)
3050     _test-input-stream/imm32
3051     # current write index
3052     0/imm32
3053     # current read index
3054     0/imm32
3055     # length
3056     6/imm32
3057     # data
3058     00 00 00 00 00 00  # 6 bytes
3059 
3060 _test-output-stream:
3061     # current write index
3062     0/imm32
3063     # current read index
3064     0/imm32
3065     # length
3066     0x200/imm32  # 512 bytes
3067     # data (32 lines x 16 bytes/line)
3068     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3069     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3070     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3071     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3072     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3073     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3074     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3075     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3076     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3077     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3078     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3079     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3080     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3081     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3082     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3083     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3084     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3085     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3086     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3087     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3088     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3089     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3090     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3091     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3092     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3093     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3094     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3095     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3096     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3097     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3098     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3099     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3100 
3101 # a test buffered file for _test-output-stream
3102 _test-output-buffered-file:
3103     # file descriptor or (address stream)
3104     _test-output-stream/imm32
3105     # current write index
3106     0/imm32
3107     # current read index
3108     0/imm32
3109     # length
3110     6/imm32
3111     # data
3112     00 00 00 00 00 00  # 6 bytes
3113 
3114 _test-data-segment:
3115   64/d 61/a 74/t 61/a
3116 _test-data-segment-end:
3117 
3118 # . . vim:nowrap:textwidth=0