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