https://github.com/akkartik/mu/blob/master/078table.subx
   1 # A table is a stream of (key, value) rows.
   2 #
   3 # Each row consists of a 4-byte key (address to a string) and a variable-size
   4 # value.
   5 #
   6 # Accessing the table performs a linear scan for a key string, and always
   7 # requires passing in the row size.
   8 #
   9 # Table primitives have the form <variant>(stream, <arg>, row-size, ...) -> address/EAX
  10 #
  11 # The following table shows available options for <variant>:
  12 #   if not found:           | arg=string              arg=slice
  13 #   ------------------------+---------------------------------------------------
  14 #   abort                   | get                     get-slice
  15 #   insert key              | get-or-insert           leaky-get-or-insert-slice
  16 #   stop                    | get-or-stop             get-slice-or-stop
  17 #   return null             | maybe-get               maybe-get-slice
  18 # Some variants may take extra args.
  19 
  20 == code
  21 #   instruction                     effective address                                                   register    displacement    immediate
  22 # . op          subop               mod             rm32          base        index         scale       r32
  23 # . 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
  24 
  25 # if no row is found, abort
  26 get:  # table : (address stream {string, _}), key : (address string), row-size : int, abort-message-prefix : (address string) -> EAX : (address _)
  27     # pseudocode:
  28     #   curr = table->data
  29     #   max = &table->data[table->write]
  30     #   while curr < max
  31     #     if string-equal?(key, *curr)
  32     #       return curr+4
  33     #     curr += row-size
  34     #   abort
  35     #
  36     # . prolog
  37     55/push-EBP
  38     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
  39     # . save registers
  40     51/push-ECX
  41     52/push-EDX
  42     56/push-ESI
  43     # ESI = table
  44     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
  45     # curr/ECX = table->data
  46     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
  47     # max/EDX = table->data + table->write
  48     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
  49     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
  50 $get:search-loop:
  51     # if (curr >= max) abort
  52     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
  53     73/jump-if-greater-or-equal-unsigned  $get:abort/disp8
  54     # if (string-equal?(key, *curr)) return curr+4
  55     # . EAX = string-equal?(key, *curr)
  56     # . . push args
  57     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
  58     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
  59     # . . call
  60     e8/call  string-equal?/disp32
  61     # . . discard args
  62     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
  63     # . if (EAX != 0) return EAX = curr+4
  64     3d/compare-EAX-and  0/imm32
  65     74/jump-if-equal  $get:mismatch/disp8
  66     8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
  67     eb/jump  $get:end/disp8
  68 $get:mismatch:
  69     # curr += row-size
  70     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
  71     # loop
  72     eb/jump  $get:search-loop/disp8
  73 $get:end:
  74     # . restore registers
  75     5e/pop-to-ESI
  76     5a/pop-to-EDX
  77     59/pop-to-ECX
  78     # . epilog
  79     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
  80     5d/pop-to-EBP
  81     c3/return
  82 
  83 $get:abort:
  84     # . _write(2/stderr, abort-message-prefix)
  85     # . . push args
  86     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
  87     68/push  2/imm32/stderr
  88     # . . call
  89     e8/call  _write/disp32
  90     # . . discard args
  91     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
  92     # . _write(2/stderr, error)
  93     # . . push args
  94     68/push  ": get: key not found: "/imm32
  95     68/push  2/imm32/stderr
  96     # . . call
  97     e8/call  _write/disp32
  98     # . . discard args
  99     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 100     # . _write(2/stderr, key)
 101     # . . push args
 102     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 103     68/push  2/imm32/stderr
 104     # . . call
 105     e8/call  _write/disp32
 106     # . . discard args
 107     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 108     # . _write(2/stderr, "\n")
 109     # . . push args
 110     68/push  "\n"/imm32
 111     68/push  2/imm32/stderr
 112     # . . call
 113     e8/call  _write/disp32
 114     # . . discard args
 115     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 116     # . syscall(exit, 1)
 117     bb/copy-to-EBX  1/imm32
 118     b8/copy-to-EAX  1/imm32/exit
 119     cd/syscall  0x80/imm8
 120     # never gets here
 121 
 122 test-get:
 123     # . prolog
 124     55/push-EBP
 125     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 126     # - setup: create a table with a couple of keys
 127     # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
 128     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
 129     68/push  0x10/imm32/length
 130     68/push  0/imm32/read
 131     68/push  0/imm32/write
 132     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 133     # insert(table, "code", 8 bytes per row)
 134     # . . push args
 135     68/push  8/imm32/row-size
 136     68/push  "code"/imm32
 137     51/push-ECX
 138     # . . call
 139     e8/call  get-or-insert/disp32
 140     # . . discard args
 141     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 142     # insert(table, "data", 8 bytes per row)
 143     # . . push args
 144     68/push  8/imm32/row-size
 145     68/push  "data"/imm32
 146     51/push-ECX
 147     # . . call
 148     e8/call  get-or-insert/disp32
 149     # . . discard args
 150     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 151 $test-get:check1:
 152     # EAX = get(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/disp32
 159     # . . discard args
 160     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 161     # check-ints-equal(EAX - table->data, 4, msg)
 162     # . check-ints-equal(EAX - table, 16, msg)
 163     # . . push args
 164     68/push  "F - test-get/0"/imm32
 165     68/push  0x10/imm32
 166     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 167     50/push-EAX
 168     # . . call
 169     e8/call  check-ints-equal/disp32
 170     # . . discard args
 171     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 172 $test-get:check2:
 173     # EAX = get(table, "data", 8 bytes per row)
 174     # . . push args
 175     68/push  8/imm32/row-size
 176     68/push  "data"/imm32
 177     51/push-ECX
 178     # . . call
 179     e8/call  get/disp32
 180     # . . discard args
 181     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 182     # check-ints-equal(EAX - table->data, 12, msg)
 183     # . check-ints-equal(EAX - table, 24, msg)
 184     # . . push args
 185     68/push  "F - test-get/1"/imm32
 186     68/push  0x18/imm32
 187     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 188     50/push-EAX
 189     # . . call
 190     e8/call  check-ints-equal/disp32
 191     # . . discard args
 192     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 193 $test-get:end:
 194     # . epilog
 195     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 196     5d/pop-to-EBP
 197     c3/return
 198 
 199 # if no row is found, abort
 200 get-slice:  # table : (address stream {string, _}), key : (address slice), row-size : int, abort-message-prefix : (address string) -> EAX : (address _)
 201     # pseudocode:
 202     #   curr = table->data
 203     #   max = &table->data[table->write]
 204     #   while curr < max
 205     #     if slice-equal?(key, *curr)
 206     #       return curr+4
 207     #     curr += row-size
 208     #   abort
 209     #
 210     # . prolog
 211     55/push-EBP
 212     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 213     # . save registers
 214     51/push-ECX
 215     52/push-EDX
 216     56/push-ESI
 217     # ESI = table
 218     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 219     # curr/ECX = table->data
 220     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
 221     # max/EDX = table->data + table->write
 222     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
 223     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
 224 $get-slice:search-loop:
 225     # if (curr >= max) abort
 226     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
 227     73/jump-if-greater-or-equal-unsigned  $get-slice:abort/disp8
 228     # if (slice-equal?(key, *curr)) return curr+4
 229     # . EAX = slice-equal?(key, *curr)
 230     # . . push args
 231     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 232     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 233     # . . call
 234     e8/call  slice-equal?/disp32
 235     # . . discard args
 236     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 237     # . if (EAX != 0) return EAX = curr+4
 238     3d/compare-EAX-and  0/imm32
 239     74/jump-if-equal  $get-slice:mismatch/disp8
 240     8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
 241     eb/jump  $get-slice:end/disp8
 242 $get-slice:mismatch:
 243     # curr += row-size
 244     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
 245     # loop
 246     eb/jump  $get-slice:search-loop/disp8
 247 $get-slice:end:
 248     # . restore registers
 249     5e/pop-to-ESI
 250     5a/pop-to-EDX
 251     59/pop-to-ECX
 252     # . epilog
 253     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 254     5d/pop-to-EBP
 255     c3/return
 256 
 257 $get-slice:abort:
 258     # . _write(2/stderr, abort-message-prefix)
 259     # . . push args
 260     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
 261     68/push  2/imm32/stderr
 262     # . . call
 263     e8/call  _write/disp32
 264     # . . discard args
 265     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 266     # . _write(2/stderr, error)
 267     # . . push args
 268     68/push  ": get-slice: key not found: "/imm32
 269     68/push  2/imm32/stderr
 270     # . . call
 271     e8/call  _write/disp32
 272     # . . discard args
 273     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 274     # . write-slice-buffered(Stderr, key)
 275     # . . push args
 276     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 277     68/push  Stderr/imm32
 278     # . . call
 279     e8/call  write-slice-buffered/disp32
 280     # . . discard args
 281     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 282     # . flush(Stderr)
 283     # . . push args
 284     68/push  Stderr/imm32
 285     # . . call
 286     e8/call  flush/disp32
 287     # . . discard args
 288     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 289     # . _write(2/stderr, "\n")
 290     # . . push args
 291     68/push  "\n"/imm32
 292     68/push  2/imm32/stderr
 293     # . . call
 294     e8/call  _write/disp32
 295     # . . discard args
 296     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 297     # . syscall(exit, 1)
 298     bb/copy-to-EBX  1/imm32
 299     b8/copy-to-EAX  1/imm32/exit
 300     cd/syscall  0x80/imm8
 301     # never gets here
 302 
 303 test-get-slice:
 304     # . prolog
 305     55/push-EBP
 306     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 307     # - setup: create a table with a couple of keys
 308     # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
 309     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
 310     68/push  0x10/imm32/length
 311     68/push  0/imm32/read
 312     68/push  0/imm32/write
 313     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 314     # insert(table, "code", 8 bytes per row)
 315     # . . push args
 316     68/push  8/imm32/row-size
 317     68/push  "code"/imm32
 318     51/push-ECX
 319     # . . call
 320     e8/call  get-or-insert/disp32
 321     # . . discard args
 322     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 323     # insert(table, "data", 8 bytes per row)
 324     # . . push args
 325     68/push  8/imm32/row-size
 326     68/push  "data"/imm32
 327     51/push-ECX
 328     # . . call
 329     e8/call  get-or-insert/disp32
 330     # . . discard args
 331     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 332 $test-get-slice:check1:
 333     # (EAX..EDX) = "code"
 334     b8/copy-to-EAX  "code"/imm32
 335     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
 336     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
 337     05/add-to-EAX  4/imm32
 338     # var slice/EDX = {EAX, EDX}
 339     52/push-EDX
 340     50/push-EAX
 341     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
 342     # EAX = get-slice(table, "code", 8 bytes per row)
 343     # . . push args
 344     68/push  8/imm32/row-size
 345     52/push-EDX
 346     51/push-ECX
 347     # . . call
 348     e8/call  get-slice/disp32
 349     # . . discard args
 350     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 351     # check-ints-equal(EAX - table->data, 4, msg)  # first row's value slot returned
 352     # . check-ints-equal(EAX - table, 16, msg)
 353     # . . push args
 354     68/push  "F - test-get-slice/0"/imm32
 355     68/push  0x10/imm32
 356     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 357     50/push-EAX
 358     # . . call
 359     e8/call  check-ints-equal/disp32
 360     # . . discard args
 361     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 362 $test-get-slice:check2:
 363     # (EAX..EDX) = "data"
 364     b8/copy-to-EAX  "data"/imm32
 365     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
 366     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
 367     05/add-to-EAX  4/imm32
 368     # var slice/EDX = {EAX, EDX}
 369     52/push-EDX
 370     50/push-EAX
 371     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
 372     # EAX = get-slice(table, "data" slice, 8 bytes per row)
 373     # . . push args
 374     68/push  8/imm32/row-size
 375     52/push-EDX
 376     51/push-ECX
 377     # . . call
 378     e8/call  get-slice/disp32
 379     # . . discard args
 380     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 381     # check-ints-equal(EAX - table->data, 12, msg)
 382     # . check-ints-equal(EAX - table, 24, msg)
 383     # . . push args
 384     68/push  "F - test-get-slice/1"/imm32
 385     68/push  0x18/imm32
 386     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 387     50/push-EAX
 388     # . . call
 389     e8/call  check-ints-equal/disp32
 390     # . . discard args
 391     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 392 $test-get-slice:end:
 393     # . epilog
 394     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 395     5d/pop-to-EBP
 396     c3/return
 397 
 398 # if no row is found, save 'key' to the next available row
 399 # if there are no rows free, abort
 400 # return the address of the value
 401 # Beware: assume keys are immutable; they're inserted by reference
 402 # TODO: pass in an allocation descriptor
 403 get-or-insert:  # table : (address stream {string, _}), key : (address string), row-size : int -> EAX : (address _)
 404     # pseudocode:
 405     #   curr = table->data
 406     #   max = &table->data[table->write]
 407     #   while curr < max
 408     #     if string-equal?(key, *curr)
 409     #       return curr+4
 410     #     curr += row-size
 411     #   if table->write >= table->length
 412     #     abort
 413     #   zero-out(max, row-size)
 414     #   *max = key
 415     #   table->write += row-size
 416     #   return max+4
 417     #
 418     # . prolog
 419     55/push-EBP
 420     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 421     # . save registers
 422     51/push-ECX
 423     52/push-EDX
 424     56/push-ESI
 425     # ESI = table
 426     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 427     # curr/ECX = table->data
 428     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
 429     # max/EDX = table->data + table->write
 430     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
 431     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
 432 $get-or-insert:search-loop:
 433     # if (curr >= max) break
 434     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
 435     73/jump-if-greater-or-equal-unsigned  $get-or-insert:not-found/disp8
 436     # if (string-equal?(key, *curr)) return curr+4
 437     # . EAX = string-equal?(key, *curr)
 438     # . . push args
 439     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 440     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 441     # . . call
 442     e8/call  string-equal?/disp32
 443     # . . discard args
 444     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 445     # . if (EAX != 0) return EAX = curr+4
 446     3d/compare-EAX-and  0/imm32
 447     74/jump-if-equal  $get-or-insert:mismatch/disp8
 448     8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
 449     eb/jump  $get-or-insert:end/disp8
 450 $get-or-insert:mismatch:
 451     # curr += row-size
 452     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
 453     # loop
 454     eb/jump  $get-or-insert:search-loop/disp8
 455 $get-or-insert:not-found:
 456     # result/EAX = 0
 457     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 458     # if (table->write >= table->length) abort
 459     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
 460     3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # compare ECX with *(ESI+8)
 461     73/jump-if-greater-or-equal-unsigned  $get-or-insert:abort/disp8
 462     # zero-out(max, row-size)
 463     # . . push args
 464     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
 465     52/push-EDX
 466     # . . call
 467     e8/call  zero-out/disp32
 468     # . . discard args
 469     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 470     # *max = key
 471     # . EAX = key
 472     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
 473     # . *max = EAX
 474     89/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDX
 475     # table->write += row-size
 476     # . EAX = row-size
 477     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0x10/disp8      .                 # copy *(EBP+16) to EAX
 478     # . table->write += EAX
 479     01/add                          0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # add EAX to *ESI
 480     # return max+4
 481     # . EAX = max
 482     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy EDX to EAX
 483     # . EAX += 4
 484     05/add-to-EAX  4/imm32
 485 $get-or-insert:end:
 486     # . restore registers
 487     5e/pop-to-ESI
 488     5a/pop-to-EDX
 489     59/pop-to-ECX
 490     # . epilog
 491     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 492     5d/pop-to-EBP
 493     c3/return
 494 
 495 $get-or-insert:abort:
 496     # . _write(2/stderr, error)
 497     # . . push args
 498     68/push  "get-or-insert: table is full\n"/imm32
 499     68/push  2/imm32/stderr
 500     # . . call
 501     e8/call  _write/disp32
 502     # . . discard args
 503     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 504     # . syscall(exit, 1)
 505     bb/copy-to-EBX  1/imm32
 506     b8/copy-to-EAX  1/imm32/exit
 507     cd/syscall  0x80/imm8
 508     # never gets here
 509 
 510 test-get-or-insert:
 511     # . prolog
 512     55/push-EBP
 513     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 514     # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
 515     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
 516     68/push  0x10/imm32/length
 517     68/push  0/imm32/read
 518     68/push  0/imm32/write
 519     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 520 $test-get-or-insert:first-call:
 521     # - start with an empty table, insert one key, verify that it was inserted
 522     # EAX = get-or-insert(table, "code", 8 bytes per row)
 523     # . . push args
 524     68/push  8/imm32/row-size
 525     68/push  "code"/imm32
 526     51/push-ECX
 527     # . . call
 528     e8/call  get-or-insert/disp32
 529     # . . discard args
 530     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 531     # check-ints-equal(EAX - table->data, 4, msg)  # first row's value slot returned
 532     # . check-ints-equal(EAX - table, 16, msg)
 533     # . . push args
 534     68/push  "F - test-get-or-insert/0"/imm32
 535     68/push  0x10/imm32
 536     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 537     50/push-EAX
 538     # . . call
 539     e8/call  check-ints-equal/disp32
 540     # . . discard args
 541     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 542 $test-get-or-insert:check2:
 543     # check-ints-equal(table->write, row-size = 8, msg)
 544     # . . push args
 545     68/push  "F - test-get-or-insert/1"/imm32
 546     68/push  8/imm32/row-size
 547     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 548     # . . call
 549     e8/call  check-ints-equal/disp32
 550     # . . discard args
 551     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 552     # check-string-equal(*table->data, "code", msg)
 553     # . . push args
 554     68/push  "F - test-get-or-insert/2"/imm32
 555     68/push  "code"/imm32
 556     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
 557     # . . call
 558     e8/call  check-string-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:second-call:
 562     # - insert the same key again, verify that it was reused
 563     # EAX = get-or-insert(table, "code", 8 bytes per row)
 564     # . . push args
 565     68/push  8/imm32/row-size
 566     68/push  "code"/imm32
 567     51/push-ECX
 568     # . . call
 569     e8/call  get-or-insert/disp32
 570     # . . discard args
 571     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 572     # check-ints-equal(EAX - table->data, 4, msg)
 573     # . check-ints-equal(EAX - table, 16, msg)
 574     # . . push args
 575     68/push  "F - test-get-or-insert/3"/imm32
 576     68/push  0x10/imm32
 577     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 578     50/push-EAX
 579     # . . call
 580     e8/call  check-ints-equal/disp32
 581     # . . discard args
 582     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 583     # no new row inserted
 584     # . check-ints-equal(table->write, row-size = 8, msg)
 585     # . . push args
 586     68/push  "F - test-get-or-insert/4"/imm32
 587     68/push  8/imm32/row-size
 588     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 589     # . . call
 590     e8/call  check-ints-equal/disp32
 591     # . . discard args
 592     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 593     # check-string-equal(*table->data, "code", msg)
 594     # . . push args
 595     68/push  "F - test-get-or-insert/5"/imm32
 596     68/push  "code"/imm32
 597     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
 598     # . . call
 599     e8/call  check-string-equal/disp32
 600     # . . discard args
 601     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 602 $test-get-or-insert:third-call:
 603     # - insert a new key, verify that it was inserted
 604     # EAX = get-or-insert(table, "data", 8 bytes per row)
 605     # . . push args
 606     68/push  8/imm32/row-size
 607     68/push  "data"/imm32
 608     51/push-ECX
 609     # . . call
 610     e8/call  get-or-insert/disp32
 611     # . . discard args
 612     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 613     # table gets a new row
 614     # check-ints-equal(EAX - table->data, 12, msg)  # second row's value slot returned
 615     # . check-ints-equal(EAX - table, 24, msg)
 616     # . . push args
 617     68/push  "F - test-get-or-insert/6"/imm32
 618     68/push  0x18/imm32
 619     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 620     50/push-EAX
 621     # . . call
 622     e8/call  check-ints-equal/disp32
 623     # . . discard args
 624     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 625     # check-ints-equal(table->write, 2 rows = 16, msg)
 626     # . . push args
 627     68/push  "F - test-get-or-insert/7"/imm32
 628     68/push  0x10/imm32/two-rows
 629     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 630     # . . call
 631     e8/call  check-ints-equal/disp32
 632     # . . discard args
 633     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 634     # check-string-equal(*table->data+8, "data", msg)
 635     # check-string-equal(*(table+20), "data", msg)
 636     # . . push args
 637     68/push  "F - test-get-or-insert/8"/imm32
 638     68/push  "data"/imm32
 639     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0x14/disp8      .                 # push *(ECX+20)
 640     # . . call
 641     e8/call  check-string-equal/disp32
 642     # . . discard args
 643     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 644 $test-get-or-insert:end:
 645     # . epilog
 646     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 647     5d/pop-to-EBP
 648     c3/return
 649 
 650 # if no row is found, save 'key' in the next available row
 651 # if there are no rows free, abort
 652 # WARNING: leaks memory
 653 # TODO: pass in an allocation descriptor
 654 leaky-get-or-insert-slice:  # table : (address stream {string, _}), key : (address slice), row-size : int -> EAX : (address _)
 655     # pseudocode:
 656     #   curr = table->data
 657     #   max = &table->data[table->write]
 658     #   while curr < max
 659     #     if slice-equal?(key, *curr)
 660     #       return curr+4
 661     #     curr += row-size
 662     #   if table->write >= table->length
 663     #     abort
 664     #   zero-out(max, row-size)
 665     #   *max = slice-to-string(Heap, key)
 666     #   table->write += row-size
 667     #   return max+4
 668     #
 669     # . prolog
 670     55/push-EBP
 671     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 672     # . save registers
 673     51/push-ECX
 674     52/push-EDX
 675     56/push-ESI
 676     # ESI = table
 677     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 678     # curr/ECX = table->data
 679     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
 680     # max/EDX = table->data + table->write
 681     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
 682     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
 683 $leaky-get-or-insert-slice:search-loop:
 684     # if (curr >= max) break
 685     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
 686     73/jump-if-greater-or-equal-unsigned  $leaky-get-or-insert-slice:not-found/disp8
 687     # if (slice-equal?(key, *curr)) return curr+4
 688     # . EAX = slice-equal?(key, *curr)
 689     # . . push args
 690     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 691     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 692     # . . call
 693     e8/call  slice-equal?/disp32
 694     # . . discard args
 695     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 696     # . if (EAX != 0) return EAX = curr+4
 697     3d/compare-EAX-and  0/imm32
 698     74/jump-if-equal  $leaky-get-or-insert-slice:mismatch/disp8
 699     8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
 700     eb/jump  $leaky-get-or-insert-slice:end/disp8
 701 $leaky-get-or-insert-slice:mismatch:
 702     # curr += row-size
 703     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
 704     # loop
 705     eb/jump  $leaky-get-or-insert-slice:search-loop/disp8
 706 $leaky-get-or-insert-slice:not-found:
 707     # result/EAX = 0
 708     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 709     # if (table->write >= table->length) abort
 710     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
 711     3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # compare ECX with *(ESI+8)
 712     7d/jump-if-greater-or-equal  $leaky-get-or-insert-slice:abort/disp8
 713     # zero-out(max, row-size)
 714     # . . push args
 715     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
 716     52/push-EDX
 717     # . . call
 718     e8/call  zero-out/disp32
 719     # . . discard args
 720     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 721     # *max = slice-to-string(Heap, key)
 722     # . EAX = slice-to-string(Heap, key)
 723     # . . push args
 724     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 725     68/push  Heap/imm32
 726     # . . call
 727     e8/call  slice-to-string/disp32
 728     # . . discard args
 729     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 730     # . *max = EAX
 731     89/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDX
 732     # table->write += row-size
 733     # . EAX = row-size
 734     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0x10/disp8      .                 # copy *(EBP+16) to EAX
 735     # . table->write += EAX
 736     01/add                          0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # add EAX to *ESI
 737     # return max+4
 738     # . EAX = max
 739     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy EDX to EAX
 740     # . EAX += 4
 741     05/add-to-EAX  4/imm32
 742 $leaky-get-or-insert-slice:end:
 743     # . restore registers
 744     5e/pop-to-ESI
 745     5a/pop-to-EDX
 746     59/pop-to-ECX
 747     # . epilog
 748     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 749     5d/pop-to-EBP
 750     c3/return
 751 
 752 $leaky-get-or-insert-slice:abort:
 753     # . _write(2/stderr, error)
 754     # . . push args
 755     68/push  "leaky-get-or-insert-slice: table is full\n"/imm32
 756     68/push  2/imm32/stderr
 757     # . . call
 758     e8/call  _write/disp32
 759     # . . discard args
 760     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 761     # . syscall(exit, 1)
 762     bb/copy-to-EBX  1/imm32
 763     b8/copy-to-EAX  1/imm32/exit
 764     cd/syscall  0x80/imm8
 765     # never gets here
 766 
 767 test-leaky-get-or-insert-slice:
 768     # . prolog
 769     55/push-EBP
 770     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 771     # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
 772     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
 773     68/push  0x10/imm32/length
 774     68/push  0/imm32/read
 775     68/push  0/imm32/write
 776     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 777     # (EAX..EDX) = "code"
 778     b8/copy-to-EAX  "code"/imm32
 779     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
 780     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
 781     05/add-to-EAX  4/imm32
 782     # var slice/EDX = {EAX, EDX}
 783     52/push-EDX
 784     50/push-EAX
 785     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
 786 $test-leaky-get-or-insert-slice:first-call:
 787     # - start with an empty table, insert one key, verify that it was inserted
 788     # EAX = leaky-get-or-insert-slice(table, "code" slice, 8 bytes per row)
 789     # . . push args
 790     68/push  8/imm32/row-size
 791     52/push-EDX
 792     51/push-ECX
 793     # . . call
 794     e8/call  leaky-get-or-insert-slice/disp32
 795     # . . discard args
 796     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 797     # check-ints-equal(EAX - table->data, 4, msg)  # first row's value slot returned
 798     # . check-ints-equal(EAX - table, 16, msg)
 799     # . . push args
 800     68/push  "F - test-leaky-get-or-insert-slice/0"/imm32
 801     68/push  0x10/imm32
 802     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 803     50/push-EAX
 804     # . . call
 805     e8/call  check-ints-equal/disp32
 806     # . . discard args
 807     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 808 $test-leaky-get-or-insert-slice:check2:
 809     # check-ints-equal(table->write, row-size = 8, msg)
 810     # . . push args
 811     68/push  "F - test-leaky-get-or-insert-slice/1"/imm32
 812     68/push  8/imm32/row-size
 813     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 814     # . . call
 815     e8/call  check-ints-equal/disp32
 816     # . . discard args
 817     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 818     # check-string-equal(*table->data, "code", msg)
 819     # . . push args
 820     68/push  "F - test-leaky-get-or-insert-slice/2"/imm32
 821     68/push  "code"/imm32
 822     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
 823     # . . call
 824     e8/call  check-string-equal/disp32
 825     # . . discard args
 826     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 827 $test-leaky-get-or-insert-slice:second-call:
 828     # - insert the same key again, verify that it was reused
 829     # EAX = leaky-get-or-insert-slice(table, "code" slice, 8 bytes per row)
 830     # . . push args
 831     68/push  8/imm32/row-size
 832     52/push-EDX
 833     51/push-ECX
 834     # . . call
 835     e8/call  leaky-get-or-insert-slice/disp32
 836     # . . discard args
 837     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 838     # check-ints-equal(EAX - table->data, 4, msg)
 839     # . check-ints-equal(EAX - table, 16, msg)
 840     # . . push args
 841     68/push  "F - test-leaky-get-or-insert-slice/3"/imm32
 842     68/push  0x10/imm32
 843     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 844     50/push-EAX
 845     # . . call
 846     e8/call  check-ints-equal/disp32
 847     # . . discard args
 848     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 849     # no new row inserted
 850     # . check-ints-equal(table->write, row-size = 8, msg)
 851     # . . push args
 852     68/push  "F - test-leaky-get-or-insert-slice/4"/imm32
 853     68/push  8/imm32/row-size
 854     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 855     # . . call
 856     e8/call  check-ints-equal/disp32
 857     # . . discard args
 858     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 859     # check-string-equal(*table->data, "code", msg)
 860     # . . push args
 861     68/push  "F - test-leaky-get-or-insert-slice/5"/imm32
 862     68/push  "code"/imm32
 863     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
 864     # . . call
 865     e8/call  check-string-equal/disp32
 866     # . . discard args
 867     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 868 $test-leaky-get-or-insert-slice:third-call:
 869     # - insert a new key, verify that it was inserted
 870     # (EAX..EDX) = "data"
 871     b8/copy-to-EAX  "data"/imm32
 872     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
 873     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
 874     05/add-to-EAX  4/imm32
 875     # var slice/EDX = {EAX, EDX}
 876     52/push-EDX
 877     50/push-EAX
 878     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
 879     # EAX = leaky-get-or-insert-slice(table, "data" slice, 8 bytes per row)
 880     # . . push args
 881     68/push  8/imm32/row-size
 882     52/push-EDX
 883     51/push-ECX
 884     # . . call
 885     e8/call  leaky-get-or-insert-slice/disp32
 886     # . . discard args
 887     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 888     # table gets a new row
 889     # check-ints-equal(EAX - table->data, 12, msg)  # second row's value slot returned
 890     # . check-ints-equal(EAX - table, 24, msg)
 891     # . . push args
 892     68/push  "F - test-leaky-get-or-insert-slice/6"/imm32
 893     68/push  0x18/imm32
 894     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
 895     50/push-EAX
 896     # . . call
 897     e8/call  check-ints-equal/disp32
 898     # . . discard args
 899     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 900     # check-ints-equal(table->write, 2 rows = 16, msg)
 901     # . . push args
 902     68/push  "F - test-leaky-get-or-insert-slice/7"/imm32
 903     68/push  0x10/imm32/two-rows
 904     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 905     # . . call
 906     e8/call  check-ints-equal/disp32
 907     # . . discard args
 908     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 909     # check-string-equal(*table->data+8, "data", msg)
 910     # check-string-equal(*(table+20), "data", msg)
 911     # . . push args
 912     68/push  "F - test-leaky-get-or-insert-slice/8"/imm32
 913     68/push  "data"/imm32
 914     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0x14/disp8      .                 # push *(ECX+20)
 915     # . . call
 916     e8/call  check-string-equal/disp32
 917     # . . discard args
 918     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 919 $test-leaky-get-or-insert-slice:end:
 920     # . epilog
 921     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 922     5d/pop-to-EBP
 923     c3/return
 924 
 925 # if no row is found, stop(ed)
 926 get-or-stop:  # table : (address stream {string, _}), key : (address string), row-size : int,
 927               # abort-message-prefix : (address string), err : (address buffered-file), ed : (address exit-descriptor)
 928               # -> EAX : (address _)
 929     # pseudocode:
 930     #   curr = table->data
 931     #   max = &table->data[table->write]
 932     #   while curr < max
 933     #     if string-equal?(key, *curr)
 934     #       return curr+4
 935     #     curr += row-size
 936     #   write-buffered(err, msg)
 937     #   stop(ed)
 938     #
 939     # . prolog
 940     55/push-EBP
 941     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 942     # . save registers
 943     51/push-ECX
 944     52/push-EDX
 945     56/push-ESI
 946     # ESI = table
 947     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 948     # curr/ECX = table->data
 949     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
 950     # max/EDX = table->data + table->write
 951     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
 952     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
 953 $get-or-stop:search-loop:
 954     # if (curr >= max) stop(ed)
 955     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
 956     73/jump-if-greater-or-equal-unsigned  $get-or-stop:stop/disp8
 957     # if (string-equal?(key, *curr)) return curr+4
 958     # . EAX = string-equal?(key, *curr)
 959     # . . push args
 960     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
 961     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 962     # . . call
 963     e8/call  string-equal?/disp32
 964     # . . discard args
 965     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 966     # . if (EAX != 0) return EAX = curr+4
 967     3d/compare-EAX-and  0/imm32
 968     74/jump-if-equal  $get-or-stop:mismatch/disp8
 969     8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
 970     eb/jump  $get-or-stop:end/disp8
 971 $get-or-stop:mismatch:
 972     # curr += row-size
 973     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
 974     # loop
 975     eb/jump  $get-or-stop:search-loop/disp8
 976 $get-or-stop:end:
 977     # . restore registers
 978     5e/pop-to-ESI
 979     5a/pop-to-EDX
 980     59/pop-to-ECX
 981     # . epilog
 982     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 983     5d/pop-to-EBP
 984     c3/return
 985 
 986 $get-or-stop:stop:
 987     # . write-buffered(err, abort-message-prefix)
 988     # . . push args
 989     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
 990     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
 991     # . . call
 992     e8/call  write-buffered/disp32
 993     # . . discard args
 994     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 995     # . write-buffered(err, error)
 996     # . . push args
 997     68/push  ": get-or-stop: key not found: "/imm32
 998     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
 999     # . . call
1000     e8/call  write-buffered/disp32
1001     # . . discard args
1002     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1003     # . write-buffered(err, key)
1004     # . . push args
1005     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
1006     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
1007     # . . call
1008     e8/call  write-buffered/disp32
1009     # . . discard args
1010     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1011     # . write-buffered(err, "\n")
1012     # . . push args
1013     68/push  "\n"/imm32
1014     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
1015     # . . call
1016     e8/call  write-buffered/disp32
1017     # . . discard args
1018     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1019     # . stop(ed, 1)
1020     # . . push args
1021     68/push  1/imm32
1022     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x1c/disp8      .                 # push *(EBP+28)
1023     # . . call
1024     e8/call  stop/disp32
1025     # never gets here
1026 $get-or-stop:terminus:
1027     # . . discard args
1028     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1029     # syscall(exit, 1)
1030     b8/copy-to-EAX  1/imm32/exit
1031     cd/syscall  0x80/imm8
1032 
1033 test-get-or-stop:
1034     # This test uses exit-descriptors. Use EBP for setting up local variables.
1035     # . prolog
1036     55/push-EBP
1037     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1038     # setup
1039     # . clear-stream(_test-error-stream)
1040     # . . push args
1041     68/push  _test-error-stream/imm32
1042     # . . call
1043     e8/call  clear-stream/disp32
1044     # . . discard args
1045     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1046     # . clear-stream(_test-error-buffered-file+4)
1047     # . . push args
1048     b8/copy-to-EAX  _test-error-buffered-file/imm32
1049     05/add-to-EAX  4/imm32
1050     50/push-EAX
1051     # . . call
1052     e8/call  clear-stream/disp32
1053     # . . discard args
1054     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1055     # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
1056     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
1057     68/push  0x10/imm32/length
1058     68/push  0/imm32/read
1059     68/push  0/imm32/write
1060     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1061     # var ed/EDX : (address exit-descriptor)
1062     68/push  0/imm32
1063     68/push  0/imm32
1064     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
1065     # size 'ed' for the calls to 'get-or-stop'
1066     # . tailor-exit-descriptor(ed, 24)
1067     # . . push args
1068     68/push  0x18/imm32/nbytes-of-args-for-get-or-stop
1069     52/push-EDX
1070     # . . call
1071     e8/call  tailor-exit-descriptor/disp32
1072     # . . discard args
1073     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1074     # insert(table, "code", 8 bytes per row)
1075     # . . push args
1076     68/push  8/imm32/row-size
1077     68/push  "code"/imm32
1078     51/push-ECX
1079     # . . call
1080     e8/call  get-or-insert/disp32
1081     # . . discard args
1082     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1083 $test-get-or-stop:success:
1084     # EAX = get-or-stop(table, "code", row-size=8, msg, _test-error-buffered-file, ed)
1085     # . . push args
1086     52/push-EDX/ed
1087     68/push  _test-error-buffered-file/imm32
1088     68/push  "foo"/imm32/abort-prefix
1089     68/push  8/imm32/row-size
1090     68/push  "code"/imm32
1091     51/push-ECX
1092     # . . call
1093     e8/call  get-or-stop/disp32
1094     # . . discard args
1095     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x18/imm32        # add to ESP
1096 $test-get-or-stop:success-assertion:
1097     # check-ints-equal(EAX - table->data, 4, msg)
1098     # . check-ints-equal(EAX - table, 16, msg)
1099     # . . push args
1100     68/push  "F - test-get-or-stop/0"/imm32
1101     68/push  0x10/imm32
1102     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
1103     50/push-EAX
1104     # . . call
1105     e8/call  check-ints-equal/disp32
1106     # . . discard args
1107     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1108 $test-get-or-stop:failure:
1109     # EAX = get-or-stop(table, "data", row-size=8, msg, _test-error-buffered-file, ed)
1110     # . . push args
1111     52/push-EDX/ed
1112     68/push  _test-error-buffered-file/imm32
1113     68/push  "foo"/imm32/abort-prefix
1114     68/push  8/imm32/row-size
1115     68/push  "data"/imm32
1116     51/push-ECX
1117     # . . call
1118     e8/call  get-or-stop/disp32
1119     # registers except ESP may be clobbered at this point
1120     # restore register args, discard others
1121     59/pop-to-ECX
1122     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
1123     5a/pop-to-EDX
1124 $test-get-or-stop:failure-assertion:
1125     # check that get-or-stop tried to call stop(1)
1126     # . check-ints-equal(ed->value, 2, msg)
1127     # . . push args
1128     68/push  "F - test-get-or-stop/1"/imm32
1129     68/push  2/imm32
1130     # . . push ed->value
1131     ff          6/subop/push        1/mod/*+disp8   2/rm32/EDX    .           .             .           .           4/disp8         .                 # push *(EDX+4)
1132     # . . call
1133     e8/call  check-ints-equal/disp32
1134     # . . discard args
1135     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1136 $test-get-or-stop:end:
1137     # . epilog
1138     # don't restore ESP from EBP; manually reclaim locals
1139     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x24/imm32        # add to ESP
1140     5d/pop-to-EBP
1141     c3/return
1142 
1143 # if no row is found, stop(ed)
1144 get-slice-or-stop:  # table : (address stream {string, _}), key : (address slice), row-size : int,
1145                     # abort-message-prefix : (address string), err : (address buffered-file), ed : (address exit-descriptor)
1146                     # -> EAX : (address _)
1147     # pseudocode:
1148     #   curr = table->data
1149     #   max = &table->data[table->write]
1150     #   while curr < max
1151     #     if slice-equal?(key, *curr)
1152     #       return curr+4
1153     #     curr += row-size
1154     #   write-buffered(err, msg)
1155     #   stop(ed)
1156     #
1157     # . prolog
1158     55/push-EBP
1159     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1160     # . save registers
1161     51/push-ECX
1162     52/push-EDX
1163     56/push-ESI
1164     # ESI = table
1165     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
1166     # curr/ECX = table->data
1167     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
1168     # max/EDX = table->data + table->write
1169     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
1170     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
1171 $get-slice-or-stop:search-loop:
1172     # if (curr >= max) stop(ed)
1173     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
1174     73/jump-if-greater-or-equal-unsigned  $get-slice-or-stop:stop/disp8
1175     # if (slice-equal?(key, *curr)) return curr+4
1176     # . EAX = slice-equal?(key, *curr)
1177     # . . push args
1178     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
1179     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
1180     # . . call
1181     e8/call  slice-equal?/disp32
1182     # . . discard args
1183     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1184     # . if (EAX != 0) return EAX = curr+4
1185     3d/compare-EAX-and  0/imm32
1186     74/jump-if-equal  $get-slice-or-stop:mismatch/disp8
1187     8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
1188     eb/jump  $get-slice-or-stop:end/disp8
1189 $get-slice-or-stop:mismatch:
1190     # curr += row-size
1191     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
1192     # loop
1193     eb/jump  $get-slice-or-stop:search-loop/disp8
1194 $get-slice-or-stop:end:
1195     # . restore registers
1196     5e/pop-to-ESI
1197     5a/pop-to-EDX
1198     59/pop-to-ECX
1199     # . epilog
1200     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1201     5d/pop-to-EBP
1202     c3/return
1203 
1204 $get-slice-or-stop:stop:
1205     # . write-buffered(err, abort-message-prefix)
1206     # . . push args
1207     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
1208     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
1209     # . . call
1210     e8/call  write-buffered/disp32
1211     # . . discard args
1212     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1213     # . write-buffered(err, error)
1214     # . . push args
1215     68/push  ": get-slice-or-stop: key not found: "/imm32
1216     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
1217     # . . call
1218     e8/call  write-buffered/disp32
1219     # . . discard args
1220     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1221     # . write-slice-buffered(err, key)
1222     # . . push args
1223     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
1224     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
1225     # . . call
1226     e8/call  write-slice-buffered/disp32
1227     # . . discard args
1228     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1229     # . write-buffered(err, "\n")
1230     # . . push args
1231     68/push  "\n"/imm32
1232     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
1233     # . . call
1234     e8/call  write-buffered/disp32
1235     # . . discard args
1236     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1237     # . stop(ed, 1)
1238     # . . push args
1239     68/push  1/imm32
1240     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x1c/disp8      .                 # push *(EBP+28)
1241     # . . call
1242     e8/call  stop/disp32
1243     # never gets here
1244 $get-slice-or-stop:terminus:
1245     # . . discard args
1246     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1247     # syscall(exit, 1)
1248     b8/copy-to-EAX  1/imm32/exit
1249     cd/syscall  0x80/imm8
1250 
1251 test-get-slice-or-stop:
1252     # This test uses exit-descriptors. Use EBP for setting up local variables.
1253     # . prolog
1254     55/push-EBP
1255     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1256     # setup
1257     # . clear-stream(_test-error-stream)
1258     # . . push args
1259     68/push  _test-error-stream/imm32
1260     # . . call
1261     e8/call  clear-stream/disp32
1262     # . . discard args
1263     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1264     # . clear-stream(_test-error-buffered-file+4)
1265     # . . push args
1266     b8/copy-to-EAX  _test-error-buffered-file/imm32
1267     05/add-to-EAX  4/imm32
1268     50/push-EAX
1269     # . . call
1270     e8/call  clear-stream/disp32
1271     # . . discard args
1272     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1273     # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
1274     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
1275     68/push  0x10/imm32/length
1276     68/push  0/imm32/read
1277     68/push  0/imm32/write
1278     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1279     # var ed/EDX : (address exit-descriptor)
1280     68/push  0/imm32
1281     68/push  0/imm32
1282     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
1283     # var slice/EBX = "code"
1284     # . (EAX..EBX) = "code"
1285     b8/copy-to-EAX  "code"/imm32
1286     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           3/r32/EBX   .               .                 # copy *EAX to EBX
1287     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  3/index/EBX   .           3/r32/EBX   4/disp8         .                 # copy EAX+EBX+4 to EBX
1288     05/add-to-EAX  4/imm32
1289     # . EBX = {EAX, EBX}
1290     53/push-EBX
1291     50/push-EAX
1292     89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
1293     # size 'ed' for the calls to 'get-or-stop' (define no locals past this point)
1294     # . tailor-exit-descriptor(ed, 24)
1295     # . . push args
1296     68/push  0x18/imm32/nbytes-of-args-for-get-or-stop
1297     52/push-EDX
1298     # . . call
1299     e8/call  tailor-exit-descriptor/disp32
1300     # . . discard args
1301     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1302     # insert(table, "code", 8 bytes per row)
1303     # . . push args
1304     68/push  8/imm32/row-size
1305     68/push  "code"/imm32
1306     51/push-ECX
1307     # . . call
1308     e8/call  get-or-insert/disp32
1309     # . . discard args
1310     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1311 $test-get-slice-or-stop:success:
1312     # EAX = get-slice-or-stop(table, slice, row-size=8, msg, _test-error-buffered-file, ed)
1313     # . . push args
1314     52/push-EDX/ed
1315     68/push  _test-error-buffered-file/imm32
1316     68/push  "foo"/imm32/abort-prefix
1317     68/push  8/imm32/row-size
1318     53/push-EBX/slice
1319     51/push-ECX
1320     # . . call
1321     e8/call  get-slice-or-stop/disp32
1322     # registers except ESP may be clobbered at this point
1323     # restore register args, discard others
1324     59/pop-to-ECX
1325     5b/pop-to-EBX
1326     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1327     5a/pop-to-EDX
1328 $test-get-slice-or-stop:success-assertion:
1329     # check-ints-equal(EAX - table->data, 4, msg)  # first row's value slot returned
1330     # . check-ints-equal(EAX - table, 16, msg)
1331     # . . push args
1332     68/push  "F - test-get-slice-or-stop/0"/imm32
1333     68/push  0x10/imm32
1334     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
1335     50/push-EAX
1336     # . . call
1337     e8/call  check-ints-equal/disp32
1338     # . . discard args
1339     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1340 $test-get-slice-or-stop:failure:
1341     # slice = "segment2"
1342     # . *EBX = "segment2"->data
1343     b8/copy-to-EAX  "segment2"/imm32
1344     05/add-to-EAX  4/imm32
1345     89/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EBX
1346     # . *(EBX+4) = "segment2"->data + len("segment2")
1347     05/add-to-EAX  8/imm32/strlen
1348     89/copy                         1/mod/*+disp8   3/rm32/EBX    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EBX+4)
1349     # EAX = get-slice-or-stop(table, slice, row-size=8, msg, _test-error-buffered-file, ed)
1350     # . . push args
1351     52/push-EDX/ed
1352     68/push  _test-error-buffered-file/imm32
1353     68/push  "foo"/imm32/abort-prefix
1354     68/push  8/imm32/row-size
1355     53/push-EBX/slice
1356     51/push-ECX
1357     # . . call
1358     e8/call  get-slice-or-stop/disp32
1359     # registers except ESP may be clobbered at this point
1360     # restore register args, discard others
1361     59/pop-to-ECX
1362     5b/pop-to-EBX
1363     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1364     5a/pop-to-EDX
1365 $test-get-slice-or-stop:failure-assertion:
1366     # check that get-or-stop tried to call stop(1)
1367     # . check-ints-equal(ed->value, 2, msg)
1368     # . . push args
1369     68/push  "F - test-get-or-stop/1"/imm32
1370     68/push  2/imm32
1371     # . . push ed->value
1372     ff          6/subop/push        1/mod/*+disp8   2/rm32/EDX    .           .             .           .           4/disp8         .                 # push *(EDX+4)
1373     # . . call
1374     e8/call  check-ints-equal/disp32
1375     # . . discard args
1376     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1377 $test-get-slice-or-stop:end:
1378     # . epilog
1379     # don't restore ESP from EBP; manually reclaim locals
1380     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x2c/imm32        # add to ESP
1381     5d/pop-to-EBP
1382     c3/return
1383 
1384 # if no row is found, return null (0)
1385 maybe-get:  # table : (address stream {string, _}), key : (address string), row-size : int -> EAX : (address _)
1386     # pseudocode:
1387     #   curr = table->data
1388     #   max = &table->data[table->write]
1389     #   while curr < max
1390     #     if string-equal?(key, *curr)
1391     #       return curr+4
1392     #     curr += row-size
1393     #   return 0
1394     #
1395     # . prolog
1396     55/push-EBP
1397     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1398     # . save registers
1399     51/push-ECX
1400     52/push-EDX
1401     56/push-ESI
1402     # ESI = table
1403     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
1404     # curr/ECX = table->data
1405     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
1406     # max/EDX = table->data + table->write
1407     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
1408     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
1409 $maybe-get:search-loop:
1410     # if (curr >= max) return null
1411     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
1412     73/jump-if-greater-or-equal-unsigned  $maybe-get:null/disp8
1413     # if (string-equal?(key, *curr)) return curr+4
1414     # . EAX = string-equal?(key, *curr)
1415     # . . push args
1416     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
1417     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
1418     # . . call
1419     e8/call  string-equal?/disp32
1420     # . . discard args
1421     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1422     # . if (EAX != 0) return EAX = curr+4
1423     3d/compare-EAX-and  0/imm32
1424     74/jump-if-equal  $maybe-get:mismatch/disp8
1425     8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
1426     eb/jump  $maybe-get:end/disp8
1427 $maybe-get:mismatch:
1428     # curr += row-size
1429     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
1430     # loop
1431     eb/jump  $maybe-get:search-loop/disp8
1432 $maybe-get:null:
1433     b8/copy-to-EAX  0/imm32
1434 $maybe-get:end:
1435     # . restore registers
1436     5e/pop-to-ESI
1437     5a/pop-to-EDX
1438     59/pop-to-ECX
1439     # . epilog
1440     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1441     5d/pop-to-EBP
1442     c3/return
1443 
1444 test-maybe-get:
1445     # . prolog
1446     55/push-EBP
1447     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1448     # - setup: create a table with one row
1449     # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
1450     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
1451     68/push  0x10/imm32/length
1452     68/push  0/imm32/read
1453     68/push  0/imm32/write
1454     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1455     # EAX = get-or-insert(table, "code", 8 bytes per row)
1456     # . . push args
1457     68/push  8/imm32/row-size
1458     68/push  "code"/imm32
1459     51/push-ECX
1460     # . . call
1461     e8/call  get-or-insert/disp32
1462     # . . discard args
1463     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1464 $test-maybe-get:success:
1465     # - check for the same key, verify that it was reused
1466     # EAX = maybe-get(table, "code", 8 bytes per row)
1467     # . . push args
1468     68/push  8/imm32/row-size
1469     68/push  "code"/imm32
1470     51/push-ECX
1471     # . . call
1472     e8/call  maybe-get/disp32
1473     # . . discard args
1474     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1475     # check-ints-equal(EAX - table->data, 4, msg)
1476     # . check-ints-equal(EAX - table, 16, msg)
1477     # . . push args
1478     68/push  "F - test-maybe-get/0"/imm32
1479     68/push  0x10/imm32
1480     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
1481     50/push-EAX
1482     # . . call
1483     e8/call  check-ints-equal/disp32
1484     # . . discard args
1485     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1486     # no new row inserted
1487     # . check-ints-equal(table->write, row-size = 8, msg)
1488     # . . push args
1489     68/push  "F - test-maybe-get/1"/imm32
1490     68/push  8/imm32/row-size
1491     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
1492     # . . call
1493     e8/call  check-ints-equal/disp32
1494     # . . discard args
1495     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1496     # check-string-equal(*table->data, "code", msg)
1497     # . . push args
1498     68/push  "F - test-maybe-get/2"/imm32
1499     68/push  "code"/imm32
1500     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
1501     # . . call
1502     e8/call  check-string-equal/disp32
1503     # . . discard args
1504     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1505 $test-maybe-get:failure:
1506     # - search for a new key
1507     # EAX = maybe-get(table, "data", 8 bytes per row)
1508     # . . push args
1509     68/push  8/imm32/row-size
1510     68/push  "data"/imm32
1511     51/push-ECX
1512     # . . call
1513     e8/call  maybe-get/disp32
1514     # . . discard args
1515     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1516     # check-ints-equal(EAX, 0, msg)
1517     # . . push args
1518     68/push  "F - test-maybe-get/3"/imm32
1519     68/push  0/imm32
1520     50/push-EAX
1521     # . . call
1522     e8/call  check-ints-equal/disp32
1523     # . . discard args
1524     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1525 $test-maybe-get:end:
1526     # . epilog
1527     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1528     5d/pop-to-EBP
1529     c3/return
1530 
1531 # if no row is found, return null (0)
1532 maybe-get-slice:  # table : (address stream {string, _}), key : (address slice), row-size : int -> EAX : (address _)
1533     # pseudocode:
1534     #   curr = table->data
1535     #   max = &table->data[table->write]
1536     #   while curr < max
1537     #     if slice-equal?(key, *curr)
1538     #       return curr+4
1539     #     curr += row-size
1540     #   return 0
1541     #
1542     # . prolog
1543     55/push-EBP
1544     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1545     # . save registers
1546     51/push-ECX
1547     52/push-EDX
1548     56/push-ESI
1549     # ESI = table
1550     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
1551     # curr/ECX = table->data
1552     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
1553     # max/EDX = table->data + table->write
1554     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
1555     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
1556 $maybe-get-slice:search-loop:
1557     # if (curr >= max) return null
1558     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
1559     73/jump-if-greater-or-equal-unsigned  $maybe-get-slice:null/disp8
1560     # if (slice-equal?(key, *curr)) return curr+4
1561     # . EAX = slice-equal?(key, *curr)
1562     # . . push args
1563     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
1564     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
1565     # . . call
1566     e8/call  slice-equal?/disp32
1567     # . . discard args
1568     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1569     # . if (EAX != 0) return EAX = curr+4
1570     3d/compare-EAX-and  0/imm32
1571     74/jump-if-equal  $maybe-get-slice:mismatch/disp8
1572     8d/copy-address                 1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy ECX+4 to EAX
1573     eb/jump  $maybe-get-slice:end/disp8
1574 $maybe-get-slice:mismatch:
1575     # curr += row-size
1576     03/add                          1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0x10/disp8      .                 # add *(EBP+16) to ECX
1577     # loop
1578     eb/jump  $maybe-get-slice:search-loop/disp8
1579 $maybe-get-slice:null:
1580     b8/copy-to-EAX  0/imm32
1581 $maybe-get-slice:end:
1582     # . restore registers
1583     5e/pop-to-ESI
1584     5a/pop-to-EDX
1585     59/pop-to-ECX
1586     # . epilog
1587     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1588     5d/pop-to-EBP
1589     c3/return
1590 
1591 test-maybe-get-slice:
1592     # . prolog
1593     55/push-EBP
1594     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1595     # - setup: create a table with one row
1596     # var table/ECX : (address stream {string, number}) = stream(2 rows * 8 bytes)
1597     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
1598     68/push  0x10/imm32/length
1599     68/push  0/imm32/read
1600     68/push  0/imm32/write
1601     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1602     # insert(table, "code", 8 bytes per row)
1603     # . . push args
1604     68/push  8/imm32/row-size
1605     68/push  "code"/imm32
1606     51/push-ECX
1607     # . . call
1608     e8/call  get-or-insert/disp32
1609     # . . discard args
1610     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1611 $test-maybe-get-slice:success:
1612     # - check for the same key, verify that it was reused
1613     # (EAX..EDX) = "code"
1614     b8/copy-to-EAX  "code"/imm32
1615     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
1616     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
1617     05/add-to-EAX  4/imm32
1618     # var slice/EDX = {EAX, EDX}
1619     52/push-EDX
1620     50/push-EAX
1621     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
1622     # EAX = maybe-get-slice(table, "code" slice, 8 bytes per row)
1623     # . . push args
1624     68/push  8/imm32/row-size
1625     52/push-EDX
1626     51/push-ECX
1627     # . . call
1628     e8/call  maybe-get-slice/disp32
1629     # . . discard args
1630     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1631     # check-ints-equal(EAX - table->data, 4, msg)
1632     # . check-ints-equal(EAX - table, 16, msg)
1633     # . . push args
1634     68/push  "F - test-maybe-get-slice/0"/imm32
1635     68/push  0x10/imm32
1636     29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
1637     50/push-EAX
1638     # . . call
1639     e8/call  check-ints-equal/disp32
1640     # . . discard args
1641     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1642     # no new row inserted
1643     # . check-ints-equal(table->write, row-size = 8, msg)
1644     # . . push args
1645     68/push  "F - test-maybe-get-slice/1"/imm32
1646     68/push  8/imm32/row-size
1647     ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
1648     # . . call
1649     e8/call  check-ints-equal/disp32
1650     # . . discard args
1651     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1652     # check-string-equal(*table->data, "code", msg)
1653     # . . push args
1654     68/push  "F - test-maybe-get-slice/2"/imm32
1655     68/push  "code"/imm32
1656     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
1657     # . . call
1658     e8/call  check-string-equal/disp32
1659     # . . discard args
1660     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1661 $test-maybe-get-slice:failure:
1662     # - search for a new key
1663     # (EAX..EDX) = "data"
1664     b8/copy-to-EAX  "data"/imm32
1665     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           2/r32/EDX   .               .                 # copy *EAX to EDX
1666     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
1667     05/add-to-EAX  4/imm32
1668     # var slice/EDX = {EAX, EDX}
1669     52/push-EDX
1670     50/push-EAX
1671     89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
1672     # EAX = maybe-get-slice(table, "data" slice, 8 bytes per row)
1673     # . . push args
1674     68/push  8/imm32/row-size
1675     52/push-EDX
1676     51/push-ECX
1677     # . . call
1678     e8/call  maybe-get-slice/disp32
1679     # . . discard args
1680     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1681     # check-ints-equal(EAX, 0, msg)
1682     # . . push args
1683     68/push  "F - test-maybe-get-slice/3"/imm32
1684     68/push  0/imm32
1685     50/push-EAX
1686     # . . call
1687     e8/call  check-ints-equal/disp32
1688     # . . discard args
1689     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1690 $test-maybe-get-slice:end:
1691     # . epilog
1692     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1693     5d/pop-to-EBP
1694     c3/return
1695 
1696 # . . vim:nowrap:textwidth=0