https://github.com/akkartik/mu/blob/master/081table.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     # . prologue
  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     # . epilogue
  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  Newline/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     # . prologue
 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     # . epilogue
 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     # . prologue
 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     # . epilogue
 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  Newline/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     # . prologue
 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     # . epilogue
 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     # . prologue
 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     # . epilogue
 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     # . prologue
 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     # . epilogue
 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     # . prologue
 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     # . epilogue
 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     # . prologue
 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     # . epilogue
 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     # . prologue
 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     # . epilogue
 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  Newline/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     # . prologue
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->buffer)
1047     # . . push args
1048     68/push  _test-error-buffered-file->buffer/imm32
1049     # . . call
1050     e8/call  clear-stream/disp32
1051     # . . discard args
1052     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1053     # var table/ecx : (address stream {string, number}) = stream(2 rows * 8 bytes)
1054     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # subtract from esp
1055     68/push  0x10/imm32/length
1056     68/push  0/imm32/read
1057     68/push  0/imm32/write
1058     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1059     # var ed/edx : (address exit-descriptor)
1060     68/push  0/imm32
1061     68/push  0/imm32
1062     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
1063     # size 'ed' for the calls to 'get-or-stop'
1064     # . tailor-exit-descriptor(ed, 24)
1065     # . . push args
1066     68/push  0x18/imm32/nbytes-of-args-for-get-or-stop
1067     52/push-edx
1068     # . . call
1069     e8/call  tailor-exit-descriptor/disp32
1070     # . . discard args
1071     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1072     # insert(table, "code", 8 bytes per row)
1073     # . . push args
1074     68/push  8/imm32/row-size
1075     68/push  "code"/imm32
1076     51/push-ecx
1077     # . . call
1078     e8/call  get-or-insert/disp32
1079     # . . discard args
1080     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1081 $test-get-or-stop:success:
1082     # eax = get-or-stop(table, "code", row-size=8, msg, _test-error-buffered-file, ed)
1083     # . . push args
1084     52/push-edx/ed
1085     68/push  _test-error-buffered-file/imm32
1086     68/push  "foo"/imm32/abort-prefix
1087     68/push  8/imm32/row-size
1088     68/push  "code"/imm32
1089     51/push-ecx
1090     # . . call
1091     e8/call  get-or-stop/disp32
1092     # . . discard args
1093     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # add to esp
1094 $test-get-or-stop:success-assertion:
1095     # check-ints-equal(eax - table->data, 4, msg)
1096     # . check-ints-equal(eax - table, 16, msg)
1097     # . . push args
1098     68/push  "F - test-get-or-stop/0"/imm32
1099     68/push  0x10/imm32
1100     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1101     50/push-eax
1102     # . . call
1103     e8/call  check-ints-equal/disp32
1104     # . . discard args
1105     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1106 $test-get-or-stop:failure:
1107     # eax = get-or-stop(table, "data", row-size=8, msg, _test-error-buffered-file, ed)
1108     # . . push args
1109     52/push-edx/ed
1110     68/push  _test-error-buffered-file/imm32
1111     68/push  "foo"/imm32/abort-prefix
1112     68/push  8/imm32/row-size
1113     68/push  "data"/imm32
1114     51/push-ecx
1115     # . . call
1116     e8/call  get-or-stop/disp32
1117     # registers except esp may be clobbered at this point
1118     # restore register args, discard others
1119     59/pop-to-ecx
1120     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1121     5a/pop-to-edx
1122 $test-get-or-stop:failure-assertion:
1123     # check that get-or-stop tried to call stop(1)
1124     # . check-ints-equal(ed->value, 2, msg)
1125     # . . push args
1126     68/push  "F - test-get-or-stop/1"/imm32
1127     68/push  2/imm32
1128     # . . push ed->value
1129     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
1130     # . . call
1131     e8/call  check-ints-equal/disp32
1132     # . . discard args
1133     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1134 $test-get-or-stop:end:
1135     # . epilogue
1136     # don't restore esp from ebp; manually reclaim locals
1137     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x24/imm32        # add to esp
1138     5d/pop-to-ebp
1139     c3/return
1140 
1141 # if no row is found, stop(ed)
1142 get-slice-or-stop:  # table : (address stream {string, _}), key : (address slice), row-size : int,
1143                     # abort-message-prefix : (address string), err : (address buffered-file), ed : (address exit-descriptor)
1144                     # -> eax : (address _)
1145     # pseudocode:
1146     #   curr = table->data
1147     #   max = &table->data[table->write]
1148     #   while curr < max
1149     #     if slice-equal?(key, *curr)
1150     #       return curr+4
1151     #     curr += row-size
1152     #   write-buffered(err, msg)
1153     #   stop(ed)
1154     #
1155     # . prologue
1156     55/push-ebp
1157     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1158     # . save registers
1159     51/push-ecx
1160     52/push-edx
1161     56/push-esi
1162     # esi = table
1163     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
1164     # curr/ecx = table->data
1165     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
1166     # max/edx = table->data + table->write
1167     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
1168     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
1169 $get-slice-or-stop:search-loop:
1170     # if (curr >= max) stop(ed)
1171     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1172     73/jump-if-greater-or-equal-unsigned  $get-slice-or-stop:stop/disp8
1173     # if (slice-equal?(key, *curr)) return curr+4
1174     # . eax = slice-equal?(key, *curr)
1175     # . . push args
1176     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1177     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1178     # . . call
1179     e8/call  slice-equal?/disp32
1180     # . . discard args
1181     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1182     # . if (eax != 0) return eax = curr+4
1183     3d/compare-eax-and  0/imm32
1184     74/jump-if-equal  $get-slice-or-stop:mismatch/disp8
1185     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy ecx+4 to eax
1186     eb/jump  $get-slice-or-stop:end/disp8
1187 $get-slice-or-stop:mismatch:
1188     # curr += row-size
1189     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
1190     # loop
1191     eb/jump  $get-slice-or-stop:search-loop/disp8
1192 $get-slice-or-stop:end:
1193     # . restore registers
1194     5e/pop-to-esi
1195     5a/pop-to-edx
1196     59/pop-to-ecx
1197     # . epilogue
1198     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1199     5d/pop-to-ebp
1200     c3/return
1201 
1202 $get-slice-or-stop:stop:
1203     # . write-buffered(err, abort-message-prefix)
1204     # . . push args
1205     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
1206     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1207     # . . call
1208     e8/call  write-buffered/disp32
1209     # . . discard args
1210     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1211     # . write-buffered(err, error)
1212     # . . push args
1213     68/push  ": get-slice-or-stop: key not found: "/imm32
1214     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1215     # . . call
1216     e8/call  write-buffered/disp32
1217     # . . discard args
1218     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1219     # . write-slice-buffered(err, key)
1220     # . . push args
1221     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1222     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1223     # . . call
1224     e8/call  write-slice-buffered/disp32
1225     # . . discard args
1226     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1227     # . write-buffered(err, "\n")
1228     # . . push args
1229     68/push  Newline/imm32
1230     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1231     # . . call
1232     e8/call  write-buffered/disp32
1233     # . . discard args
1234     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1235     # . stop(ed, 1)
1236     # . . push args
1237     68/push  1/imm32
1238     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x1c/disp8      .                 # push *(ebp+28)
1239     # . . call
1240     e8/call  stop/disp32
1241     # never gets here
1242 $get-slice-or-stop:terminus:
1243     # . . discard args
1244     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1245     # syscall(exit, 1)
1246     b8/copy-to-eax  1/imm32/exit
1247     cd/syscall  0x80/imm8
1248 
1249 test-get-slice-or-stop:
1250     # This test uses exit-descriptors. Use ebp for setting up local variables.
1251     # . prologue
1252     55/push-ebp
1253     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1254     # setup
1255     # . clear-stream(_test-error-stream)
1256     # . . push args
1257     68/push  _test-error-stream/imm32
1258     # . . call
1259     e8/call  clear-stream/disp32
1260     # . . discard args
1261     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1262     # . clear-stream(_test-error-buffered-file->buffer)
1263     # . . push args
1264     68/push  _test-error-buffered-file->buffer/imm32
1265     # . . call
1266     e8/call  clear-stream/disp32
1267     # . . discard args
1268     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1269     # var table/ecx : (address stream {string, number}) = stream(2 rows * 8 bytes)
1270     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # subtract from esp
1271     68/push  0x10/imm32/length
1272     68/push  0/imm32/read
1273     68/push  0/imm32/write
1274     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1275     # var ed/edx : (address exit-descriptor)
1276     68/push  0/imm32
1277     68/push  0/imm32
1278     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
1279     # var slice/ebx = "code"
1280     # . (eax..ebx) = "code"
1281     b8/copy-to-eax  "code"/imm32
1282     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # copy *eax to ebx
1283     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
1284     05/add-to-eax  4/imm32
1285     # . ebx = {eax, ebx}
1286     53/push-ebx
1287     50/push-eax
1288     89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
1289     # size 'ed' for the calls to 'get-or-stop' (define no locals past this point)
1290     # . tailor-exit-descriptor(ed, 24)
1291     # . . push args
1292     68/push  0x18/imm32/nbytes-of-args-for-get-or-stop
1293     52/push-edx
1294     # . . call
1295     e8/call  tailor-exit-descriptor/disp32
1296     # . . discard args
1297     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1298     # insert(table, "code", 8 bytes per row)
1299     # . . push args
1300     68/push  8/imm32/row-size
1301     68/push  "code"/imm32
1302     51/push-ecx
1303     # . . call
1304     e8/call  get-or-insert/disp32
1305     # . . discard args
1306     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1307 $test-get-slice-or-stop:success:
1308     # eax = get-slice-or-stop(table, slice, row-size=8, msg, _test-error-buffered-file, ed)
1309     # . . push args
1310     52/push-edx/ed
1311     68/push  _test-error-buffered-file/imm32
1312     68/push  "foo"/imm32/abort-prefix
1313     68/push  8/imm32/row-size
1314     53/push-ebx/slice
1315     51/push-ecx
1316     # . . call
1317     e8/call  get-slice-or-stop/disp32
1318     # registers except esp may be clobbered at this point
1319     # restore register args, discard others
1320     59/pop-to-ecx
1321     5b/pop-to-ebx
1322     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1323     5a/pop-to-edx
1324 $test-get-slice-or-stop:success-assertion:
1325     # check-ints-equal(eax - table->data, 4, msg)  # first row's value slot returned
1326     # . check-ints-equal(eax - table, 16, msg)
1327     # . . push args
1328     68/push  "F - test-get-slice-or-stop/0"/imm32
1329     68/push  0x10/imm32
1330     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1331     50/push-eax
1332     # . . call
1333     e8/call  check-ints-equal/disp32
1334     # . . discard args
1335     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1336 $test-get-slice-or-stop:failure:
1337     # slice = "segment2"
1338     # . *ebx = "segment2"->data
1339     b8/copy-to-eax  "segment2"/imm32
1340     05/add-to-eax  4/imm32
1341     89/copy                         0/mod/indirect  3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # copy eax to *ebx
1342     # . *(ebx+4) = "segment2"->data + len("segment2")
1343     05/add-to-eax  8/imm32/strlen
1344     89/copy                         1/mod/*+disp8   3/rm32/ebx    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(ebx+4)
1345     # eax = get-slice-or-stop(table, slice, row-size=8, msg, _test-error-buffered-file, ed)
1346     # . . push args
1347     52/push-edx/ed
1348     68/push  _test-error-buffered-file/imm32
1349     68/push  "foo"/imm32/abort-prefix
1350     68/push  8/imm32/row-size
1351     53/push-ebx/slice
1352     51/push-ecx
1353     # . . call
1354     e8/call  get-slice-or-stop/disp32
1355     # registers except esp may be clobbered at this point
1356     # restore register args, discard others
1357     59/pop-to-ecx
1358     5b/pop-to-ebx
1359     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1360     5a/pop-to-edx
1361 $test-get-slice-or-stop:failure-assertion:
1362     # check that get-or-stop tried to call stop(1)
1363     # . check-ints-equal(ed->value, 2, msg)
1364     # . . push args
1365     68/push  "F - test-get-or-stop/1"/imm32
1366     68/push  2/imm32
1367     # . . push ed->value
1368     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
1369     # . . call
1370     e8/call  check-ints-equal/disp32
1371     # . . discard args
1372     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1373 $test-get-slice-or-stop:end:
1374     # . epilogue
1375     # don't restore esp from ebp; manually reclaim locals
1376     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x2c/imm32        # add to esp
1377     5d/pop-to-ebp
1378     c3/return
1379 
1380 # if no row is found, return null (0)
1381 maybe-get:  # table : (address stream {string, _}), key : (address string), row-size : int -> eax : (address _)
1382     # pseudocode:
1383     #   curr = table->data
1384     #   max = &table->data[table->write]
1385     #   while curr < max
1386     #     if string-equal?(key, *curr)
1387     #       return curr+4
1388     #     curr += row-size
1389     #   return 0
1390     #
1391     # . prologue
1392     55/push-ebp
1393     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1394     # . save registers
1395     51/push-ecx
1396     52/push-edx
1397     56/push-esi
1398     # esi = table
1399     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
1400     # curr/ecx = table->data
1401     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
1402     # max/edx = table->data + table->write
1403     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
1404     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
1405 $maybe-get:search-loop:
1406     # if (curr >= max) return null
1407     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1408     73/jump-if-greater-or-equal-unsigned  $maybe-get:null/disp8
1409     # if (string-equal?(key, *curr)) return curr+4
1410     # . eax = string-equal?(key, *curr)
1411     # . . push args
1412     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1413     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1414     # . . call
1415     e8/call  string-equal?/disp32
1416     # . . discard args
1417     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1418     # . if (eax != 0) return eax = curr+4
1419     3d/compare-eax-and  0/imm32
1420     74/jump-if-equal  $maybe-get:mismatch/disp8
1421     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy ecx+4 to eax
1422     eb/jump  $maybe-get:end/disp8
1423 $maybe-get:mismatch:
1424     # curr += row-size
1425     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
1426     # loop
1427     eb/jump  $maybe-get:search-loop/disp8
1428 $maybe-get:null:
1429     b8/copy-to-eax  0/imm32
1430 $maybe-get:end:
1431     # . restore registers
1432     5e/pop-to-esi
1433     5a/pop-to-edx
1434     59/pop-to-ecx
1435     # . epilogue
1436     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1437     5d/pop-to-ebp
1438     c3/return
1439 
1440 test-maybe-get:
1441     # . prologue
1442     55/push-ebp
1443     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1444     # - setup: create a table with one row
1445     # var table/ecx : (address stream {string, number}) = stream(2 rows * 8 bytes)
1446     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # subtract from esp
1447     68/push  0x10/imm32/length
1448     68/push  0/imm32/read
1449     68/push  0/imm32/write
1450     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1451     # eax = get-or-insert(table, "code", 8 bytes per row)
1452     # . . push args
1453     68/push  8/imm32/row-size
1454     68/push  "code"/imm32
1455     51/push-ecx
1456     # . . call
1457     e8/call  get-or-insert/disp32
1458     # . . discard args
1459     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1460 $test-maybe-get:success:
1461     # - check for the same key, verify that it was reused
1462     # eax = maybe-get(table, "code", 8 bytes per row)
1463     # . . push args
1464     68/push  8/imm32/row-size
1465     68/push  "code"/imm32
1466     51/push-ecx
1467     # . . call
1468     e8/call  maybe-get/disp32
1469     # . . discard args
1470     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1471     # check-ints-equal(eax - table->data, 4, msg)
1472     # . check-ints-equal(eax - table, 16, msg)
1473     # . . push args
1474     68/push  "F - test-maybe-get/0"/imm32
1475     68/push  0x10/imm32
1476     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1477     50/push-eax
1478     # . . call
1479     e8/call  check-ints-equal/disp32
1480     # . . discard args
1481     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1482     # no new row inserted
1483     # . check-ints-equal(table->write, row-size = 8, msg)
1484     # . . push args
1485     68/push  "F - test-maybe-get/1"/imm32
1486     68/push  8/imm32/row-size
1487     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1488     # . . call
1489     e8/call  check-ints-equal/disp32
1490     # . . discard args
1491     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1492     # check-string-equal(*table->data, "code", msg)
1493     # . . push args
1494     68/push  "F - test-maybe-get/2"/imm32
1495     68/push  "code"/imm32
1496     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
1497     # . . call
1498     e8/call  check-string-equal/disp32
1499     # . . discard args
1500     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1501 $test-maybe-get:failure:
1502     # - search for a new key
1503     # eax = maybe-get(table, "data", 8 bytes per row)
1504     # . . push args
1505     68/push  8/imm32/row-size
1506     68/push  "data"/imm32
1507     51/push-ecx
1508     # . . call
1509     e8/call  maybe-get/disp32
1510     # . . discard args
1511     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1512     # check-ints-equal(eax, 0, msg)
1513     # . . push args
1514     68/push  "F - test-maybe-get/3"/imm32
1515     68/push  0/imm32
1516     50/push-eax
1517     # . . call
1518     e8/call  check-ints-equal/disp32
1519     # . . discard args
1520     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1521 $test-maybe-get:end:
1522     # . epilogue
1523     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1524     5d/pop-to-ebp
1525     c3/return
1526 
1527 # if no row is found, return null (0)
1528 maybe-get-slice:  # table : (address stream {string, _}), key : (address slice), row-size : int -> eax : (address _)
1529     # pseudocode:
1530     #   curr = table->data
1531     #   max = &table->data[table->write]
1532     #   while curr < max
1533     #     if slice-equal?(key, *curr)
1534     #       return curr+4
1535     #     curr += row-size
1536     #   return 0
1537     #
1538     # . prologue
1539     55/push-ebp
1540     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1541     # . save registers
1542     51/push-ecx
1543     52/push-edx
1544     56/push-esi
1545     # esi = table
1546     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
1547     # curr/ecx = table->data
1548     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
1549     # max/edx = table->data + table->write
1550     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
1551     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
1552 $maybe-get-slice:search-loop:
1553     # if (curr >= max) return null
1554     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1555     73/jump-if-greater-or-equal-unsigned  $maybe-get-slice:null/disp8
1556     # if (slice-equal?(key, *curr)) return curr+4
1557     # . eax = slice-equal?(key, *curr)
1558     # . . push args
1559     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1560     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1561     # . . call
1562     e8/call  slice-equal?/disp32
1563     # . . discard args
1564     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1565     # . if (eax != 0) return eax = curr+4
1566     3d/compare-eax-and  0/imm32
1567     74/jump-if-equal  $maybe-get-slice:mismatch/disp8
1568     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy ecx+4 to eax
1569     eb/jump  $maybe-get-slice:end/disp8
1570 $maybe-get-slice:mismatch:
1571     # curr += row-size
1572     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
1573     # loop
1574     eb/jump  $maybe-get-slice:search-loop/disp8
1575 $maybe-get-slice:null:
1576     b8/copy-to-eax  0/imm32
1577 $maybe-get-slice:end:
1578     # . restore registers
1579     5e/pop-to-esi
1580     5a/pop-to-edx
1581     59/pop-to-ecx
1582     # . epilogue
1583     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1584     5d/pop-to-ebp
1585     c3/return
1586 
1587 test-maybe-get-slice:
1588     # . prologue
1589     55/push-ebp
1590     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1591     # - setup: create a table with one row
1592     # var table/ecx : (address stream {string, number}) = stream(2 rows * 8 bytes)
1593     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # subtract from esp
1594     68/push  0x10/imm32/length
1595     68/push  0/imm32/read
1596     68/push  0/imm32/write
1597     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1598     # insert(table, "code", 8 bytes per row)
1599     # . . push args
1600     68/push  8/imm32/row-size
1601     68/push  "code"/imm32
1602     51/push-ecx
1603     # . . call
1604     e8/call  get-or-insert/disp32
1605     # . . discard args
1606     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1607 $test-maybe-get-slice:success:
1608     # - check for the same key, verify that it was reused
1609     # (eax..edx) = "code"
1610     b8/copy-to-eax  "code"/imm32
1611     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
1612     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
1613     05/add-to-eax  4/imm32
1614     # var slice/edx = {eax, edx}
1615     52/push-edx
1616     50/push-eax
1617     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
1618     # eax = maybe-get-slice(table, "code" slice, 8 bytes per row)
1619     # . . push args
1620     68/push  8/imm32/row-size
1621     52/push-edx
1622     51/push-ecx
1623     # . . call
1624     e8/call  maybe-get-slice/disp32
1625     # . . discard args
1626     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1627     # check-ints-equal(eax - table->data, 4, msg)
1628     # . check-ints-equal(eax - table, 16, msg)
1629     # . . push args
1630     68/push  "F - test-maybe-get-slice/0"/imm32
1631     68/push  0x10/imm32
1632     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1633     50/push-eax
1634     # . . call
1635     e8/call  check-ints-equal/disp32
1636     # . . discard args
1637     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1638     # no new row inserted
1639     # . check-ints-equal(table->write, row-size = 8, msg)
1640     # . . push args
1641     68/push  "F - test-maybe-get-slice/1"/imm32
1642     68/push  8/imm32/row-size
1643     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1644     # . . call
1645     e8/call  check-ints-equal/disp32
1646     # . . discard args
1647     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1648     # check-string-equal(*table->data, "code", msg)
1649     # . . push args
1650     68/push  "F - test-maybe-get-slice/2"/imm32
1651     68/push  "code"/imm32
1652     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
1653     # . . call
1654     e8/call  check-string-equal/disp32
1655     # . . discard args
1656     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1657 $test-maybe-get-slice:failure:
1658     # - search for a new key
1659     # (eax..edx) = "data"
1660     b8/copy-to-eax  "data"/imm32
1661     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
1662     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
1663     05/add-to-eax  4/imm32
1664     # var slice/edx = {eax, edx}
1665     52/push-edx
1666     50/push-eax
1667     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
1668     # eax = maybe-get-slice(table, "data" slice, 8 bytes per row)
1669     # . . push args
1670     68/push  8/imm32/row-size
1671     52/push-edx
1672     51/push-ecx
1673     # . . call
1674     e8/call  maybe-get-slice/disp32
1675     # . . discard args
1676     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1677     # check-ints-equal(eax, 0, msg)
1678     # . . push args
1679     68/push  "F - test-maybe-get-slice/3"/imm32
1680     68/push  0/imm32
1681     50/push-eax
1682     # . . call
1683     e8/call  check-ints-equal/disp32
1684     # . . discard args
1685     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1686 $test-maybe-get-slice:end:
1687     # . epilogue
1688     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1689     5d/pop-to-ebp
1690     c3/return
1691 
1692 # . . vim:nowrap:textwidth=0