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 an 8-byte key -- a (handle array byte) -- and a variable-size value.
   4 #
   5 # Accessing the table performs a linear scan for a key string, and always
   6 # requires passing in the row size.
   7 #
   8 # Table primitives have the form <variant>(stream, <arg>, row-size, ...) -> address/eax
   9 #
  10 # The following table shows available options for <variant>:
  11 #   if not found:           | arg=string              arg=slice
  12 #   ------------------------+---------------------------------------------------
  13 #   abort                   | get                     get-slice
  14 #   insert key              | get-or-insert           get-or-insert-slice
  15 #                           | get-or-insert-handle
  16 #   stop                    | get-or-stop             get-slice-or-stop
  17 #   return null             | maybe-get               maybe-get-slice
  18 # Some variants may take extra args.
  19 
  20 == code
  21 #   instruction                     effective address                                                   register    displacement    immediate
  22 # . op          subop               mod             rm32          base        index         scale       r32
  23 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
  24 
  25 # if no row is found, abort
  26 get:  # table: (addr stream {(handle array byte), T}), key: (addr array byte), row-size: int, abort-message-prefix: (addr array byte) -> eax: (addr T)
  27     # pseudocode:
  28     #   curr = table->data
  29     #   max = &table->data[table->write]
  30     #   while curr < max
  31     #     var c: (addr array byte) = lookup(*curr)
  32     #     if string-equal?(key, c)
  33     #       return curr+8
  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 handle array byte) = 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     # var c/eax: (addr array byte) = lookup(*curr)
  56     # . . push args
  57     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
  58     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
  59     # . . call
  60     e8/call  lookup/disp32
  61     # . . discard args
  62     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
  63     # if (string-equal?(key, c)) return curr+8
  64     # . eax = string-equal?(key, c)
  65     # . . push args
  66     50/push-eax
  67     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
  68     # . . call
  69     e8/call  string-equal?/disp32
  70     # . . discard args
  71     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
  72     # . if (eax != false) return eax = curr+8
  73     3d/compare-eax-and  0/imm32/false
  74     74/jump-if-=  $get:mismatch/disp8
  75     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
  76     eb/jump  $get:end/disp8
  77 $get:mismatch:
  78     # curr += row-size
  79     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
  80     # loop
  81     eb/jump  $get:search-loop/disp8
  82 $get:end:
  83     # . restore registers
  84     5e/pop-to-esi
  85     5a/pop-to-edx
  86     59/pop-to-ecx
  87     # . epilogue
  88     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
  89     5d/pop-to-ebp
  90     c3/return
  91 
  92 $get:abort:
  93     # . _write(2/stderr, abort-message-prefix)
  94     # . . push args
  95     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
  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, error)
 102     # . . push args
 103     68/push  ": get: key not found: "/imm32
 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, key)
 110     # . . push args
 111     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 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     # . _write(2/stderr, "\n")
 118     # . . push args
 119     68/push  Newline/imm32
 120     68/push  2/imm32/stderr
 121     # . . call
 122     e8/call  _write/disp32
 123     # . . discard args
 124     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 125     # . syscall(exit, 1)
 126     bb/copy-to-ebx  1/imm32
 127     b8/copy-to-eax  1/imm32/exit
 128     cd/syscall  0x80/imm8
 129     # never gets here
 130 
 131 test-get:
 132     # . prologue
 133     55/push-ebp
 134     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 135     # - setup: create a table with a couple of keys
 136     # var table/ecx: (stream {(handle array byte), number} 24)  # 2 rows * 12 bytes/row
 137     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
 138     68/push  0x18/imm32/size
 139     68/push  0/imm32/read
 140     68/push  0/imm32/write
 141     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
 142     # insert(table, "code", 12 bytes/row, Heap)
 143     # . . push args
 144     68/push  Heap/imm32
 145     68/push  0xc/imm32/row-size
 146     68/push  "code"/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    .           .             .           .           .               0x10/imm32        # add to esp
 152     # insert(table, "data", 12 bytes/row, Heap)
 153     # . . push args
 154     68/push  Heap/imm32
 155     68/push  0xc/imm32/row-size
 156     68/push  "data"/imm32
 157     51/push-ecx
 158     # . . call
 159     e8/call  get-or-insert/disp32
 160     # . . discard args
 161     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
 162 $test-get:check1:
 163     # eax = get(table, "code", 12 bytes/row)
 164     # . . push args
 165     68/push  0xc/imm32/row-size
 166     68/push  "code"/imm32
 167     51/push-ecx
 168     # . . call
 169     e8/call  get/disp32
 170     # . . discard args
 171     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 172     # check-ints-equal(eax - table->data, 8, msg)
 173     # . check-ints-equal(eax - table, 20, msg)
 174     # . . push args
 175     68/push  "F - test-get/0"/imm32
 176     68/push  0x14/imm32
 177     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 178     50/push-eax
 179     # . . call
 180     e8/call  check-ints-equal/disp32
 181     # . . discard args
 182     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 183 $test-get:check2:
 184     # eax = get(table, "data", 12 bytes/row)
 185     # . . push args
 186     68/push  0xc/imm32/row-size
 187     68/push  "data"/imm32
 188     51/push-ecx
 189     # . . call
 190     e8/call  get/disp32
 191     # . . discard args
 192     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 193     # check-ints-equal(eax - table->data, 20, msg)
 194     # . check-ints-equal(eax - table, 32, msg)
 195     # . . push args
 196     68/push  "F - test-get/1"/imm32
 197     68/push  0x20/imm32
 198     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 199     50/push-eax
 200     # . . call
 201     e8/call  check-ints-equal/disp32
 202     # . . discard args
 203     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 204 $test-get:end:
 205     # . epilogue
 206     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 207     5d/pop-to-ebp
 208     c3/return
 209 
 210 # if no row is found, abort
 211 get-slice:  # table: (addr stream {(handle array byte), T}), key: (addr slice), row-size: int, abort-message-prefix: (addr array byte) -> eax: (addr T)
 212     # pseudocode:
 213     #   curr = table->data
 214     #   max = &table->data[table->write]
 215     #   while curr < max
 216     #     var c: (addr array byte) = lookup(*curr)
 217     #     if slice-equal?(key, c)
 218     #       return curr+8
 219     #     curr += row-size
 220     #   abort
 221     #
 222     # . prologue
 223     55/push-ebp
 224     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 225     # . save registers
 226     51/push-ecx
 227     52/push-edx
 228     56/push-esi
 229     # esi = table
 230     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 231     # var curr/ecx: (addr handle array byte) = table->data
 232     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
 233     # var max/edx: (addr byte) = &table->data[table->write]
 234     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
 235     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
 236 $get-slice:search-loop:
 237     # if (curr >= max) abort
 238     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
 239     73/jump-if-addr>=  $get-slice:abort/disp8
 240     # var c/eax: (addr array byte) = lookup(*curr)
 241     # . . push args
 242     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
 243     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 244     # . . call
 245     e8/call  lookup/disp32
 246     # . . discard args
 247     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 248     # if (slice-equal?(key, c)) return curr+8
 249     # . eax = slice-equal?(key, c)
 250     # . . push args
 251     50/push-eax
 252     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 253     # . . call
 254     e8/call  slice-equal?/disp32
 255     # . . discard args
 256     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 257     # . if (eax != false) return eax = curr+8
 258     3d/compare-eax-and  0/imm32/false
 259     74/jump-if-=  $get-slice:mismatch/disp8
 260     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
 261     eb/jump  $get-slice:end/disp8
 262 $get-slice:mismatch:
 263     # curr += row-size
 264     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
 265     # loop
 266     eb/jump  $get-slice:search-loop/disp8
 267 $get-slice:end:
 268     # . restore registers
 269     5e/pop-to-esi
 270     5a/pop-to-edx
 271     59/pop-to-ecx
 272     # . epilogue
 273     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 274     5d/pop-to-ebp
 275     c3/return
 276 
 277 $get-slice:abort:
 278     # . _write(2/stderr, abort-message-prefix)
 279     # . . push args
 280     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
 281     68/push  2/imm32/stderr
 282     # . . call
 283     e8/call  _write/disp32
 284     # . . discard args
 285     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 286     # . _write(2/stderr, error)
 287     # . . push args
 288     68/push  ": get-slice: key not found: "/imm32
 289     68/push  2/imm32/stderr
 290     # . . call
 291     e8/call  _write/disp32
 292     # . . discard args
 293     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 294     # . write-slice-buffered(Stderr, key)
 295     # . . push args
 296     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 297     68/push  Stderr/imm32
 298     # . . call
 299     e8/call  write-slice-buffered/disp32
 300     # . . discard args
 301     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 302     # . flush(Stderr)
 303     # . . push args
 304     68/push  Stderr/imm32
 305     # . . call
 306     e8/call  flush/disp32
 307     # . . discard args
 308     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 309     # . _write(2/stderr, "\n")
 310     # . . push args
 311     68/push  Newline/imm32
 312     68/push  2/imm32/stderr
 313     # . . call
 314     e8/call  _write/disp32
 315     # . . discard args
 316     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 317     # . syscall(exit, 1)
 318     bb/copy-to-ebx  1/imm32
 319     b8/copy-to-eax  1/imm32/exit
 320     cd/syscall  0x80/imm8
 321     # never gets here
 322 
 323 test-get-slice:
 324     # . prologue
 325     55/push-ebp
 326     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 327     # - setup: create a table with a couple of keys
 328     # var table/ecx: (stream {string, number} 24)  # 2 rows * 12 bytes/row
 329     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
 330     68/push  0x18/imm32/size
 331     68/push  0/imm32/read
 332     68/push  0/imm32/write
 333     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
 334     # insert(table, "code", 12 bytes/row, Heap)
 335     # . . push args
 336     68/push  Heap/imm32
 337     68/push  0xc/imm32/row-size
 338     68/push  "code"/imm32
 339     51/push-ecx
 340     # . . call
 341     e8/call  get-or-insert/disp32
 342     # . . discard args
 343     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
 344     # insert(table, "data", 12 bytes/row, Heap)
 345     # . . push args
 346     68/push  Heap/imm32
 347     68/push  0xc/imm32/row-size
 348     68/push  "data"/imm32
 349     51/push-ecx
 350     # . . call
 351     e8/call  get-or-insert/disp32
 352     # . . discard args
 353     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
 354 $test-get-slice:check1:
 355     # (eax..edx) = "code"
 356     b8/copy-to-eax  "code"/imm32
 357     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
 358     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
 359     05/add-to-eax  4/imm32
 360     # var slice/edx: slice = {eax, edx}
 361     52/push-edx
 362     50/push-eax
 363     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
 364     # eax = get-slice(table, "code", 12 bytes/row)
 365     # . . push args
 366     68/push  0xc/imm32/row-size
 367     52/push-edx
 368     51/push-ecx
 369     # . . call
 370     e8/call  get-slice/disp32
 371     # . . discard args
 372     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 373     # check-ints-equal(eax - table->data, 8, msg)  # first row's value slot returned
 374     # . check-ints-equal(eax - table, 20, msg)
 375     # . . push args
 376     68/push  "F - test-get-slice/0"/imm32
 377     68/push  0x14/imm32
 378     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 379     50/push-eax
 380     # . . call
 381     e8/call  check-ints-equal/disp32
 382     # . . discard args
 383     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 384 $test-get-slice:check2:
 385     # (eax..edx) = "data"
 386     b8/copy-to-eax  "data"/imm32
 387     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
 388     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
 389     05/add-to-eax  4/imm32
 390     # var slice/edx: slice = {eax, edx}
 391     52/push-edx
 392     50/push-eax
 393     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
 394     # eax = get-slice(table, "data" slice, 12 bytes/row)
 395     # . . push args
 396     68/push  0xc/imm32/row-size
 397     52/push-edx
 398     51/push-ecx
 399     # . . call
 400     e8/call  get-slice/disp32
 401     # . . discard args
 402     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 403     # check-ints-equal(eax - table->data, 20, msg)
 404     # . check-ints-equal(eax - table, 32, msg)
 405     # . . push args
 406     68/push  "F - test-get-slice/1"/imm32
 407     68/push  0x20/imm32
 408     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 409     50/push-eax
 410     # . . call
 411     e8/call  check-ints-equal/disp32
 412     # . . discard args
 413     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 414 $test-get-slice:end:
 415     # . epilogue
 416     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 417     5d/pop-to-ebp
 418     c3/return
 419 
 420 # if no row is found, save 'key' to the next available row
 421 # if there are no rows free, abort
 422 # return the address of the value
 423 get-or-insert:  # table: (addr stream {(handle array byte), T}), key: (addr array byte), row-size: int, ad: (addr allocation-descriptor) -> eax: (addr T)
 424     # pseudocode:
 425     #   curr = table->data
 426     #   max = &table->data[table->write]
 427     #   while curr < max
 428     #     var c: (addr array byte) = lookup(*curr)
 429     #     if string-equal?(key, c)
 430     #       return curr+8
 431     #     curr += row-size
 432     #   if table->write >= table->size
 433     #     abort
 434     #   zero-out(max, row-size)
 435     #   copy-array(ad, key, max)
 436     #   table->write += row-size
 437     #   return max+8
 438     #
 439     # . prologue
 440     55/push-ebp
 441     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 442     # . save registers
 443     51/push-ecx
 444     52/push-edx
 445     56/push-esi
 446     # esi = table
 447     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 448     # var curr/ecx: (addr handle array byte) = table->data
 449     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
 450     # var max/edx: (addr byte) = &table->data[table->write]
 451     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
 452     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
 453 $get-or-insert:search-loop:
 454     # if (curr >= max) break
 455     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
 456     73/jump-if-addr>=  $get-or-insert:not-found/disp8
 457     # var c/eax: (addr array byte) = lookup(*curr)
 458     # . . push args
 459     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
 460     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 461     # . . call
 462     e8/call  lookup/disp32
 463     # . . discard args
 464     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 465     # if (string-equal?(key, c)) return curr+8
 466     # . eax = string-equal?(key, c)
 467     # . . push args
 468     50/push-eax
 469     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 470     # . . call
 471     e8/call  string-equal?/disp32
 472     # . . discard args
 473     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 474     # . if (eax != false) return eax = curr+8
 475     3d/compare-eax-and  0/imm32/false
 476     74/jump-if-=  $get-or-insert:mismatch/disp8
 477     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
 478     eb/jump  $get-or-insert:end/disp8
 479 $get-or-insert:mismatch:
 480     # curr += row-size
 481     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
 482     # loop
 483     eb/jump  $get-or-insert:search-loop/disp8
 484 $get-or-insert:not-found:
 485     # if (table->write >= table->size) abort
 486     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
 487     3b/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   8/disp8         .                 # compare ecx with *(esi+8)
 488     73/jump-if-addr>=  $get-or-insert:abort/disp8
 489     # zero-out(max, row-size)
 490     # . . push args
 491     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
 492     52/push-edx
 493     # . . call
 494     e8/call  zero-out/disp32
 495     # . . discard args
 496     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 497     # copy-array(ad, key, max)
 498     # . . push args
 499     52/push-edx
 500     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 501     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
 502     # . . call
 503     e8/call  copy-array/disp32
 504     # . . discard args
 505     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 506     # table->write += row-size
 507     # . eax = row-size
 508     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
 509     # . table->write += eax
 510     01/add                          0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # add eax to *esi
 511     # return max+8
 512     # . eax = max
 513     89/copy                         3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy edx to eax
 514     # . eax += 8
 515     05/add-to-eax  8/imm32
 516 $get-or-insert:end:
 517     # . restore registers
 518     5e/pop-to-esi
 519     5a/pop-to-edx
 520     59/pop-to-ecx
 521     # . epilogue
 522     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 523     5d/pop-to-ebp
 524     c3/return
 525 
 526 $get-or-insert:abort:
 527     # . _write(2/stderr, error)
 528     # . . push args
 529     68/push  "get-or-insert: table is full\n"/imm32
 530     68/push  2/imm32/stderr
 531     # . . call
 532     e8/call  _write/disp32
 533     # . . discard args
 534     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 535     # . syscall(exit, 1)
 536     bb/copy-to-ebx  1/imm32
 537     b8/copy-to-eax  1/imm32/exit
 538     cd/syscall  0x80/imm8
 539     # never gets here
 540 
 541 test-get-or-insert:
 542     # . prologue
 543     55/push-ebp
 544     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 545     # var table/ecx: (stream {(handle array byte), number} 24)  # 2 rows * 12 bytes/row
 546     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
 547     68/push  0x18/imm32/size
 548     68/push  0/imm32/read
 549     68/push  0/imm32/write
 550     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
 551 $test-get-or-insert:first-call:
 552     # - start with an empty table, insert one key, verify that it was inserted
 553     # eax = get-or-insert(table, "code", 12 bytes/row, Heap)
 554     # . . push args
 555     68/push  Heap/imm32
 556     68/push  0xc/imm32/row-size
 557     68/push  "code"/imm32
 558     51/push-ecx
 559     # . . call
 560     e8/call  get-or-insert/disp32
 561     # . . discard args
 562     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
 563     # check-ints-equal(eax - table->data, 8, msg)  # first row's value slot returned
 564     # . check-ints-equal(eax - table, 20, msg)
 565     # . . push args
 566     68/push  "F - test-get-or-insert/0"/imm32
 567     68/push  0x14/imm32
 568     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 569     50/push-eax
 570     # . . call
 571     e8/call  check-ints-equal/disp32
 572     # . . discard args
 573     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 574     # check-ints-equal(table->write, row-size = 12, msg)
 575     # . . push args
 576     68/push  "F - test-get-or-insert/1"/imm32
 577     68/push  0xc/imm32/row-size
 578     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 579     # . . call
 580     e8/call  check-ints-equal/disp32
 581     # . . discard args
 582     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 583     # var curr-addr/eax: (addr array byte) = lookup(table->data)
 584     # . . push args
 585     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
 586     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
 587     # . . call
 588     e8/call  lookup/disp32
 589     # . . discard args
 590     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 591     # check-strings-equal(curr-addr, "code", msg)
 592     # . . push args
 593     68/push  "F - test-get-or-insert/2"/imm32
 594     68/push  "code"/imm32
 595     50/push-eax
 596     # . . call
 597     e8/call  check-strings-equal/disp32
 598     # . . discard args
 599     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 600 $test-get-or-insert:second-call:
 601     # - insert the same key again, verify that it was reused
 602     # eax = get-or-insert(table, "code", 12 bytes/row, Heap)
 603     # . . push args
 604     68/push  Heap/imm32
 605     68/push  0xc/imm32/row-size
 606     68/push  "code"/imm32
 607     51/push-ecx
 608     # . . call
 609     e8/call  get-or-insert/disp32
 610     # . . discard args
 611     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
 612     # check-ints-equal(eax - table->data, 8, msg)
 613     # . check-ints-equal(eax - table, 20, msg)
 614     # . . push args
 615     68/push  "F - test-get-or-insert/3"/imm32
 616     68/push  0x14/imm32
 617     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 618     50/push-eax
 619     # . . call
 620     e8/call  check-ints-equal/disp32
 621     # . . discard args
 622     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 623     # no new row inserted
 624     # . check-ints-equal(table->write, row-size = 12, msg)
 625     # . . push args
 626     68/push  "F - test-get-or-insert/4"/imm32
 627     68/push  0xc/imm32/row-size
 628     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 629     # . . call
 630     e8/call  check-ints-equal/disp32
 631     # . . discard args
 632     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 633     # curr-addr = lookup(table->data)
 634     # . . push args
 635     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
 636     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
 637     # . . call
 638     e8/call  lookup/disp32
 639     # . . discard args
 640     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 641     # check-strings-equal(curr-addr, "code", msg)
 642     # . . push args
 643     68/push  "F - test-get-or-insert/5"/imm32
 644     68/push  "code"/imm32
 645     50/push-eax
 646     # . . call
 647     e8/call  check-strings-equal/disp32
 648     # . . discard args
 649     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 650 $test-get-or-insert:third-call:
 651     # - insert a new key, verify that it was inserted
 652     # eax = get-or-insert(table, "data", 12 bytes/row, Heap)
 653     # . . push args
 654     68/push  Heap/imm32
 655     68/push  0xc/imm32/row-size
 656     68/push  "data"/imm32
 657     51/push-ecx
 658     # . . call
 659     e8/call  get-or-insert/disp32
 660     # . . discard args
 661     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
 662     # table gets a new row
 663     # check-ints-equal(eax - table->data, 20, msg)  # second row's value slot returned
 664     # . check-ints-equal(eax - table, 32, msg)
 665     # . . push args
 666     68/push  "F - test-get-or-insert/6"/imm32
 667     68/push  0x20/imm32
 668     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 669     50/push-eax
 670     # . . call
 671     e8/call  check-ints-equal/disp32
 672     # . . discard args
 673     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 674     # check-ints-equal(table->write, 2 rows = 24, msg)
 675     # . . push args
 676     68/push  "F - test-get-or-insert/7"/imm32
 677     68/push  0x18/imm32/two-rows
 678     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 679     # . . call
 680     e8/call  check-ints-equal/disp32
 681     # . . discard args
 682     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 683     # curr-addr = lookup(table->data+12)
 684     # . . push args
 685     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x1c/disp8      .                 # push *(ecx+28)
 686     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x18/disp8      .                 # push *(ecx+24)
 687     # . . call
 688     e8/call  lookup/disp32
 689     # . . discard args
 690     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 691     # check-strings-equal(curr-addr, "data", msg)
 692     # . . push args
 693     68/push  "F - test-get-or-insert/8"/imm32
 694     68/push  "data"/imm32
 695     50/push-eax
 696     # . . call
 697     e8/call  check-strings-equal/disp32
 698     # . . discard args
 699     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 700 $test-get-or-insert:end:
 701     # . epilogue
 702     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 703     5d/pop-to-ebp
 704     c3/return
 705 
 706 # if no row is found, save 'key' to the next available row
 707 # if there are no rows free, abort
 708 # return the address of the value
 709 get-or-insert-handle:  # table: (addr stream {(handle array byte), T}), key: (handle array byte), row-size: int -> eax: (addr T)
 710     # pseudocode:
 711     #   var curr: (addr handle stream) = table->data
 712     #   var max: (addr byte) = &table->data[table->write]
 713     #   var k: (addr array byte) = lookup(key)
 714     #   while curr < max
 715     #     var c: (addr array byte) = lookup(*curr)
 716     #     if string-equal?(k, c)
 717     #       return curr+8
 718     #     curr += row-size
 719     #   if table->write >= table->size
 720     #     abort
 721     #   *max = key
 722     #   table->write += row-size
 723     #   return max+8
 724     #
 725     # . prologue
 726     55/push-ebp
 727     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 728     # . save registers
 729     51/push-ecx
 730     52/push-edx
 731     53/push-ebx
 732     56/push-esi
 733     # esi = table
 734     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 735     # var k/ebx: (addr array byte) = lookup(key)
 736     # . eax = lookup(key)
 737     # . . push args
 738     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
 739     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 740     # . . call
 741     e8/call  lookup/disp32
 742     # . . discard args
 743     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 744     # . ebx = eax
 745     89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # copy eax to ebx
 746     # var curr/ecx: (addr handle array byte) = table->data
 747     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
 748     # var max/edx: (addr byte) = &table->data[table->write]
 749     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
 750     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
 751 $get-or-insert-handle:search-loop:
 752     # if (curr >= max) break
 753     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
 754     73/jump-if-addr>=  $get-or-insert-handle:not-found/disp8
 755     # var c/eax: (addr array byte) = lookup(*curr)
 756     # . . push args
 757     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
 758     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 759     # . . call
 760     e8/call  lookup/disp32
 761     # . . discard args
 762     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 763     # if (string-equal?(k, c)) return curr+8
 764     # . eax = string-equal?(k, c)
 765     # . . push args
 766     50/push-eax
 767     53/push-ebx
 768     # . . call
 769     e8/call  string-equal?/disp32
 770     # . . discard args
 771     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 772     # . if (eax != false) return eax = curr+8
 773     3d/compare-eax-and  0/imm32/false
 774     74/jump-if-=  $get-or-insert-handle:mismatch/disp8
 775     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
 776     eb/jump  $get-or-insert-handle:end/disp8
 777 $get-or-insert-handle:mismatch:
 778     # curr += row-size
 779     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x14/disp8      .                 # add *(ebp+20) to ecx
 780     # loop
 781     eb/jump  $get-or-insert-handle:search-loop/disp8
 782 $get-or-insert-handle:not-found:
 783     # if (table->write >= table->size) abort
 784     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
 785     3b/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   8/disp8         .                 # compare ecx with *(esi+8)
 786     73/jump-if-addr>=  $get-or-insert-handle:abort/disp8
 787     # table->write += row-size
 788     # . eax = row-size
 789     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x14/disp8      .                 # copy *(ebp+20) to eax
 790     # . table->write += eax
 791     01/add                          0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # add eax to *esi
 792     # *max = key
 793     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
 794     89/copy                         0/mod/indirect  2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy eax to *edx
 795     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
 796     89/copy                         1/mod/*+disp8   2/rm32/edx    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edx+4)
 797     # return max+8
 798     # . eax = max
 799     89/copy                         3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy edx to eax
 800     # . eax += 8
 801     05/add-to-eax  8/imm32
 802 $get-or-insert-handle:end:
 803     # . restore registers
 804     5e/pop-to-esi
 805     5b/pop-to-ebx
 806     5a/pop-to-edx
 807     59/pop-to-ecx
 808     # . epilogue
 809     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 810     5d/pop-to-ebp
 811     c3/return
 812 
 813 $get-or-insert-handle:abort:
 814     # . _write(2/stderr, error)
 815     # . . push args
 816     68/push  "get-or-insert-handle: table is full\n"/imm32
 817     68/push  2/imm32/stderr
 818     # . . call
 819     e8/call  _write/disp32
 820     # . . discard args
 821     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 822     # . syscall(exit, 1)
 823     bb/copy-to-ebx  1/imm32
 824     b8/copy-to-eax  1/imm32/exit
 825     cd/syscall  0x80/imm8
 826     # never gets here
 827 
 828 test-get-or-insert-handle:
 829     # . prologue
 830     55/push-ebp
 831     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 832     # var table/ecx: (stream {(handle array byte), number} 24)  # 2 rows * 12 bytes/row
 833     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
 834     68/push  0x18/imm32/size
 835     68/push  0/imm32/read
 836     68/push  0/imm32/write
 837     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
 838     # var h/edx: (handle array byte)
 839     68/push  0/imm32
 840     68/push  0/imm32
 841     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
 842 $test-get-or-insert-handle:first-call:
 843     # - start with an empty table, insert one key, verify that it was inserted
 844     # copy-array(Heap, "code", h)
 845     # . . push args
 846     52/push-edx
 847     68/push  "code"/imm32
 848     68/push  Heap/imm32
 849     # . . call
 850     e8/call  copy-array/disp32
 851     # . . discard args
 852     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 853     # eax = get-or-insert-handle(table, h, 12 bytes/row)
 854     # . . push args
 855     68/push  0xc/imm32/row-size
 856     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
 857     ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
 858     51/push-ecx
 859     # . . call
 860     e8/call  get-or-insert-handle/disp32
 861     # . . discard args
 862     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
 863     # check-ints-equal(eax - table->data, 8, msg)  # first row's value slot returned
 864     # . check-ints-equal(eax - table, 20, msg)
 865     # . . push args
 866     68/push  "F - test-get-or-insert-handle/0"/imm32
 867     68/push  0x14/imm32
 868     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 869     50/push-eax
 870     # . . call
 871     e8/call  check-ints-equal/disp32
 872     # . . discard args
 873     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 874     # check-ints-equal(table->write, row-size = 12, msg)
 875     # . . push args
 876     68/push  "F - test-get-or-insert-handle/1"/imm32
 877     68/push  0xc/imm32/row-size
 878     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 879     # . . call
 880     e8/call  check-ints-equal/disp32
 881     # . . discard args
 882     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 883     # var curr-addr/eax: (addr array byte) = lookup(table->data)
 884     # . . push args
 885     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
 886     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
 887     # . . call
 888     e8/call  lookup/disp32
 889     # . . discard args
 890     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 891     # check-strings-equal(curr-addr, "code", msg)
 892     # . . push args
 893     68/push  "F - test-get-or-insert-handle/2"/imm32
 894     68/push  "code"/imm32
 895     50/push-eax
 896     # . . call
 897     e8/call  check-strings-equal/disp32
 898     # . . discard args
 899     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 900 $test-get-or-insert-handle:second-call:
 901     # - insert the same key again, verify that it was reused
 902     # copy-array(Heap, "code", h)
 903     # . . push args
 904     52/push-edx
 905     68/push  "code"/imm32
 906     68/push  Heap/imm32
 907     # . . call
 908     e8/call  copy-array/disp32
 909     # . . discard args
 910     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 911     # eax = get-or-insert-handle(table, h, 12 bytes/row)
 912     # . . push args
 913     68/push  0xc/imm32/row-size
 914     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
 915     ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
 916     51/push-ecx
 917     # . . call
 918     e8/call  get-or-insert-handle/disp32
 919     # . . discard args
 920     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
 921     # check-ints-equal(eax - table->data, 8, msg)
 922     # . check-ints-equal(eax - table, 20, msg)
 923     # . . push args
 924     68/push  "F - test-get-or-insert-handle/3"/imm32
 925     68/push  0x14/imm32
 926     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 927     50/push-eax
 928     # . . call
 929     e8/call  check-ints-equal/disp32
 930     # . . discard args
 931     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 932     # no new row inserted
 933     # . check-ints-equal(table->write, row-size = 12, msg)
 934     # . . push args
 935     68/push  "F - test-get-or-insert-handle/4"/imm32
 936     68/push  0xc/imm32/row-size
 937     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 938     # . . call
 939     e8/call  check-ints-equal/disp32
 940     # . . discard args
 941     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 942     # curr-addr = lookup(table->data)
 943     # . . push args
 944     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
 945     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
 946     # . . call
 947     e8/call  lookup/disp32
 948     # . . discard args
 949     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 950     # check-strings-equal(curr-addr, "code", msg)
 951     # . . push args
 952     68/push  "F - test-get-or-insert-handle/5"/imm32
 953     68/push  "code"/imm32
 954     50/push-eax
 955     # . . call
 956     e8/call  check-strings-equal/disp32
 957     # . . discard args
 958     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 959 $test-get-or-insert-handle:third-call:
 960     # - insert a new key, verify that it was inserted
 961     # copy-array(Heap, "data", h)
 962     # . . push args
 963     52/push-edx
 964     68/push  "data"/imm32
 965     68/push  Heap/imm32
 966     # . . call
 967     e8/call  copy-array/disp32
 968     # . . discard args
 969     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 970     # eax = get-or-insert-handle(table, h, 12 bytes/row)
 971     # . . push args
 972     68/push  0xc/imm32/row-size
 973     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
 974     ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
 975     51/push-ecx
 976     # . . call
 977     e8/call  get-or-insert-handle/disp32
 978     # . . discard args
 979     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
 980     # table gets a new row
 981     # check-ints-equal(eax - table->data, 20, msg)  # second row's value slot returned
 982     # . check-ints-equal(eax - table, 32, msg)
 983     # . . push args
 984     68/push  "F - test-get-or-insert-handle/6"/imm32
 985     68/push  0x20/imm32
 986     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 987     50/push-eax
 988     # . . call
 989     e8/call  check-ints-equal/disp32
 990     # . . discard args
 991     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 992     # check-ints-equal(table->write, 2 rows = 24, msg)
 993     # . . push args
 994     68/push  "F - test-get-or-insert-handle/7"/imm32
 995     68/push  0x18/imm32/two-rows
 996     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 997     # . . call
 998     e8/call  check-ints-equal/disp32
 999     # . . discard args
1000     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1001     # curr-addr = lookup(table->data+12)
1002     # . . push args
1003     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x1c/disp8      .                 # push *(ecx+28)
1004     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x18/disp8      .                 # push *(ecx+24)
1005     # . . call
1006     e8/call  lookup/disp32
1007     # . . discard args
1008     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1009     # check-strings-equal(curr-addr, "data", msg)
1010     # . . push args
1011     68/push  "F - test-get-or-insert-handle/8"/imm32
1012     68/push  "data"/imm32
1013     50/push-eax
1014     # . . call
1015     e8/call  check-strings-equal/disp32
1016     # . . discard args
1017     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1018 $test-get-or-insert-handle:end:
1019     # . epilogue
1020     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1021     5d/pop-to-ebp
1022     c3/return
1023 
1024 # if no row is found, save 'key' in the next available row
1025 # if there are no rows free, abort
1026 get-or-insert-slice:  # table: (addr stream {(handle array byte), T}), key: (addr slice), row-size: int, ad: (addr allocation-descriptor) -> eax: (addr T)
1027     # pseudocode:
1028     #   curr = table->data
1029     #   max = &table->data[table->write]
1030     #   while curr < max
1031     #     var c: (addr array byte) = lookup(*curr)
1032     #     if slice-equal?(key, *curr)
1033     #       return curr+8
1034     #     curr += row-size
1035     #   if table->write >= table->size
1036     #     abort
1037     #   zero-out(max, row-size)
1038     #   slice-to-string(ad, key, max)
1039     #   table->write += row-size
1040     #   return max+8
1041     #
1042     # . prologue
1043     55/push-ebp
1044     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1045     # . save registers
1046     51/push-ecx
1047     52/push-edx
1048     56/push-esi
1049     # esi = table
1050     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
1051     # var curr/ecx: (addr handle array byte) = table->data
1052     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
1053     # var max/edx: (addr byte) = &table->data[table->write]
1054     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
1055     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
1056 $get-or-insert-slice:search-loop:
1057     # if (curr >= max) break
1058     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1059     73/jump-if-addr>=  $get-or-insert-slice:not-found/disp8
1060     # var c/eax: (addr array byte) = lookup(*curr)
1061     # . . push args
1062     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
1063     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1064     # . . call
1065     e8/call  lookup/disp32
1066     # . . discard args
1067     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1068     # if (slice-equal?(key, c)) return curr+4
1069     # . eax = slice-equal?(key, c)
1070     # . . push args
1071     50/push-eax
1072     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1073     # . . call
1074     e8/call  slice-equal?/disp32
1075     # . . discard args
1076     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1077     # . if (eax != false) return eax = curr+8
1078     3d/compare-eax-and  0/imm32/false
1079     74/jump-if-=  $get-or-insert-slice:mismatch/disp8
1080     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
1081     eb/jump  $get-or-insert-slice:end/disp8
1082 $get-or-insert-slice:mismatch:
1083     # curr += row-size
1084     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
1085     # loop
1086     eb/jump  $get-or-insert-slice:search-loop/disp8
1087 $get-or-insert-slice:not-found:
1088     # result/eax = 0
1089     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
1090     # if (table->write >= table->size) abort
1091     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
1092     3b/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   8/disp8         .                 # compare ecx with *(esi+8)
1093     7d/jump-if->=  $get-or-insert-slice:abort/disp8
1094     # zero-out(max, row-size)
1095     # . . push args
1096     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
1097     52/push-edx
1098     # . . call
1099     e8/call  zero-out/disp32
1100     # . . discard args
1101     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1102     # slice-to-string(ad, key, max)
1103     # . . push args
1104     52/push-edx
1105     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1106     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
1107     # . . call
1108     e8/call  slice-to-string/disp32
1109     # . . discard args
1110     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1111     # table->write += row-size
1112     # . eax = row-size
1113     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
1114     # . table->write += eax
1115     01/add                          0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # add eax to *esi
1116     # return max+8
1117     # . eax = max
1118     89/copy                         3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy edx to eax
1119     # . eax += 8
1120     05/add-to-eax  8/imm32
1121 $get-or-insert-slice:end:
1122     # . restore registers
1123     5e/pop-to-esi
1124     5a/pop-to-edx
1125     59/pop-to-ecx
1126     # . epilogue
1127     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1128     5d/pop-to-ebp
1129     c3/return
1130 
1131 $get-or-insert-slice:abort:
1132     # . _write(2/stderr, error)
1133     # . . push args
1134     68/push  "get-or-insert-slice: table is full\n"/imm32
1135     68/push  2/imm32/stderr
1136     # . . call
1137     e8/call  _write/disp32
1138     # . . discard args
1139     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1140     # . syscall(exit, 1)
1141     bb/copy-to-ebx  1/imm32
1142     b8/copy-to-eax  1/imm32/exit
1143     cd/syscall  0x80/imm8
1144     # never gets here
1145 
1146 test-get-or-insert-slice:
1147     # . prologue
1148     55/push-ebp
1149     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1150     # var table/ecx: (stream {string, number} 24)  # 2 rows * 12 bytes/row
1151     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
1152     68/push  0x18/imm32/size
1153     68/push  0/imm32/read
1154     68/push  0/imm32/write
1155     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1156     # (eax..edx) = "code"
1157     b8/copy-to-eax  "code"/imm32
1158     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
1159     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
1160     05/add-to-eax  4/imm32
1161     # var slice/edx: slice = {eax, edx}
1162     52/push-edx
1163     50/push-eax
1164     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
1165 $test-get-or-insert-slice:first-call:
1166     # - start with an empty table, insert one key, verify that it was inserted
1167     # eax = get-or-insert-slice(table, "code" slice, 12 bytes/row, Heap)
1168     # . . push args
1169     68/push  Heap/imm32
1170     68/push  0xc/imm32/row-size
1171     52/push-edx
1172     51/push-ecx
1173     # . . call
1174     e8/call  get-or-insert-slice/disp32
1175     # . . discard args
1176     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1177     # check-ints-equal(eax - table->data, 8, msg)  # first row's value slot returned
1178     # . check-ints-equal(eax - table, 20, msg)
1179     # . . push args
1180     68/push  "F - test-get-or-insert-slice/0"/imm32
1181     68/push  0x14/imm32
1182     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1183     50/push-eax
1184     # . . call
1185     e8/call  check-ints-equal/disp32
1186     # . . discard args
1187     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1188     # check-ints-equal(table->write, row-size = 12, msg)
1189     # . . push args
1190     68/push  "F - test-get-or-insert-slice/1"/imm32
1191     68/push  0xc/imm32/row-size
1192     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1193     # . . call
1194     e8/call  check-ints-equal/disp32
1195     # . . discard args
1196     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1197     # var curr-addr/eax: (addr array byte) = lookup(table->data)
1198     # . . push args
1199     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
1200     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
1201     # . . call
1202     e8/call  lookup/disp32
1203     # . . discard args
1204     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1205     # check-strings-equal(curr-addr, "code", msg)
1206     # . . push args
1207     68/push  "F - test-get-or-insert-slice/2"/imm32
1208     68/push  "code"/imm32
1209     50/push-eax
1210     # . . call
1211     e8/call  check-strings-equal/disp32
1212     # . . discard args
1213     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1214 $test-get-or-insert-slice:second-call:
1215     # - insert the same key again, verify that it was reused
1216     # eax = get-or-insert-slice(table, "code" slice, 12 bytes/row)
1217     # . . push args
1218     68/push  Heap/imm32
1219     68/push  0xc/imm32/row-size
1220     52/push-edx
1221     51/push-ecx
1222     # . . call
1223     e8/call  get-or-insert-slice/disp32
1224     # . . discard args
1225     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1226     # check-ints-equal(eax - table->data, 8, msg)
1227     # . check-ints-equal(eax - table, 20, msg)
1228     # . . push args
1229     68/push  "F - test-get-or-insert-slice/3"/imm32
1230     68/push  0x14/imm32
1231     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1232     50/push-eax
1233     # . . call
1234     e8/call  check-ints-equal/disp32
1235     # . . discard args
1236     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1237     # no new row inserted
1238     # . check-ints-equal(table->write, row-size = 12, msg)
1239     # . . push args
1240     68/push  "F - test-get-or-insert-slice/4"/imm32
1241     68/push  0xc/imm32/row-size
1242     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1243     # . . call
1244     e8/call  check-ints-equal/disp32
1245     # . . discard args
1246     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1247     # curr-addr = lookup(table->data)
1248     # . . push args
1249     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
1250     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
1251     # . . call
1252     e8/call  lookup/disp32
1253     # . . discard args
1254     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1255     # check-strings-equal(curr-addr, "code", msg)
1256     # . . push args
1257     68/push  "F - test-get-or-insert-slice/5"/imm32
1258     68/push  "code"/imm32
1259     50/push-eax
1260     # . . call
1261     e8/call  check-strings-equal/disp32
1262     # . . discard args
1263     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1264 $test-get-or-insert-slice:third-call:
1265     # - insert a new key, verify that it was inserted
1266     # (eax..edx) = "data"
1267     b8/copy-to-eax  "data"/imm32
1268     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
1269     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
1270     05/add-to-eax  4/imm32
1271     # var slice/edx: slice = {eax, edx}
1272     52/push-edx
1273     50/push-eax
1274     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
1275     # eax = get-or-insert-slice(table, "data" slice, 12 bytes/row)
1276     # . . push args
1277     68/push  Heap/imm32
1278     68/push  0xc/imm32/row-size
1279     52/push-edx
1280     51/push-ecx
1281     # . . call
1282     e8/call  get-or-insert-slice/disp32
1283     # . . discard args
1284     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1285     # table gets a new row
1286     # check-ints-equal(eax - table->data, 20, msg)  # second row's value slot returned
1287     # . check-ints-equal(eax - table, 32, msg)
1288     # . . push args
1289     68/push  "F - test-get-or-insert-slice/6"/imm32
1290     68/push  0x20/imm32
1291     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1292     50/push-eax
1293     # . . call
1294     e8/call  check-ints-equal/disp32
1295     # . . discard args
1296     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1297     # check-ints-equal(table->write, 2 rows = 24, msg)
1298     # . . push args
1299     68/push  "F - test-get-or-insert-slice/7"/imm32
1300     68/push  0x18/imm32/two-rows
1301     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1302     # . . call
1303     e8/call  check-ints-equal/disp32
1304     # . . discard args
1305     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1306     # curr-addr = lookup(table->data+12)
1307     # . . push args
1308     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x1c/disp8      .                 # push *(ecx+28)
1309     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x18/disp8      .                 # push *(ecx+24)
1310     # . . call
1311     e8/call  lookup/disp32
1312     # . . discard args
1313     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1314     # check-strings-equal(curr-addr, "data", msg)
1315     # . . push args
1316     68/push  "F - test-get-or-insert-slice/8"/imm32
1317     68/push  "data"/imm32
1318     50/push-eax
1319     # . . call
1320     e8/call  check-strings-equal/disp32
1321     # . . discard args
1322     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1323 $test-get-or-insert-slice:end:
1324     # . epilogue
1325     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1326     5d/pop-to-ebp
1327     c3/return
1328 
1329 # if no row is found, stop(ed)
1330 get-or-stop:  # table: (addr stream {(handle array byte), T}), key: (addr array byte), row-size: int,
1331               # abort-message-prefix: (addr array byte), err: (addr buffered-file), ed: (addr exit-descriptor)
1332               # -> eax: (addr T)
1333     # pseudocode:
1334     #   curr = table->data
1335     #   max = &table->data[table->write]
1336     #   while curr < max
1337     #     var c: (addr array byte) = lookup(*curr)
1338     #     if string-equal?(key, c)
1339     #       return curr+8
1340     #     curr += row-size
1341     #   write-buffered(err, msg)
1342     #   stop(ed)
1343     #
1344     # . prologue
1345     55/push-ebp
1346     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1347     # . save registers
1348     51/push-ecx
1349     52/push-edx
1350     56/push-esi
1351     # esi = table
1352     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
1353     # var curr/ecx: (addr handle array byte) = table->data
1354     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
1355     # var max/edx: (addr byte) = &table->data[table->write]
1356     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
1357     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
1358 $get-or-stop:search-loop:
1359     # if (curr >= max) stop(ed)
1360     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1361     73/jump-if-addr>=  $get-or-stop:stop/disp8
1362     # var c/eax: (addr array byte) = lookup(*curr)
1363     # . . push args
1364     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
1365     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1366     # . . call
1367     e8/call  lookup/disp32
1368     # . . discard args
1369     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1370     # if (string-equal?(key, c)) return curr+8
1371     # . eax = string-equal?(key, c)
1372     # . . push args
1373     50/push-eax
1374     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1375     # . . call
1376     e8/call  string-equal?/disp32
1377     # . . discard args
1378     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1379     # . if (eax != false) return eax = curr+8
1380     3d/compare-eax-and  0/imm32/false
1381     74/jump-if-=  $get-or-stop:mismatch/disp8
1382     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
1383     eb/jump  $get-or-stop:end/disp8
1384 $get-or-stop:mismatch:
1385     # curr += row-size
1386     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
1387     # loop
1388     eb/jump  $get-or-stop:search-loop/disp8
1389 $get-or-stop:end:
1390     # . restore registers
1391     5e/pop-to-esi
1392     5a/pop-to-edx
1393     59/pop-to-ecx
1394     # . epilogue
1395     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1396     5d/pop-to-ebp
1397     c3/return
1398 
1399 $get-or-stop:stop:
1400     # . write-buffered(err, abort-message-prefix)
1401     # . . push args
1402     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
1403     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1404     # . . call
1405     e8/call  write-buffered/disp32
1406     # . . discard args
1407     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1408     # . write-buffered(err, error)
1409     # . . push args
1410     68/push  ": get-or-stop: key not found: "/imm32
1411     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1412     # . . call
1413     e8/call  write-buffered/disp32
1414     # . . discard args
1415     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1416     # . write-buffered(err, key)
1417     # . . push args
1418     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1419     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1420     # . . call
1421     e8/call  write-buffered/disp32
1422     # . . discard args
1423     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1424     # . write-buffered(err, "\n")
1425     # . . push args
1426     68/push  Newline/imm32
1427     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1428     # . . call
1429     e8/call  write-buffered/disp32
1430     # . . discard args
1431     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1432     # . stop(ed, 1)
1433     # . . push args
1434     68/push  1/imm32
1435     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x1c/disp8      .                 # push *(ebp+28)
1436     # . . call
1437     e8/call  stop/disp32
1438     # never gets here
1439 $get-or-stop:terminus:
1440     # . . discard args
1441     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1442     # syscall(exit, 1)
1443     b8/copy-to-eax  1/imm32/exit
1444     cd/syscall  0x80/imm8
1445 
1446 test-get-or-stop:
1447     # This test uses exit-descriptors. Use ebp for setting up local variables.
1448     # . prologue
1449     55/push-ebp
1450     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1451     # setup
1452     # . clear-stream(_test-error-stream)
1453     # . . push args
1454     68/push  _test-error-stream/imm32
1455     # . . call
1456     e8/call  clear-stream/disp32
1457     # . . discard args
1458     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1459     # . clear-stream($_test-error-buffered-file->buffer)
1460     # . . push args
1461     68/push  $_test-error-buffered-file->buffer/imm32
1462     # . . call
1463     e8/call  clear-stream/disp32
1464     # . . discard args
1465     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1466     # var table/ecx: (stream {string, number} 24)  # 2 rows * 12 bytes/row
1467     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
1468     68/push  0x18/imm32/size
1469     68/push  0/imm32/read
1470     68/push  0/imm32/write
1471     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1472     # var ed/edx: exit-descriptor
1473     68/push  0/imm32
1474     68/push  0/imm32
1475     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
1476     # size 'ed' for the calls to 'get-or-stop'
1477     # . tailor-exit-descriptor(ed, 24)
1478     # . . push args
1479     68/push  0x18/imm32/nbytes-of-args-for-get-or-stop
1480     52/push-edx
1481     # . . call
1482     e8/call  tailor-exit-descriptor/disp32
1483     # . . discard args
1484     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1485     # insert(table, "code", 12 bytes/row, Heap)
1486     # . . push args
1487     68/push  Heap/imm32
1488     68/push  0xc/imm32/row-size
1489     68/push  "code"/imm32
1490     51/push-ecx
1491     # . . call
1492     e8/call  get-or-insert/disp32
1493     # . . discard args
1494     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1495 $test-get-or-stop:success:
1496     # eax = get-or-stop(table, "code", row-size=12, msg, _test-error-buffered-file, ed)
1497     # . . push args
1498     52/push-edx/ed
1499     68/push  _test-error-buffered-file/imm32
1500     68/push  "foo"/imm32/abort-prefix
1501     68/push  0xc/imm32/row-size
1502     68/push  "code"/imm32
1503     51/push-ecx
1504     # . . call
1505     e8/call  get-or-stop/disp32
1506     # . . discard args
1507     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # add to esp
1508 $test-get-or-stop:success-assertion:
1509     # check-ints-equal(eax - table->data, 8, msg)
1510     # . check-ints-equal(eax - table, 20, msg)
1511     # . . push args
1512     68/push  "F - test-get-or-stop/0"/imm32
1513     68/push  0x14/imm32
1514     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1515     50/push-eax
1516     # . . call
1517     e8/call  check-ints-equal/disp32
1518     # . . discard args
1519     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1520 $test-get-or-stop:failure:
1521     # eax = get-or-stop(table, "data", row-size=12, msg, _test-error-buffered-file, ed)
1522     # . . push args
1523     52/push-edx/ed
1524     68/push  _test-error-buffered-file/imm32
1525     68/push  "foo"/imm32/abort-prefix
1526     68/push  0xc/imm32/row-size
1527     68/push  "data"/imm32
1528     51/push-ecx
1529     # . . call
1530     e8/call  get-or-stop/disp32
1531     # registers except esp may be clobbered at this point
1532     # restore register args, discard others
1533     59/pop-to-ecx
1534     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1535     5a/pop-to-edx
1536 $test-get-or-stop:failure-assertion:
1537     # check that get-or-stop tried to call stop(1)
1538     # . check-ints-equal(ed->value, 2, msg)
1539     # . . push args
1540     68/push  "F - test-get-or-stop/1"/imm32
1541     68/push  2/imm32
1542     # . . push ed->value
1543     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
1544     # . . call
1545     e8/call  check-ints-equal/disp32
1546     # . . discard args
1547     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1548 $test-get-or-stop:end:
1549     # . epilogue
1550     # don't restore esp from ebp; manually reclaim locals
1551     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x2c/imm32        # add to esp
1552     5d/pop-to-ebp
1553     c3/return
1554 
1555 # if no row is found, stop(ed)
1556 get-slice-or-stop:  # table: (addr stream {(handle array byte), _}), key: (addr slice), row-size: int,
1557                     # abort-message-prefix: (addr string), err: (addr buffered-file), ed: (addr exit-descriptor)
1558                     # -> eax: (addr _)
1559     # pseudocode:
1560     #   curr = table->data
1561     #   max = &table->data[table->write]
1562     #   while curr < max
1563     #     var c: (addr array byte) = lookup(*curr)
1564     #     if slice-equal?(key, c)
1565     #       return curr+8
1566     #     curr += row-size
1567     #   write-buffered(err, msg)
1568     #   stop(ed)
1569     #
1570     # . prologue
1571     55/push-ebp
1572     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1573     # . save registers
1574     51/push-ecx
1575     52/push-edx
1576     56/push-esi
1577     # esi = table
1578     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
1579     # var curr/ecx: (addr handle array byte) = table->data
1580     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
1581     # var max/edx: (addr byte) = &table->data[table->write]
1582     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
1583     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
1584 $get-slice-or-stop:search-loop:
1585     # if (curr >= max) stop(ed)
1586     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1587     73/jump-if-addr>=  $get-slice-or-stop:stop/disp8
1588     # var c/eax: (addr array byte) = lookup(*curr)
1589     # . . push args
1590     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
1591     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1592     # . . call
1593     e8/call  lookup/disp32
1594     # . . discard args
1595     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1596     # if (slice-equal?(key, c)) return curr+4
1597     # . eax = slice-equal?(key, c)
1598     # . . push args
1599     50/push-eax
1600     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1601     # . . call
1602     e8/call  slice-equal?/disp32
1603     # . . discard args
1604     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1605     # . if (eax != false) return eax = curr+8
1606     3d/compare-eax-and  0/imm32/false
1607     74/jump-if-=  $get-slice-or-stop:mismatch/disp8
1608     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
1609     eb/jump  $get-slice-or-stop:end/disp8
1610 $get-slice-or-stop:mismatch:
1611     # curr += row-size
1612     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
1613     # loop
1614     eb/jump  $get-slice-or-stop:search-loop/disp8
1615 $get-slice-or-stop:end:
1616     # . restore registers
1617     5e/pop-to-esi
1618     5a/pop-to-edx
1619     59/pop-to-ecx
1620     # . epilogue
1621     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1622     5d/pop-to-ebp
1623     c3/return
1624 
1625 $get-slice-or-stop:stop:
1626     # . write-buffered(err, abort-message-prefix)
1627     # . . push args
1628     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
1629     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1630     # . . call
1631     e8/call  write-buffered/disp32
1632     # . . discard args
1633     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1634     # . write-buffered(err, error)
1635     # . . push args
1636     68/push  ": get-slice-or-stop: key not found: "/imm32
1637     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1638     # . . call
1639     e8/call  write-buffered/disp32
1640     # . . discard args
1641     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1642     # . write-slice-buffered(err, key)
1643     # . . push args
1644     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1645     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1646     # . . call
1647     e8/call  write-slice-buffered/disp32
1648     # . . discard args
1649     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1650     # . write-buffered(err, "\n")
1651     # . . push args
1652     68/push  Newline/imm32
1653     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1654     # . . call
1655     e8/call  write-buffered/disp32
1656     # . . discard args
1657     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1658     # . stop(ed, 1)
1659     # . . push args
1660     68/push  1/imm32
1661     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x1c/disp8      .                 # push *(ebp+28)
1662     # . . call
1663     e8/call  stop/disp32
1664     # never gets here
1665 $get-slice-or-stop:terminus:
1666     # . . discard args
1667     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1668     # syscall(exit, 1)
1669     b8/copy-to-eax  1/imm32/exit
1670     cd/syscall  0x80/imm8
1671 
1672 test-get-slice-or-stop:
1673     # This test uses exit-descriptors. Use ebp for setting up local variables.
1674     # . prologue
1675     55/push-ebp
1676     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1677     # setup
1678     # . clear-stream(_test-error-stream)
1679     # . . push args
1680     68/push  _test-error-stream/imm32
1681     # . . call
1682     e8/call  clear-stream/disp32
1683     # . . discard args
1684     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1685     # . clear-stream($_test-error-buffered-file->buffer)
1686     # . . push args
1687     68/push  $_test-error-buffered-file->buffer/imm32
1688     # . . call
1689     e8/call  clear-stream/disp32
1690     # . . discard args
1691     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1692     # var table/ecx: (stream {string, number} 24)  # 2 rows * 12 bytes/row
1693     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
1694     68/push  0x18/imm32/size
1695     68/push  0/imm32/read
1696     68/push  0/imm32/write
1697     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1698     # var ed/edx: exit-descriptor
1699     68/push  0/imm32
1700     68/push  0/imm32
1701     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
1702     # var slice/ebx: slice = "code"
1703     # . (eax..ebx) = "code"
1704     b8/copy-to-eax  "code"/imm32
1705     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # copy *eax to ebx
1706     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
1707     05/add-to-eax  4/imm32
1708     # . ebx = {eax, ebx}
1709     53/push-ebx
1710     50/push-eax
1711     89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
1712     # size 'ed' for the calls to 'get-or-stop' (define no locals past this point)
1713     # . tailor-exit-descriptor(ed, 24)
1714     # . . push args
1715     68/push  0x18/imm32/nbytes-of-args-for-get-or-stop
1716     52/push-edx
1717     # . . call
1718     e8/call  tailor-exit-descriptor/disp32
1719     # . . discard args
1720     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1721     # insert(table, "code", 12 bytes/row, Heap)
1722     # . . push args
1723     68/push  Heap/imm32
1724     68/push  0xc/imm32/row-size
1725     68/push  "code"/imm32
1726     51/push-ecx
1727     # . . call
1728     e8/call  get-or-insert/disp32
1729     # . . discard args
1730     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1731 $test-get-slice-or-stop:success:
1732     # eax = get-slice-or-stop(table, slice, row-size=12, msg, _test-error-buffered-file, ed)
1733     # . . push args
1734     52/push-edx/ed
1735     68/push  _test-error-buffered-file/imm32
1736     68/push  "foo"/imm32/abort-prefix
1737     68/push  0xc/imm32/row-size
1738     53/push-ebx/slice
1739     51/push-ecx
1740     # . . call
1741     e8/call  get-slice-or-stop/disp32
1742     # registers except esp may be clobbered at this point
1743     # restore register args, discard others
1744     59/pop-to-ecx
1745     5b/pop-to-ebx
1746     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1747     5a/pop-to-edx
1748 $test-get-slice-or-stop:success-assertion:
1749     # check-ints-equal(eax - table->data, 8, msg)  # first row's value slot returned
1750     # . check-ints-equal(eax - table, 20, msg)
1751     # . . push args
1752     68/push  "F - test-get-slice-or-stop/0"/imm32
1753     68/push  0x14/imm32
1754     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1755     50/push-eax
1756     # . . call
1757     e8/call  check-ints-equal/disp32
1758     # . . discard args
1759     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1760 $test-get-slice-or-stop:failure:
1761     # slice = "segment2"
1762     # . *ebx = "segment2"->data
1763     b8/copy-to-eax  "segment2"/imm32
1764     05/add-to-eax  4/imm32
1765     89/copy                         0/mod/indirect  3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # copy eax to *ebx
1766     # . *(ebx+4) = "segment2"->data + len("segment2")
1767     05/add-to-eax  8/imm32/strlen
1768     89/copy                         1/mod/*+disp8   3/rm32/ebx    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(ebx+4)
1769     # eax = get-slice-or-stop(table, slice, row-size=12, msg, _test-error-buffered-file, ed)
1770     # . . push args
1771     52/push-edx/ed
1772     68/push  _test-error-buffered-file/imm32
1773     68/push  "foo"/imm32/abort-prefix
1774     68/push  0xc/imm32/row-size
1775     53/push-ebx/slice
1776     51/push-ecx
1777     # . . call
1778     e8/call  get-slice-or-stop/disp32
1779     # registers except esp may be clobbered at this point
1780     # restore register args, discard others
1781     59/pop-to-ecx
1782     5b/pop-to-ebx
1783     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1784     5a/pop-to-edx
1785 $test-get-slice-or-stop:failure-assertion:
1786     # check that get-or-stop tried to call stop(1)
1787     # . check-ints-equal(ed->value, 2, msg)
1788     # . . push args
1789     68/push  "F - test-get-or-stop/1"/imm32
1790     68/push  2/imm32
1791     # . . push ed->value
1792     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
1793     # . . call
1794     e8/call  check-ints-equal/disp32
1795     # . . discard args
1796     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1797 $test-get-slice-or-stop:end:
1798     # . epilogue
1799     # don't restore esp from ebp; manually reclaim locals
1800     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x34/imm32        # add to esp
1801     5d/pop-to-ebp
1802     c3/return
1803 
1804 # if no row is found, return null (0)
1805 maybe-get:  # table: (addr stream {(handle array byte), T}), key: (addr array byte), row-size: int -> eax: (addr T)
1806     # pseudocode:
1807     #   curr = table->data
1808     #   max = &table->data[table->write]
1809     #   while curr < max
1810     #     var c: (addr array byte) = lookup(*curr)
1811     #     if string-equal?(key, c)
1812     #       return curr+8
1813     #     curr += row-size
1814     #   return 0
1815     #
1816     # . prologue
1817     55/push-ebp
1818     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1819     # . save registers
1820     51/push-ecx
1821     52/push-edx
1822     56/push-esi
1823     # esi = table
1824     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
1825     # var curr/ecx: (addr handle array byte) = table->data
1826     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
1827     # var max/edx: (addr byte) = &table->data[table->write]
1828     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
1829     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
1830 $maybe-get:search-loop:
1831     # if (curr >= max) return null
1832     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1833     73/jump-if-addr>=  $maybe-get:null/disp8
1834     # var c/eax: (addr array byte) = lookup(*curr)
1835     # . . push args
1836     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
1837     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1838     # . . call
1839     e8/call  lookup/disp32
1840     # . . discard args
1841     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1842     # if (string-equal?(key, c)) return curr+4
1843     # . eax = string-equal?(key, c)
1844     # . . push args
1845     50/push-eax
1846     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1847     # . . call
1848     e8/call  string-equal?/disp32
1849     # . . discard args
1850     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1851     # . if (eax != false) return eax = curr+8
1852     3d/compare-eax-and  0/imm32/false
1853     74/jump-if-=  $maybe-get:mismatch/disp8
1854     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
1855     eb/jump  $maybe-get:end/disp8
1856 $maybe-get:mismatch:
1857     # curr += row-size
1858     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
1859     # loop
1860     eb/jump  $maybe-get:search-loop/disp8
1861 $maybe-get:null:
1862     b8/copy-to-eax  0/imm32
1863 $maybe-get:end:
1864     # . restore registers
1865     5e/pop-to-esi
1866     5a/pop-to-edx
1867     59/pop-to-ecx
1868     # . epilogue
1869     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1870     5d/pop-to-ebp
1871     c3/return
1872 
1873 test-maybe-get:
1874     # . prologue
1875     55/push-ebp
1876     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1877     # - setup: create a table with one row
1878     # var table/ecx: (stream {string, number} 24)   # 2 rows * 12 bytes/row
1879     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
1880     68/push  0x18/imm32/size
1881     68/push  0/imm32/read
1882     68/push  0/imm32/write
1883     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1884     # eax = get-or-insert(table, "code", 12 bytes/row, Heap)
1885     # . . push args
1886     68/push  Heap/imm32
1887     68/push  0xc/imm32/row-size
1888     68/push  "code"/imm32
1889     51/push-ecx
1890     # . . call
1891     e8/call  get-or-insert/disp32
1892     # . . discard args
1893     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1894 $test-maybe-get:success:
1895     # - check for the same key, verify that it was reused
1896     # eax = maybe-get(table, "code", 12 bytes/row)
1897     # . . push args
1898     68/push  0xc/imm32/row-size
1899     68/push  "code"/imm32
1900     51/push-ecx
1901     # . . call
1902     e8/call  maybe-get/disp32
1903     # . . discard args
1904     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1905     # check-ints-equal(eax - table->data, 8, msg)
1906     # . check-ints-equal(eax - table, 20, msg)
1907     # . . push args
1908     68/push  "F - test-maybe-get/0"/imm32
1909     68/push  0x14/imm32
1910     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1911     50/push-eax
1912     # . . call
1913     e8/call  check-ints-equal/disp32
1914     # . . discard args
1915     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1916     # no new row inserted
1917     # . check-ints-equal(table->write, row-size = 12, msg)
1918     # . . push args
1919     68/push  "F - test-maybe-get/1"/imm32
1920     68/push  0xc/imm32/row-size
1921     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1922     # . . call
1923     e8/call  check-ints-equal/disp32
1924     # . . discard args
1925     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1926     # var curr-addr/eax: (addr array byte) = lookup(table->data)
1927     # . . push args
1928     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
1929     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
1930     # . . call
1931     e8/call  lookup/disp32
1932     # . . discard args
1933     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1934     # check-strings-equal(curr-addr, "code", msg)
1935     # . . push args
1936     68/push  "F - test-maybe-get/2"/imm32
1937     68/push  "code"/imm32
1938     50/push-eax
1939     # . . call
1940     e8/call  check-strings-equal/disp32
1941     # . . discard args
1942     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1943 $test-maybe-get:failure:
1944     # - search for a new key
1945     # eax = maybe-get(table, "data", 12 bytes/row)
1946     # . . push args
1947     68/push  0xc/imm32/row-size
1948     68/push  "data"/imm32
1949     51/push-ecx
1950     # . . call
1951     e8/call  maybe-get/disp32
1952     # . . discard args
1953     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1954     # check-ints-equal(eax, 0, msg)
1955     # . . push args
1956     68/push  "F - test-maybe-get/3"/imm32
1957     68/push  0/imm32
1958     50/push-eax
1959     # . . call
1960     e8/call  check-ints-equal/disp32
1961     # . . discard args
1962     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1963 $test-maybe-get:end:
1964     # . epilogue
1965     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1966     5d/pop-to-ebp
1967     c3/return
1968 
1969 # if no row is found, return null (0)
1970 maybe-get-slice:  # table: (addr stream {(handle array byte), T}), key: (addr slice), row-size: int -> eax: (addr T)
1971     # pseudocode:
1972     #   curr = table->data
1973     #   max = &table->data[table->write]
1974     #   while curr < max
1975     #     var c: (addr array byte) = lookup(*curr)
1976     #     if slice-equal?(key, c)
1977     #       return curr+8
1978     #     curr += row-size
1979     #   return 0
1980     #
1981     # . prologue
1982     55/push-ebp
1983     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1984     # . save registers
1985     51/push-ecx
1986     52/push-edx
1987     56/push-esi
1988     # esi = table
1989     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
1990     # var curr/ecx: (addr handle array byte) = table->data
1991     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
1992     # var max/edx: (addr byte) = &table->data[table->write]
1993     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
1994     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
1995 $maybe-get-slice:search-loop:
1996     # if (curr >= max) return null
1997     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1998     73/jump-if-addr>=  $maybe-get-slice:null/disp8
1999     # var c/eax: (addr array byte) = lookup(*curr)
2000     # . . push args
2001     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
2002     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
2003     # . . call
2004     e8/call  lookup/disp32
2005     # . . discard args
2006     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
2007     # if (slice-equal?(key, c)) return curr+4
2008     # . eax = slice-equal?(key, c)
2009     # . . push args
2010     50/push-eax
2011     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
2012     # . . call
2013     e8/call  slice-equal?/disp32
2014     # . . discard args
2015     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
2016     # . if (eax != false) return eax = curr+8
2017     3d/compare-eax-and  0/imm32/false
2018     74/jump-if-=  $maybe-get-slice:mismatch/disp8
2019     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
2020     eb/jump  $maybe-get-slice:end/disp8
2021 $maybe-get-slice:mismatch:
2022     # curr += row-size
2023     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
2024     # loop
2025     eb/jump  $maybe-get-slice:search-loop/disp8
2026 $maybe-get-slice:null:
2027     b8/copy-to-eax  0/imm32
2028 $maybe-get-slice:end:
2029     # . restore registers
2030     5e/pop-to-esi
2031     5a/pop-to-edx
2032     59/pop-to-ecx
2033     # . epilogue
2034     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
2035     5d/pop-to-ebp
2036     c3/return
2037 
2038 test-maybe-get-slice:
2039     # . prologue
2040     55/push-ebp
2041     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
2042     # - setup: create a table with one row
2043     # var table/ecx: (stream {string, number} 24)   # 2 rows * 12 bytes/row
2044     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
2045     68/push  0x18/imm32/size
2046     68/push  0/imm32/read
2047     68/push  0/imm32/write
2048     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
2049     # insert(table, "code", 12 bytes/row, Heap)
2050     # . . push args
2051     68/push  Heap/imm32
2052     68/push  0xc/imm32/row-size
2053     68/push  "code"/imm32
2054     51/push-ecx
2055     # . . call
2056     e8/call  get-or-insert/disp32
2057     # . . discard args
2058     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
2059 $test-maybe-get-slice:success:
2060     # - check for the same key, verify that it was reused
2061     # (eax..edx) = "code"
2062     b8/copy-to-eax  "code"/imm32
2063     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
2064     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
2065     05/add-to-eax  4/imm32
2066     # var slice/edx: slice = {eax, edx}
2067     52/push-edx
2068     50/push-eax
2069     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
2070     # eax = maybe-get-slice(table, "code" slice, 12 bytes/row)
2071     # . . push args
2072     68/push  0xc/imm32/row-size
2073     52/push-edx
2074     51/push-ecx
2075     # . . call
2076     e8/call  maybe-get-slice/disp32
2077     # . . discard args
2078     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2079     # check-ints-equal(eax - table->data, 8, msg)
2080     # . check-ints-equal(eax - table, 20, msg)
2081     # . . push args
2082     68/push  "F - test-maybe-get-slice/0"/imm32
2083     68/push  0x14/imm32
2084     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
2085     50/push-eax
2086     # . . call
2087     e8/call  check-ints-equal/disp32
2088     # . . discard args
2089     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2090     # no new row inserted
2091     # . check-ints-equal(table->write, row-size = 12, msg)
2092     # . . push args
2093     68/push  "F - test-maybe-get-slice/1"/imm32
2094     68/push  0xc/imm32/row-size
2095     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
2096     # . . call
2097     e8/call  check-ints-equal/disp32
2098     # . . discard args
2099     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2100     # var curr-addr/eax: (addr array byte) = lookup(table->data)
2101     # . . push args
2102     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
2103     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
2104     # . . call
2105     e8/call  lookup/disp32
2106     # . . discard args
2107     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
2108     # check-strings-equal(curr-addr, "code", msg)
2109     # . . push args
2110     68/push  "F - test-maybe-get-slice/2"/imm32
2111     68/push  "code"/imm32
2112     50/push-eax
2113     # . . call
2114     e8/call  check-strings-equal/disp32
2115     # . . discard args
2116     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2117 $test-maybe-get-slice:failure:
2118     # - search for a new key
2119     # (eax..edx) = "data"
2120     b8/copy-to-eax  "data"/imm32
2121     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
2122     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
2123     05/add-to-eax  4/imm32
2124     # var slice/edx: slice = {eax, edx}
2125     52/push-edx
2126     50/push-eax
2127     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
2128     # eax = maybe-get-slice(table, "data" slice, 12 bytes/row)
2129     # . . push args
2130     68/push  0xc/imm32/row-size
2131     52/push-edx
2132     51/push-ecx
2133     # . . call
2134     e8/call  maybe-get-slice/disp32
2135     # . . discard args
2136     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2137     # check-ints-equal(eax, 0, msg)
2138     # . . push args
2139     68/push  "F - test-maybe-get-slice/3"/imm32
2140     68/push  0/imm32
2141     50/push-eax
2142     # . . call
2143     e8/call  check-ints-equal/disp32
2144     # . . discard args
2145     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2146 $test-maybe-get-slice:end:
2147     # . epilogue
2148     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
2149     5d/pop-to-ebp
2150     c3/return
2151 
2152 # . . vim:nowrap:textwidth=0