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