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