https://github.com/akkartik/mu/blob/main/linux/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 # save 'key' to the next available row
 704 # if key already exists, abort
 705 # if there are no rows free, abort
 706 # return the address of the value
 707 insert-or-abort:  # table: (addr stream {(handle array byte), T}), key: (addr array byte), row-size: int, ad: (addr allocation-descriptor) -> result/eax: (addr T)
 708     # pseudocode:
 709     #   curr = table->data
 710     #   max = &table->data[table->write]
 711     #   while curr < max
 712     #     var c: (addr array byte) = lookup(*curr)
 713     #     if string-equal?(key, c)
 714     #       abort
 715     #     curr += row-size
 716     #   if table->write >= table->size
 717     #     abort
 718     #   zero-out(max, row-size)
 719     #   copy-array(ad, key, max)
 720     #   table->write += row-size
 721     #   return max+8
 722     #
 723     # . prologue
 724     55/push-ebp
 725     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 726     # . save registers
 727     51/push-ecx
 728     52/push-edx
 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 curr/ecx: (addr handle array byte) = table->data
 733     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
 734     # var max/edx: (addr byte) = &table->data[table->write]
 735     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
 736     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
 737 $insert-or-abort:search-loop:
 738     # if (curr >= max) break
 739     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
 740     73/jump-if-addr>=  $insert-or-abort:not-found/disp8
 741     # var c/eax: (addr array byte) = lookup(*curr)
 742     # . . push args
 743     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
 744     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 745     # . . call
 746     e8/call  lookup/disp32
 747     # . . discard args
 748     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 749     # if (string-equal?(key, c)) abort
 750     # . eax = string-equal?(key, c)
 751     # . . push args
 752     50/push-eax
 753     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 754     # . . call
 755     e8/call  string-equal?/disp32
 756     # . . discard args
 757     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 758     # . if (eax != false) abort
 759     3d/compare-eax-and  0/imm32/false
 760     0f 85/jump-if-!=  $insert-or-abort:error-duplicate/disp32
 761     # curr += row-size
 762     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
 763     # loop
 764     eb/jump  $insert-or-abort:search-loop/disp8
 765 $insert-or-abort:not-found:
 766     # if (table->write >= table->size) abort
 767     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
 768     3b/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   8/disp8         .                 # compare ecx with *(esi+8)
 769     0f 83/jump-if-addr>=  $insert-or-abort:error-full/disp32
 770     # zero-out(max, row-size)
 771     # . . push args
 772     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
 773     52/push-edx
 774     # . . call
 775     e8/call  zero-out/disp32
 776     # . . discard args
 777     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 778     # copy-array(ad, key, max)
 779     # . . push args
 780     52/push-edx
 781     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 782     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
 783     # . . call
 784     e8/call  copy-array/disp32
 785     # . . discard args
 786     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 787     # table->write += row-size
 788     # . eax = row-size
 789     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
 790     # . table->write += eax
 791     01/add                          0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # add eax to *esi
 792     # return max+8
 793     # . eax = max
 794     89/copy                         3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy edx to eax
 795     # . eax += 8
 796     05/add-to-eax  8/imm32
 797 $insert-or-abort:end:
 798     # . restore registers
 799     5e/pop-to-esi
 800     5a/pop-to-edx
 801     59/pop-to-ecx
 802     # . epilogue
 803     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 804     5d/pop-to-ebp
 805     c3/return
 806 
 807 $insert-or-abort:error-duplicate:
 808     # . _write(2/stderr, error)
 809     # . . push args
 810     68/push  "insert-or-abort: key already exists: "/imm32
 811     68/push  2/imm32/stderr
 812     # . . call
 813     e8/call  _write/disp32
 814     # . . discard args
 815     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 816     # . _write(2/stderr, key)
 817     # . . push args
 818     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 819     68/push  2/imm32/stderr
 820     # . . call
 821     e8/call  _write/disp32
 822     # . . discard args
 823     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 824     # . _write(2/stderr, "\n")
 825     # . . push args
 826     68/push  "\n"/imm32
 827     68/push  2/imm32/stderr
 828     # . . call
 829     e8/call  _write/disp32
 830     # . . discard args
 831     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 832     # . syscall_exit(1)
 833     bb/copy-to-ebx  1/imm32
 834     e8/call  syscall_exit/disp32
 835     # never gets here
 836 
 837 $insert-or-abort:error-full:
 838     # . _write(2/stderr, error)
 839     # . . push args
 840     68/push  "insert-or-abort: table is full\n"/imm32
 841     68/push  2/imm32/stderr
 842     # . . call
 843     e8/call  _write/disp32
 844     # . . discard args
 845     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 846     # . syscall_exit(1)
 847     bb/copy-to-ebx  1/imm32
 848     e8/call  syscall_exit/disp32
 849     # never gets here
 850 
 851 # if no row is found, save 'key' to the next available row
 852 # if there are no rows free, abort
 853 # return the address of the value
 854 get-or-insert-handle:  # table: (addr stream {(handle array byte), T}), key: (handle array byte), row-size: int -> result/eax: (addr T)
 855     # pseudocode:
 856     #   var curr: (addr handle stream) = table->data
 857     #   var max: (addr byte) = &table->data[table->write]
 858     #   var k: (addr array byte) = lookup(key)
 859     #   while curr < max
 860     #     var c: (addr array byte) = lookup(*curr)
 861     #     if string-equal?(k, c)
 862     #       return curr+8
 863     #     curr += row-size
 864     #   if table->write >= table->size
 865     #     abort
 866     #   *max = key
 867     #   table->write += row-size
 868     #   return max+8
 869     #
 870     # . prologue
 871     55/push-ebp
 872     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 873     # . save registers
 874     51/push-ecx
 875     52/push-edx
 876     53/push-ebx
 877     56/push-esi
 878     # esi = table
 879     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 880     # var k/ebx: (addr array byte) = lookup(key)
 881     # . eax = lookup(key)
 882     # . . push args
 883     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
 884     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 885     # . . call
 886     e8/call  lookup/disp32
 887     # . . discard args
 888     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 889     # . ebx = eax
 890     89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # copy eax to ebx
 891     # var curr/ecx: (addr handle array byte) = table->data
 892     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
 893     # var max/edx: (addr byte) = &table->data[table->write]
 894     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
 895     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
 896 $get-or-insert-handle:search-loop:
 897     # if (curr >= max) break
 898     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
 899     73/jump-if-addr>=  $get-or-insert-handle:not-found/disp8
 900     # var c/eax: (addr array byte) = lookup(*curr)
 901     # . . push args
 902     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
 903     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 904     # . . call
 905     e8/call  lookup/disp32
 906     # . . discard args
 907     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 908     # if (string-equal?(k, c)) return curr+8
 909     # . eax = string-equal?(k, c)
 910     # . . push args
 911     50/push-eax
 912     53/push-ebx
 913     # . . call
 914     e8/call  string-equal?/disp32
 915     # . . discard args
 916     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 917     # . if (eax != false) return eax = curr+8
 918     3d/compare-eax-and  0/imm32/false
 919     74/jump-if-=  $get-or-insert-handle:mismatch/disp8
 920     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
 921     eb/jump  $get-or-insert-handle:end/disp8
 922 $get-or-insert-handle:mismatch:
 923     # curr += row-size
 924     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x14/disp8      .                 # add *(ebp+20) to ecx
 925     # loop
 926     eb/jump  $get-or-insert-handle:search-loop/disp8
 927 $get-or-insert-handle:not-found:
 928     # if (table->write >= table->size) abort
 929     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
 930     3b/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   8/disp8         .                 # compare ecx with *(esi+8)
 931     73/jump-if-addr>=  $get-or-insert-handle:abort/disp8
 932     # table->write += row-size
 933     # . eax = row-size
 934     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x14/disp8      .                 # copy *(ebp+20) to eax
 935     # . table->write += eax
 936     01/add                          0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # add eax to *esi
 937     # *max = key
 938     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
 939     89/copy                         0/mod/indirect  2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy eax to *edx
 940     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
 941     89/copy                         1/mod/*+disp8   2/rm32/edx    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edx+4)
 942     # return max+8
 943     # . eax = max
 944     89/copy                         3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy edx to eax
 945     # . eax += 8
 946     05/add-to-eax  8/imm32
 947 $get-or-insert-handle:end:
 948     # . restore registers
 949     5e/pop-to-esi
 950     5b/pop-to-ebx
 951     5a/pop-to-edx
 952     59/pop-to-ecx
 953     # . epilogue
 954     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 955     5d/pop-to-ebp
 956     c3/return
 957 
 958 $get-or-insert-handle:abort:
 959     # . _write(2/stderr, error)
 960     # . . push args
 961     68/push  "get-or-insert-handle: table is full\n"/imm32
 962     68/push  2/imm32/stderr
 963     # . . call
 964     e8/call  _write/disp32
 965     # . . discard args
 966     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 967     # . syscall_exit(1)
 968     bb/copy-to-ebx  1/imm32
 969     e8/call  syscall_exit/disp32
 970     # never gets here
 971 
 972 test-get-or-insert-handle:
 973     # . prologue
 974     55/push-ebp
 975     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 976     # var table/ecx: (stream {(handle array byte), number} 24)  # 2 rows * 12 bytes/row
 977     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
 978     68/push  0x18/imm32/size
 979     68/push  0/imm32/read
 980     68/push  0/imm32/write
 981     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
 982     # var h/edx: (handle array byte)
 983     68/push  0/imm32
 984     68/push  0/imm32
 985     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
 986 $test-get-or-insert-handle:first-call:
 987     # - start with an empty table, insert one key, verify that it was inserted
 988     # copy-array(Heap, "code", h)
 989     # . . push args
 990     52/push-edx
 991     68/push  "code"/imm32
 992     68/push  Heap/imm32
 993     # . . call
 994     e8/call  copy-array/disp32
 995     # . . discard args
 996     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 997     # eax = get-or-insert-handle(table, h, 12 bytes/row)
 998     # . . push args
 999     68/push  0xc/imm32/row-size
1000     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
1001     ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
1002     51/push-ecx
1003     # . . call
1004     e8/call  get-or-insert-handle/disp32
1005     # . . discard args
1006     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1007     # check-ints-equal(eax - table->data, 8, msg)  # first row's value slot returned
1008     # . check-ints-equal(eax - table, 20, msg)
1009     # . . push args
1010     68/push  "F - test-get-or-insert-handle/0"/imm32
1011     68/push  0x14/imm32
1012     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1013     50/push-eax
1014     # . . call
1015     e8/call  check-ints-equal/disp32
1016     # . . discard args
1017     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1018     # check-ints-equal(table->write, row-size = 12, msg)
1019     # . . push args
1020     68/push  "F - test-get-or-insert-handle/1"/imm32
1021     68/push  0xc/imm32/row-size
1022     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1023     # . . call
1024     e8/call  check-ints-equal/disp32
1025     # . . discard args
1026     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1027     # var curr-addr/eax: (addr array byte) = lookup(table->data)
1028     # . . push args
1029     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
1030     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
1031     # . . call
1032     e8/call  lookup/disp32
1033     # . . discard args
1034     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1035     # check-strings-equal(curr-addr, "code", msg)
1036     # . . push args
1037     68/push  "F - test-get-or-insert-handle/2"/imm32
1038     68/push  "code"/imm32
1039     50/push-eax
1040     # . . call
1041     e8/call  check-strings-equal/disp32
1042     # . . discard args
1043     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1044 $test-get-or-insert-handle:second-call:
1045     # - insert the same key again, verify that it was reused
1046     # copy-array(Heap, "code", h)
1047     # . . push args
1048     52/push-edx
1049     68/push  "code"/imm32
1050     68/push  Heap/imm32
1051     # . . call
1052     e8/call  copy-array/disp32
1053     # . . discard args
1054     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1055     # eax = get-or-insert-handle(table, h, 12 bytes/row)
1056     # . . push args
1057     68/push  0xc/imm32/row-size
1058     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
1059     ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
1060     51/push-ecx
1061     # . . call
1062     e8/call  get-or-insert-handle/disp32
1063     # . . discard args
1064     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1065     # check-ints-equal(eax - table->data, 8, msg)
1066     # . check-ints-equal(eax - table, 20, msg)
1067     # . . push args
1068     68/push  "F - test-get-or-insert-handle/3"/imm32
1069     68/push  0x14/imm32
1070     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1071     50/push-eax
1072     # . . call
1073     e8/call  check-ints-equal/disp32
1074     # . . discard args
1075     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1076     # no new row inserted
1077     # . check-ints-equal(table->write, row-size = 12, msg)
1078     # . . push args
1079     68/push  "F - test-get-or-insert-handle/4"/imm32
1080     68/push  0xc/imm32/row-size
1081     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1082     # . . call
1083     e8/call  check-ints-equal/disp32
1084     # . . discard args
1085     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1086     # curr-addr = lookup(table->data)
1087     # . . push args
1088     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
1089     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
1090     # . . call
1091     e8/call  lookup/disp32
1092     # . . discard args
1093     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1094     # check-strings-equal(curr-addr, "code", msg)
1095     # . . push args
1096     68/push  "F - test-get-or-insert-handle/5"/imm32
1097     68/push  "code"/imm32
1098     50/push-eax
1099     # . . call
1100     e8/call  check-strings-equal/disp32
1101     # . . discard args
1102     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1103 $test-get-or-insert-handle:third-call:
1104     # - insert a new key, verify that it was inserted
1105     # copy-array(Heap, "data", h)
1106     # . . push args
1107     52/push-edx
1108     68/push  "data"/imm32
1109     68/push  Heap/imm32
1110     # . . call
1111     e8/call  copy-array/disp32
1112     # . . discard args
1113     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1114     # eax = get-or-insert-handle(table, h, 12 bytes/row)
1115     # . . push args
1116     68/push  0xc/imm32/row-size
1117     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
1118     ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
1119     51/push-ecx
1120     # . . call
1121     e8/call  get-or-insert-handle/disp32
1122     # . . discard args
1123     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1124     # table gets a new row
1125     # check-ints-equal(eax - table->data, 20, msg)  # second row's value slot returned
1126     # . check-ints-equal(eax - table, 32, msg)
1127     # . . push args
1128     68/push  "F - test-get-or-insert-handle/6"/imm32
1129     68/push  0x20/imm32
1130     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1131     50/push-eax
1132     # . . call
1133     e8/call  check-ints-equal/disp32
1134     # . . discard args
1135     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1136     # check-ints-equal(table->write, 2 rows = 24, msg)
1137     # . . push args
1138     68/push  "F - test-get-or-insert-handle/7"/imm32
1139     68/push  0x18/imm32/two-rows
1140     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1141     # . . call
1142     e8/call  check-ints-equal/disp32
1143     # . . discard args
1144     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1145     # curr-addr = lookup(table->data+12)
1146     # . . push args
1147     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x1c/disp8      .                 # push *(ecx+28)
1148     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x18/disp8      .                 # push *(ecx+24)
1149     # . . call
1150     e8/call  lookup/disp32
1151     # . . discard args
1152     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1153     # check-strings-equal(curr-addr, "data", msg)
1154     # . . push args
1155     68/push  "F - test-get-or-insert-handle/8"/imm32
1156     68/push  "data"/imm32
1157     50/push-eax
1158     # . . call
1159     e8/call  check-strings-equal/disp32
1160     # . . discard args
1161     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1162 $test-get-or-insert-handle:end:
1163     # . epilogue
1164     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1165     5d/pop-to-ebp
1166     c3/return
1167 
1168 # if no row is found, save 'key' in the next available row
1169 # if there are no rows free, abort
1170 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)
1171     # pseudocode:
1172     #   curr = table->data
1173     #   max = &table->data[table->write]
1174     #   while curr < max
1175     #     var c: (addr array byte) = lookup(*curr)
1176     #     if slice-equal?(key, *curr)
1177     #       return curr+8
1178     #     curr += row-size
1179     #   if table->write >= table->size
1180     #     abort
1181     #   zero-out(max, row-size)
1182     #   slice-to-string(ad, key, max)
1183     #   table->write += row-size
1184     #   return max+8
1185     #
1186     # . prologue
1187     55/push-ebp
1188     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1189     # . save registers
1190     51/push-ecx
1191     52/push-edx
1192     56/push-esi
1193     # esi = table
1194     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
1195     # var curr/ecx: (addr handle array byte) = table->data
1196     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
1197     # var max/edx: (addr byte) = &table->data[table->write]
1198     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
1199     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
1200 $get-or-insert-slice:search-loop:
1201     # if (curr >= max) break
1202     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1203     73/jump-if-addr>=  $get-or-insert-slice:not-found/disp8
1204     # var c/eax: (addr array byte) = lookup(*curr)
1205     # . . push args
1206     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
1207     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1208     # . . call
1209     e8/call  lookup/disp32
1210     # . . discard args
1211     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1212     # if (slice-equal?(key, c)) return curr+4
1213     # . eax = slice-equal?(key, c)
1214     # . . push args
1215     50/push-eax
1216     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1217     # . . call
1218     e8/call  slice-equal?/disp32
1219     # . . discard args
1220     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1221     # . if (eax != false) return eax = curr+8
1222     3d/compare-eax-and  0/imm32/false
1223     74/jump-if-=  $get-or-insert-slice:mismatch/disp8
1224     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
1225     eb/jump  $get-or-insert-slice:end/disp8
1226 $get-or-insert-slice:mismatch:
1227     # curr += row-size
1228     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
1229     # loop
1230     eb/jump  $get-or-insert-slice:search-loop/disp8
1231 $get-or-insert-slice:not-found:
1232     # result/eax = 0
1233     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
1234     # if (table->write >= table->size) abort
1235     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
1236     3b/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   8/disp8         .                 # compare ecx with *(esi+8)
1237     7d/jump-if->=  $get-or-insert-slice:abort/disp8
1238     # zero-out(max, row-size)
1239     # . . push args
1240     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
1241     52/push-edx
1242     # . . call
1243     e8/call  zero-out/disp32
1244     # . . discard args
1245     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1246     # slice-to-string(ad, key, max)
1247     # . . push args
1248     52/push-edx
1249     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1250     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
1251     # . . call
1252     e8/call  slice-to-string/disp32
1253     # . . discard args
1254     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1255     # table->write += row-size
1256     # . eax = row-size
1257     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
1258     # . table->write += eax
1259     01/add                          0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # add eax to *esi
1260     # return max+8
1261     # . eax = max
1262     89/copy                         3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy edx to eax
1263     # . eax += 8
1264     05/add-to-eax  8/imm32
1265 $get-or-insert-slice:end:
1266     # . restore registers
1267     5e/pop-to-esi
1268     5a/pop-to-edx
1269     59/pop-to-ecx
1270     # . epilogue
1271     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1272     5d/pop-to-ebp
1273     c3/return
1274 
1275 $get-or-insert-slice:abort:
1276     # . _write(2/stderr, error)
1277     # . . push args
1278     68/push  "get-or-insert-slice: table is full\n"/imm32
1279     68/push  2/imm32/stderr
1280     # . . call
1281     e8/call  _write/disp32
1282     # . . discard args
1283     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1284     # . syscall_exit(1)
1285     bb/copy-to-ebx  1/imm32
1286     e8/call  syscall_exit/disp32
1287     # never gets here
1288 
1289 test-get-or-insert-slice:
1290     # . prologue
1291     55/push-ebp
1292     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1293     # var table/ecx: (stream {string, number} 24)  # 2 rows * 12 bytes/row
1294     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
1295     68/push  0x18/imm32/size
1296     68/push  0/imm32/read
1297     68/push  0/imm32/write
1298     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1299     # (eax..edx) = "code"
1300     b8/copy-to-eax  "code"/imm32
1301     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
1302     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
1303     05/add-to-eax  4/imm32
1304     # var slice/edx: slice = {eax, edx}
1305     52/push-edx
1306     50/push-eax
1307     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
1308 $test-get-or-insert-slice:first-call:
1309     # - start with an empty table, insert one key, verify that it was inserted
1310     # eax = get-or-insert-slice(table, "code" slice, 12 bytes/row, Heap)
1311     # . . push args
1312     68/push  Heap/imm32
1313     68/push  0xc/imm32/row-size
1314     52/push-edx
1315     51/push-ecx
1316     # . . call
1317     e8/call  get-or-insert-slice/disp32
1318     # . . discard args
1319     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1320     # check-ints-equal(eax - table->data, 8, msg)  # first row's value slot returned
1321     # . check-ints-equal(eax - table, 20, msg)
1322     # . . push args
1323     68/push  "F - test-get-or-insert-slice/0"/imm32
1324     68/push  0x14/imm32
1325     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1326     50/push-eax
1327     # . . call
1328     e8/call  check-ints-equal/disp32
1329     # . . discard args
1330     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1331     # check-ints-equal(table->write, row-size = 12, msg)
1332     # . . push args
1333     68/push  "F - test-get-or-insert-slice/1"/imm32
1334     68/push  0xc/imm32/row-size
1335     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1336     # . . call
1337     e8/call  check-ints-equal/disp32
1338     # . . discard args
1339     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1340     # var curr-addr/eax: (addr array byte) = lookup(table->data)
1341     # . . push args
1342     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
1343     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
1344     # . . call
1345     e8/call  lookup/disp32
1346     # . . discard args
1347     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1348     # check-strings-equal(curr-addr, "code", msg)
1349     # . . push args
1350     68/push  "F - test-get-or-insert-slice/2"/imm32
1351     68/push  "code"/imm32
1352     50/push-eax
1353     # . . call
1354     e8/call  check-strings-equal/disp32
1355     # . . discard args
1356     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1357 $test-get-or-insert-slice:second-call:
1358     # - insert the same key again, verify that it was reused
1359     # eax = get-or-insert-slice(table, "code" slice, 12 bytes/row)
1360     # . . push args
1361     68/push  Heap/imm32
1362     68/push  0xc/imm32/row-size
1363     52/push-edx
1364     51/push-ecx
1365     # . . call
1366     e8/call  get-or-insert-slice/disp32
1367     # . . discard args
1368     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1369     # check-ints-equal(eax - table->data, 8, msg)
1370     # . check-ints-equal(eax - table, 20, msg)
1371     # . . push args
1372     68/push  "F - test-get-or-insert-slice/3"/imm32
1373     68/push  0x14/imm32
1374     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1375     50/push-eax
1376     # . . call
1377     e8/call  check-ints-equal/disp32
1378     # . . discard args
1379     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1380     # no new row inserted
1381     # . check-ints-equal(table->write, row-size = 12, msg)
1382     # . . push args
1383     68/push  "F - test-get-or-insert-slice/4"/imm32
1384     68/push  0xc/imm32/row-size
1385     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1386     # . . call
1387     e8/call  check-ints-equal/disp32
1388     # . . discard args
1389     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1390     # curr-addr = lookup(table->data)
1391     # . . push args
1392     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
1393     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
1394     # . . call
1395     e8/call  lookup/disp32
1396     # . . discard args
1397     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1398     # check-strings-equal(curr-addr, "code", msg)
1399     # . . push args
1400     68/push  "F - test-get-or-insert-slice/5"/imm32
1401     68/push  "code"/imm32
1402     50/push-eax
1403     # . . call
1404     e8/call  check-strings-equal/disp32
1405     # . . discard args
1406     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1407 $test-get-or-insert-slice:third-call:
1408     # - insert a new key, verify that it was inserted
1409     # (eax..edx) = "data"
1410     b8/copy-to-eax  "data"/imm32
1411     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
1412     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
1413     05/add-to-eax  4/imm32
1414     # var slice/edx: slice = {eax, edx}
1415     52/push-edx
1416     50/push-eax
1417     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
1418     # eax = get-or-insert-slice(table, "data" slice, 12 bytes/row)
1419     # . . push args
1420     68/push  Heap/imm32
1421     68/push  0xc/imm32/row-size
1422     52/push-edx
1423     51/push-ecx
1424     # . . call
1425     e8/call  get-or-insert-slice/disp32
1426     # . . discard args
1427     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1428     # table gets a new row
1429     # check-ints-equal(eax - table->data, 20, msg)  # second row's value slot returned
1430     # . check-ints-equal(eax - table, 32, msg)
1431     # . . push args
1432     68/push  "F - test-get-or-insert-slice/6"/imm32
1433     68/push  0x20/imm32
1434     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1435     50/push-eax
1436     # . . call
1437     e8/call  check-ints-equal/disp32
1438     # . . discard args
1439     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1440     # check-ints-equal(table->write, 2 rows = 24, msg)
1441     # . . push args
1442     68/push  "F - test-get-or-insert-slice/7"/imm32
1443     68/push  0x18/imm32/two-rows
1444     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1445     # . . call
1446     e8/call  check-ints-equal/disp32
1447     # . . discard args
1448     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1449     # curr-addr = lookup(table->data+12)
1450     # . . push args
1451     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x1c/disp8      .                 # push *(ecx+28)
1452     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x18/disp8      .                 # push *(ecx+24)
1453     # . . call
1454     e8/call  lookup/disp32
1455     # . . discard args
1456     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1457     # check-strings-equal(curr-addr, "data", msg)
1458     # . . push args
1459     68/push  "F - test-get-or-insert-slice/8"/imm32
1460     68/push  "data"/imm32
1461     50/push-eax
1462     # . . call
1463     e8/call  check-strings-equal/disp32
1464     # . . discard args
1465     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1466 $test-get-or-insert-slice:end:
1467     # . epilogue
1468     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1469     5d/pop-to-ebp
1470     c3/return
1471 
1472 # if no row is found, save 'key' in the next available row
1473 # if there are no rows free, abort
1474 insert-slice-or-abort:  # table: (addr stream {(handle array byte), T}), key: (addr slice), row-size: int, ad: (addr allocation-descriptor) -> result/eax: (addr T)
1475     # pseudocode:
1476     #   curr = table->data
1477     #   max = &table->data[table->write]
1478     #   while curr < max
1479     #     var c: (addr array byte) = lookup(*curr)
1480     #     if slice-equal?(key, *curr)
1481     #       return curr+8
1482     #     curr += row-size
1483     #   if table->write >= table->size
1484     #     abort
1485     #   zero-out(max, row-size)
1486     #   slice-to-string(ad, key, max)
1487     #   table->write += row-size
1488     #   return max+8
1489     #
1490     # . prologue
1491     55/push-ebp
1492     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1493     # . save registers
1494     51/push-ecx
1495     52/push-edx
1496     56/push-esi
1497     # esi = table
1498     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
1499     # var curr/ecx: (addr handle array byte) = table->data
1500     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
1501     # var max/edx: (addr byte) = &table->data[table->write]
1502     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
1503     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
1504 $insert-slice-or-abort:search-loop:
1505     # if (curr >= max) break
1506     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1507     73/jump-if-addr>=  $insert-slice-or-abort:not-found/disp8
1508     # var c/eax: (addr array byte) = lookup(*curr)
1509     # . . push args
1510     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
1511     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1512     # . . call
1513     e8/call  lookup/disp32
1514     # . . discard args
1515     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1516     # if (slice-equal?(key, c)) abort
1517     # . eax = slice-equal?(key, c)
1518     # . . push args
1519     50/push-eax
1520     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1521     # . . call
1522     e8/call  slice-equal?/disp32
1523     # . . discard args
1524     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1525     # . if (eax != false) abort
1526     3d/compare-eax-and  0/imm32/false
1527     0f 85/jump-if-!=  $insert-slice-or-abort:error-duplicate/disp32
1528     # curr += row-size
1529     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
1530     # loop
1531     eb/jump  $insert-slice-or-abort:search-loop/disp8
1532 $insert-slice-or-abort:not-found:
1533     # result/eax = 0
1534     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
1535     # if (table->write >= table->size) abort
1536     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
1537     3b/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   8/disp8         .                 # compare ecx with *(esi+8)
1538     0f 8d/jump-if->=  $insert-slice-or-abort:error-full/disp32
1539     # zero-out(max, row-size)
1540     # . . push args
1541     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
1542     52/push-edx
1543     # . . call
1544     e8/call  zero-out/disp32
1545     # . . discard args
1546     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1547     # slice-to-string(ad, key, max)
1548     # . . push args
1549     52/push-edx
1550     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1551     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
1552     # . . call
1553     e8/call  slice-to-string/disp32
1554     # . . discard args
1555     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1556     # table->write += row-size
1557     # . eax = row-size
1558     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
1559     # . table->write += eax
1560     01/add                          0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # add eax to *esi
1561     # return max+8
1562     # . eax = max
1563     89/copy                         3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy edx to eax
1564     # . eax += 8
1565     05/add-to-eax  8/imm32
1566 $insert-slice-or-abort:end:
1567     # . restore registers
1568     5e/pop-to-esi
1569     5a/pop-to-edx
1570     59/pop-to-ecx
1571     # . epilogue
1572     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1573     5d/pop-to-ebp
1574     c3/return
1575 
1576 $insert-slice-or-abort:error-duplicate:
1577     # . flush(Stderr)
1578     # . . push args
1579     68/push  Stderr/imm32
1580     # . . call
1581     e8/call  flush/disp32
1582     # . . discard args
1583     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1584     # . _write(2/stderr, error)
1585     # . . push args
1586     68/push  "insert-slice-or-abort: key already exists: "/imm32
1587     68/push  2/imm32/stderr
1588     # . . call
1589     e8/call  _write/disp32
1590     # . . discard args
1591     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1592     # . write-slice-buffered(Stderr, key)
1593     # . . push args
1594     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1595     68/push  Stderr/imm32
1596     # . . call
1597     e8/call  write-slice-buffered/disp32
1598     # . . discard args
1599     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1600     # . flush(Stderr)
1601     # . . push args
1602     68/push  Stderr/imm32
1603     # . . call
1604     e8/call  flush/disp32
1605     # . . discard args
1606     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1607     # . _write(2/stderr, "\n")
1608     # . . push args
1609     68/push  "\n"/imm32
1610     68/push  2/imm32/stderr
1611     # . . call
1612     e8/call  _write/disp32
1613     # . . discard args
1614     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1615     # . syscall_exit(1)
1616     bb/copy-to-ebx  1/imm32
1617     e8/call  syscall_exit/disp32
1618     # never gets here
1619 
1620 $insert-slice-or-abort:error-full:
1621     # . _write(2/stderr, error)
1622     # . . push args
1623     68/push  "insert-slice-or-abort: table is full\n"/imm32
1624     68/push  2/imm32/stderr
1625     # . . call
1626     e8/call  _write/disp32
1627     # . . discard args
1628     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1629     # . syscall_exit(1)
1630     bb/copy-to-ebx  1/imm32
1631     e8/call  syscall_exit/disp32
1632     # never gets here
1633 
1634 
1635 # if no row is found, stop(ed)
1636 get-or-stop:  # table: (addr stream {(handle array byte), T}), key: (addr array byte), row-size: int,
1637               # abort-message-prefix: (addr array byte), err: (addr buffered-file), ed: (addr exit-descriptor)
1638               # -> result/eax: (addr T)
1639     # pseudocode:
1640     #   curr = table->data
1641     #   max = &table->data[table->write]
1642     #   while curr < max
1643     #     var c: (addr array byte) = lookup(*curr)
1644     #     if string-equal?(key, c)
1645     #       return curr+8
1646     #     curr += row-size
1647     #   write-buffered(err, msg)
1648     #   stop(ed)
1649     #
1650     # . prologue
1651     55/push-ebp
1652     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1653     # . save registers
1654     51/push-ecx
1655     52/push-edx
1656     56/push-esi
1657     # esi = table
1658     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
1659     # var curr/ecx: (addr handle array byte) = table->data
1660     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
1661     # var max/edx: (addr byte) = &table->data[table->write]
1662     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
1663     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
1664 $get-or-stop:search-loop:
1665     # if (curr >= max) stop(ed)
1666     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1667     73/jump-if-addr>=  $get-or-stop:stop/disp8
1668     # var c/eax: (addr array byte) = lookup(*curr)
1669     # . . push args
1670     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
1671     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1672     # . . call
1673     e8/call  lookup/disp32
1674     # . . discard args
1675     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1676     # if (string-equal?(key, c)) return curr+8
1677     # . eax = string-equal?(key, c)
1678     # . . push args
1679     50/push-eax
1680     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1681     # . . call
1682     e8/call  string-equal?/disp32
1683     # . . discard args
1684     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1685     # . if (eax != false) return eax = curr+8
1686     3d/compare-eax-and  0/imm32/false
1687     74/jump-if-=  $get-or-stop:mismatch/disp8
1688     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
1689     eb/jump  $get-or-stop:end/disp8
1690 $get-or-stop:mismatch:
1691     # curr += row-size
1692     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
1693     # loop
1694     eb/jump  $get-or-stop:search-loop/disp8
1695 $get-or-stop:end:
1696     # . restore registers
1697     5e/pop-to-esi
1698     5a/pop-to-edx
1699     59/pop-to-ecx
1700     # . epilogue
1701     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1702     5d/pop-to-ebp
1703     c3/return
1704 
1705 $get-or-stop:stop:
1706     # . write-buffered(err, abort-message-prefix)
1707     # . . push args
1708     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
1709     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1710     # . . call
1711     e8/call  write-buffered/disp32
1712     # . . discard args
1713     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1714     # . write-buffered(err, error)
1715     # . . push args
1716     68/push  ": get-or-stop: key not found: "/imm32
1717     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1718     # . . call
1719     e8/call  write-buffered/disp32
1720     # . . discard args
1721     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1722     # . write-buffered(err, key)
1723     # . . push args
1724     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1725     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1726     # . . call
1727     e8/call  write-buffered/disp32
1728     # . . discard args
1729     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1730     # . write-buffered(err, "\n")
1731     # . . push args
1732     68/push  Newline/imm32
1733     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1734     # . . call
1735     e8/call  write-buffered/disp32
1736     # . . discard args
1737     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1738     # . stop(ed, 1)
1739     # . . push args
1740     68/push  1/imm32
1741     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x1c/disp8      .                 # push *(ebp+28)
1742     # . . call
1743     e8/call  stop/disp32
1744     # never gets here
1745 $get-or-stop:terminus:
1746     # . . discard args
1747     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1748     # syscall_exit(1)
1749     bb/copy-to-ebx  1/imm32
1750     e8/call  syscall_exit/disp32
1751 
1752 test-get-or-stop:
1753     # This test uses exit-descriptors. Use ebp for setting up local variables.
1754     # . prologue
1755     55/push-ebp
1756     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1757     # setup
1758     # . clear-stream(_test-error-stream)
1759     # . . push args
1760     68/push  _test-error-stream/imm32
1761     # . . call
1762     e8/call  clear-stream/disp32
1763     # . . discard args
1764     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1765     # . clear-stream($_test-error-buffered-file->buffer)
1766     # . . push args
1767     68/push  $_test-error-buffered-file->buffer/imm32
1768     # . . call
1769     e8/call  clear-stream/disp32
1770     # . . discard args
1771     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1772     # var table/ecx: (stream {string, number} 24)  # 2 rows * 12 bytes/row
1773     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
1774     68/push  0x18/imm32/size
1775     68/push  0/imm32/read
1776     68/push  0/imm32/write
1777     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1778     # var ed/edx: exit-descriptor
1779     68/push  0/imm32
1780     68/push  0/imm32
1781     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
1782     # size 'ed' for the calls to 'get-or-stop'
1783     # . tailor-exit-descriptor(ed, 24)
1784     # . . push args
1785     68/push  0x18/imm32/nbytes-of-args-for-get-or-stop
1786     52/push-edx
1787     # . . call
1788     e8/call  tailor-exit-descriptor/disp32
1789     # . . discard args
1790     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1791     # insert(table, "code", 12 bytes/row, Heap)
1792     # . . push args
1793     68/push  Heap/imm32
1794     68/push  0xc/imm32/row-size
1795     68/push  "code"/imm32
1796     51/push-ecx
1797     # . . call
1798     e8/call  get-or-insert/disp32
1799     # . . discard args
1800     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1801 $test-get-or-stop:success:
1802     # eax = get-or-stop(table, "code", row-size=12, msg, _test-error-buffered-file, ed)
1803     # . . push args
1804     52/push-edx/ed
1805     68/push  _test-error-buffered-file/imm32
1806     68/push  "foo"/imm32/abort-prefix
1807     68/push  0xc/imm32/row-size
1808     68/push  "code"/imm32
1809     51/push-ecx
1810     # . . call
1811     e8/call  get-or-stop/disp32
1812     # . . discard args
1813     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # add to esp
1814 $test-get-or-stop:success-assertion:
1815     # check-ints-equal(eax - table->data, 8, msg)
1816     # . check-ints-equal(eax - table, 20, msg)
1817     # . . push args
1818     68/push  "F - test-get-or-stop/0"/imm32
1819     68/push  0x14/imm32
1820     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1821     50/push-eax
1822     # . . call
1823     e8/call  check-ints-equal/disp32
1824     # . . discard args
1825     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1826 $test-get-or-stop:failure:
1827     # eax = get-or-stop(table, "data", row-size=12, msg, _test-error-buffered-file, ed)
1828     # . . push args
1829     52/push-edx/ed
1830     68/push  _test-error-buffered-file/imm32
1831     68/push  "foo"/imm32/abort-prefix
1832     68/push  0xc/imm32/row-size
1833     68/push  "data"/imm32
1834     51/push-ecx
1835     # . . call
1836     e8/call  get-or-stop/disp32
1837     # registers except esp may be clobbered at this point
1838     # restore register args, discard others
1839     59/pop-to-ecx
1840     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
1841     5a/pop-to-edx
1842 $test-get-or-stop:failure-assertion:
1843     # check that get-or-stop tried to call stop(1)
1844     # . check-ints-equal(ed->value, 2, msg)
1845     # . . push args
1846     68/push  "F - test-get-or-stop/1"/imm32
1847     68/push  2/imm32
1848     # . . push ed->value
1849     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
1850     # . . call
1851     e8/call  check-ints-equal/disp32
1852     # . . discard args
1853     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1854 $test-get-or-stop:end:
1855     # . epilogue
1856     # don't restore esp from ebp; manually reclaim locals
1857     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x2c/imm32        # add to esp
1858     5d/pop-to-ebp
1859     c3/return
1860 
1861 # if no row is found, stop(ed)
1862 get-slice-or-stop:  # table: (addr stream {(handle array byte), _}), key: (addr slice), row-size: int,
1863                     # abort-message-prefix: (addr string), err: (addr buffered-file), ed: (addr exit-descriptor)
1864                     # -> result/eax: (addr _)
1865     # pseudocode:
1866     #   curr = table->data
1867     #   max = &table->data[table->write]
1868     #   while curr < max
1869     #     var c: (addr array byte) = lookup(*curr)
1870     #     if slice-equal?(key, c)
1871     #       return curr+8
1872     #     curr += row-size
1873     #   write-buffered(err, msg)
1874     #   stop(ed)
1875     #
1876     # . prologue
1877     55/push-ebp
1878     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1879     # . save registers
1880     51/push-ecx
1881     52/push-edx
1882     56/push-esi
1883     # esi = table
1884     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
1885     # var curr/ecx: (addr handle array byte) = table->data
1886     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
1887     # var max/edx: (addr byte) = &table->data[table->write]
1888     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
1889     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
1890 $get-slice-or-stop:search-loop:
1891     # if (curr >= max) stop(ed)
1892     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1893     73/jump-if-addr>=  $get-slice-or-stop:stop/disp8
1894     # var c/eax: (addr array byte) = lookup(*curr)
1895     # . . push args
1896     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
1897     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
1898     # . . call
1899     e8/call  lookup/disp32
1900     # . . discard args
1901     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1902     # if (slice-equal?(key, c)) return curr+4
1903     # . eax = slice-equal?(key, c)
1904     # . . push args
1905     50/push-eax
1906     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1907     # . . call
1908     e8/call  slice-equal?/disp32
1909     # . . discard args
1910     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1911     # . if (eax != false) return eax = curr+8
1912     3d/compare-eax-and  0/imm32/false
1913     74/jump-if-=  $get-slice-or-stop:mismatch/disp8
1914     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
1915     eb/jump  $get-slice-or-stop:end/disp8
1916 $get-slice-or-stop:mismatch:
1917     # curr += row-size
1918     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
1919     # loop
1920     eb/jump  $get-slice-or-stop:search-loop/disp8
1921 $get-slice-or-stop:end:
1922     # . restore registers
1923     5e/pop-to-esi
1924     5a/pop-to-edx
1925     59/pop-to-ecx
1926     # . epilogue
1927     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1928     5d/pop-to-ebp
1929     c3/return
1930 
1931 $get-slice-or-stop:stop:
1932     # . write-buffered(err, abort-message-prefix)
1933     # . . push args
1934     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
1935     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1936     # . . call
1937     e8/call  write-buffered/disp32
1938     # . . discard args
1939     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1940     # . write-buffered(err, error)
1941     # . . push args
1942     68/push  ": get-slice-or-stop: key not found: "/imm32
1943     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1944     # . . call
1945     e8/call  write-buffered/disp32
1946     # . . discard args
1947     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1948     # . write-slice-buffered(err, key)
1949     # . . push args
1950     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
1951     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1952     # . . call
1953     e8/call  write-slice-buffered/disp32
1954     # . . discard args
1955     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1956     # . write-buffered(err, "\n")
1957     # . . push args
1958     68/push  Newline/imm32
1959     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
1960     # . . call
1961     e8/call  write-buffered/disp32
1962     # . . discard args
1963     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1964     # . stop(ed, 1)
1965     # . . push args
1966     68/push  1/imm32
1967     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x1c/disp8      .                 # push *(ebp+28)
1968     # . . call
1969     e8/call  stop/disp32
1970     # never gets here
1971 $get-slice-or-stop:terminus:
1972     # . . discard args
1973     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1974     # syscall_exit(1)
1975     bb/copy-to-ebx  1/imm32
1976     e8/call  syscall_exit/disp32
1977 
1978 test-get-slice-or-stop:
1979     # This test uses exit-descriptors. Use ebp for setting up local variables.
1980     # . prologue
1981     55/push-ebp
1982     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1983     # setup
1984     # . clear-stream(_test-error-stream)
1985     # . . push args
1986     68/push  _test-error-stream/imm32
1987     # . . call
1988     e8/call  clear-stream/disp32
1989     # . . discard args
1990     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1991     # . clear-stream($_test-error-buffered-file->buffer)
1992     # . . push args
1993     68/push  $_test-error-buffered-file->buffer/imm32
1994     # . . call
1995     e8/call  clear-stream/disp32
1996     # . . discard args
1997     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1998     # var table/ecx: (stream {string, number} 24)  # 2 rows * 12 bytes/row
1999     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
2000     68/push  0x18/imm32/size
2001     68/push  0/imm32/read
2002     68/push  0/imm32/write
2003     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
2004     # var ed/edx: exit-descriptor
2005     68/push  0/imm32
2006     68/push  0/imm32
2007     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
2008     # var slice/ebx: slice = "code"
2009     # . (eax..ebx) = "code"
2010     b8/copy-to-eax  "code"/imm32
2011     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # copy *eax to ebx
2012     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
2013     05/add-to-eax  4/imm32
2014     # . ebx = {eax, ebx}
2015     53/push-ebx
2016     50/push-eax
2017     89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
2018     # size 'ed' for the calls to 'get-or-stop' (define no locals past this point)
2019     # . tailor-exit-descriptor(ed, 24)
2020     # . . push args
2021     68/push  0x18/imm32/nbytes-of-args-for-get-or-stop
2022     52/push-edx
2023     # . . call
2024     e8/call  tailor-exit-descriptor/disp32
2025     # . . discard args
2026     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
2027     # insert(table, "code", 12 bytes/row, Heap)
2028     # . . push args
2029     68/push  Heap/imm32
2030     68/push  0xc/imm32/row-size
2031     68/push  "code"/imm32
2032     51/push-ecx
2033     # . . call
2034     e8/call  get-or-insert/disp32
2035     # . . discard args
2036     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
2037 $test-get-slice-or-stop:success:
2038     # eax = get-slice-or-stop(table, slice, row-size=12, msg, _test-error-buffered-file, ed)
2039     # . . push args
2040     52/push-edx/ed
2041     68/push  _test-error-buffered-file/imm32
2042     68/push  "foo"/imm32/abort-prefix
2043     68/push  0xc/imm32/row-size
2044     53/push-ebx/slice
2045     51/push-ecx
2046     # . . call
2047     e8/call  get-slice-or-stop/disp32
2048     # registers except esp may be clobbered at this point
2049     # restore register args, discard others
2050     59/pop-to-ecx
2051     5b/pop-to-ebx
2052     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2053     5a/pop-to-edx
2054 $test-get-slice-or-stop:success-assertion:
2055     # check-ints-equal(eax - table->data, 8, msg)  # first row's value slot returned
2056     # . check-ints-equal(eax - table, 20, msg)
2057     # . . push args
2058     68/push  "F - test-get-slice-or-stop/0"/imm32
2059     68/push  0x14/imm32
2060     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
2061     50/push-eax
2062     # . . call
2063     e8/call  check-ints-equal/disp32
2064     # . . discard args
2065     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2066 $test-get-slice-or-stop:failure:
2067     # slice = "segment2"
2068     # . *ebx = "segment2"->data
2069     b8/copy-to-eax  "segment2"/imm32
2070     05/add-to-eax  4/imm32
2071     89/copy                         0/mod/indirect  3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # copy eax to *ebx
2072     # . *(ebx+4) = "segment2"->data + len("segment2")
2073     05/add-to-eax  8/imm32/strlen
2074     89/copy                         1/mod/*+disp8   3/rm32/ebx    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(ebx+4)
2075     # eax = get-slice-or-stop(table, slice, row-size=12, msg, _test-error-buffered-file, ed)
2076     # . . push args
2077     52/push-edx/ed
2078     68/push  _test-error-buffered-file/imm32
2079     68/push  "foo"/imm32/abort-prefix
2080     68/push  0xc/imm32/row-size
2081     53/push-ebx/slice
2082     51/push-ecx
2083     # . . call
2084     e8/call  get-slice-or-stop/disp32
2085     # registers except esp may be clobbered at this point
2086     # restore register args, discard others
2087     59/pop-to-ecx
2088     5b/pop-to-ebx
2089     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2090     5a/pop-to-edx
2091 $test-get-slice-or-stop:failure-assertion:
2092     # check that get-or-stop tried to call stop(1)
2093     # . check-ints-equal(ed->value, 2, msg)
2094     # . . push args
2095     68/push  "F - test-get-or-stop/1"/imm32
2096     68/push  2/imm32
2097     # . . push ed->value
2098     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
2099     # . . call
2100     e8/call  check-ints-equal/disp32
2101     # . . discard args
2102     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2103 $test-get-slice-or-stop:end:
2104     # . epilogue
2105     # don't restore esp from ebp; manually reclaim locals
2106     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x34/imm32        # add to esp
2107     5d/pop-to-ebp
2108     c3/return
2109 
2110 # if no row is found, return null (0)
2111 maybe-get:  # table: (addr stream {(handle array byte), T}), key: (addr array byte), row-size: int -> result/eax: (addr T)
2112     # pseudocode:
2113     #   curr = table->data
2114     #   max = &table->data[table->write]
2115     #   while curr < max
2116     #     var c: (addr array byte) = lookup(*curr)
2117     #     if string-equal?(key, c)
2118     #       return curr+8
2119     #     curr += row-size
2120     #   return 0
2121     #
2122     # . prologue
2123     55/push-ebp
2124     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
2125     # . save registers
2126     51/push-ecx
2127     52/push-edx
2128     56/push-esi
2129     # esi = table
2130     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
2131     # var curr/ecx: (addr handle array byte) = table->data
2132     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
2133     # var max/edx: (addr byte) = &table->data[table->write]
2134     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
2135     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
2136 $maybe-get:search-loop:
2137     # if (curr >= max) return null
2138     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
2139     73/jump-if-addr>=  $maybe-get:null/disp8
2140     # var c/eax: (addr array byte) = lookup(*curr)
2141     # . . push args
2142     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
2143     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
2144     # . . call
2145     e8/call  lookup/disp32
2146     # . . discard args
2147     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
2148     # if (string-equal?(key, c)) return curr+4
2149     # . eax = string-equal?(key, c)
2150     # . . push args
2151     50/push-eax
2152     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
2153     # . . call
2154     e8/call  string-equal?/disp32
2155     # . . discard args
2156     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
2157     # . if (eax != false) return eax = curr+8
2158     3d/compare-eax-and  0/imm32/false
2159     74/jump-if-=  $maybe-get:mismatch/disp8
2160     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
2161     eb/jump  $maybe-get:end/disp8
2162 $maybe-get:mismatch:
2163     # curr += row-size
2164     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
2165     # loop
2166     eb/jump  $maybe-get:search-loop/disp8
2167 $maybe-get:null:
2168     b8/copy-to-eax  0/imm32
2169 $maybe-get:end:
2170     # . restore registers
2171     5e/pop-to-esi
2172     5a/pop-to-edx
2173     59/pop-to-ecx
2174     # . epilogue
2175     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
2176     5d/pop-to-ebp
2177     c3/return
2178 
2179 test-maybe-get:
2180     # . prologue
2181     55/push-ebp
2182     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
2183     # - setup: create a table with one row
2184     # var table/ecx: (stream {string, number} 24)   # 2 rows * 12 bytes/row
2185     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
2186     68/push  0x18/imm32/size
2187     68/push  0/imm32/read
2188     68/push  0/imm32/write
2189     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
2190     # eax = get-or-insert(table, "code", 12 bytes/row, Heap)
2191     # . . push args
2192     68/push  Heap/imm32
2193     68/push  0xc/imm32/row-size
2194     68/push  "code"/imm32
2195     51/push-ecx
2196     # . . call
2197     e8/call  get-or-insert/disp32
2198     # . . discard args
2199     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
2200 $test-maybe-get:success:
2201     # - check for the same key, verify that it was reused
2202     # eax = maybe-get(table, "code", 12 bytes/row)
2203     # . . push args
2204     68/push  0xc/imm32/row-size
2205     68/push  "code"/imm32
2206     51/push-ecx
2207     # . . call
2208     e8/call  maybe-get/disp32
2209     # . . discard args
2210     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2211     # check-ints-equal(eax - table->data, 8, msg)
2212     # . check-ints-equal(eax - table, 20, msg)
2213     # . . push args
2214     68/push  "F - test-maybe-get/0"/imm32
2215     68/push  0x14/imm32
2216     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
2217     50/push-eax
2218     # . . call
2219     e8/call  check-ints-equal/disp32
2220     # . . discard args
2221     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2222     # no new row inserted
2223     # . check-ints-equal(table->write, row-size = 12, msg)
2224     # . . push args
2225     68/push  "F - test-maybe-get/1"/imm32
2226     68/push  0xc/imm32/row-size
2227     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
2228     # . . call
2229     e8/call  check-ints-equal/disp32
2230     # . . discard args
2231     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2232     # var curr-addr/eax: (addr array byte) = lookup(table->data)
2233     # . . push args
2234     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
2235     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
2236     # . . call
2237     e8/call  lookup/disp32
2238     # . . discard args
2239     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
2240     # check-strings-equal(curr-addr, "code", msg)
2241     # . . push args
2242     68/push  "F - test-maybe-get/2"/imm32
2243     68/push  "code"/imm32
2244     50/push-eax
2245     # . . call
2246     e8/call  check-strings-equal/disp32
2247     # . . discard args
2248     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2249 $test-maybe-get:failure:
2250     # - search for a new key
2251     # eax = maybe-get(table, "data", 12 bytes/row)
2252     # . . push args
2253     68/push  0xc/imm32/row-size
2254     68/push  "data"/imm32
2255     51/push-ecx
2256     # . . call
2257     e8/call  maybe-get/disp32
2258     # . . discard args
2259     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2260     # check-ints-equal(eax, 0, msg)
2261     # . . push args
2262     68/push  "F - test-maybe-get/3"/imm32
2263     68/push  0/imm32
2264     50/push-eax
2265     # . . call
2266     e8/call  check-ints-equal/disp32
2267     # . . discard args
2268     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2269 $test-maybe-get:end:
2270     # . epilogue
2271     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
2272     5d/pop-to-ebp
2273     c3/return
2274 
2275 # if no row is found, return null (0)
2276 maybe-get-slice:  # table: (addr stream {(handle array byte), T}), key: (addr slice), row-size: int -> result/eax: (addr T)
2277     # pseudocode:
2278     #   curr = table->data
2279     #   max = &table->data[table->write]
2280     #   while curr < max
2281     #     var c: (addr array byte) = lookup(*curr)
2282     #     if slice-equal?(key, c)
2283     #       return curr+8
2284     #     curr += row-size
2285     #   return 0
2286     #
2287     # . prologue
2288     55/push-ebp
2289     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
2290     # . save registers
2291     51/push-ecx
2292     52/push-edx
2293     56/push-esi
2294     # esi = table
2295     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
2296     # var curr/ecx: (addr handle array byte) = table->data
2297     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
2298     # var max/edx: (addr byte) = &table->data[table->write]
2299     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
2300     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
2301 $maybe-get-slice:search-loop:
2302     # if (curr >= max) return null
2303     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
2304     73/jump-if-addr>=  $maybe-get-slice:null/disp8
2305     # var c/eax: (addr array byte) = lookup(*curr)
2306     # . . push args
2307     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
2308     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
2309     # . . call
2310     e8/call  lookup/disp32
2311     # . . discard args
2312     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
2313     # if (slice-equal?(key, c)) return curr+4
2314     # . eax = slice-equal?(key, c)
2315     # . . push args
2316     50/push-eax
2317     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
2318     # . . call
2319     e8/call  slice-equal?/disp32
2320     # . . discard args
2321     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
2322     # . if (eax != false) return eax = curr+8
2323     3d/compare-eax-and  0/imm32/false
2324     74/jump-if-=  $maybe-get-slice:mismatch/disp8
2325     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   8/disp8         .                 # copy ecx+8 to eax
2326     eb/jump  $maybe-get-slice:end/disp8
2327 $maybe-get-slice:mismatch:
2328     # curr += row-size
2329     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
2330     # loop
2331     eb/jump  $maybe-get-slice:search-loop/disp8
2332 $maybe-get-slice:null:
2333     b8/copy-to-eax  0/imm32
2334 $maybe-get-slice:end:
2335     # . restore registers
2336     5e/pop-to-esi
2337     5a/pop-to-edx
2338     59/pop-to-ecx
2339     # . epilogue
2340     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
2341     5d/pop-to-ebp
2342     c3/return
2343 
2344 test-maybe-get-slice:
2345     # . prologue
2346     55/push-ebp
2347     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
2348     # - setup: create a table with one row
2349     # var table/ecx: (stream {string, number} 24)   # 2 rows * 12 bytes/row
2350     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
2351     68/push  0x18/imm32/size
2352     68/push  0/imm32/read
2353     68/push  0/imm32/write
2354     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
2355     # insert(table, "code", 12 bytes/row, Heap)
2356     # . . push args
2357     68/push  Heap/imm32
2358     68/push  0xc/imm32/row-size
2359     68/push  "code"/imm32
2360     51/push-ecx
2361     # . . call
2362     e8/call  get-or-insert/disp32
2363     # . . discard args
2364     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
2365 $test-maybe-get-slice:success:
2366     # - check for the same key, verify that it was reused
2367     # (eax..edx) = "code"
2368     b8/copy-to-eax  "code"/imm32
2369     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
2370     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
2371     05/add-to-eax  4/imm32
2372     # var slice/edx: slice = {eax, edx}
2373     52/push-edx
2374     50/push-eax
2375     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
2376     # eax = maybe-get-slice(table, "code" slice, 12 bytes/row)
2377     # . . push args
2378     68/push  0xc/imm32/row-size
2379     52/push-edx
2380     51/push-ecx
2381     # . . call
2382     e8/call  maybe-get-slice/disp32
2383     # . . discard args
2384     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2385     # check-ints-equal(eax - table->data, 8, msg)
2386     # . check-ints-equal(eax - table, 20, msg)
2387     # . . push args
2388     68/push  "F - test-maybe-get-slice/0"/imm32
2389     68/push  0x14/imm32
2390     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
2391     50/push-eax
2392     # . . call
2393     e8/call  check-ints-equal/disp32
2394     # . . discard args
2395     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2396     # no new row inserted
2397     # . check-ints-equal(table->write, row-size = 12, msg)
2398     # . . push args
2399     68/push  "F - test-maybe-get-slice/1"/imm32
2400     68/push  0xc/imm32/row-size
2401     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
2402     # . . call
2403     e8/call  check-ints-equal/disp32
2404     # . . discard args
2405     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2406     # var curr-addr/eax: (addr array byte) = lookup(table->data)
2407     # . . push args
2408     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x10/disp8      .                 # push *(ecx+16)
2409     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
2410     # . . call
2411     e8/call  lookup/disp32
2412     # . . discard args
2413     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
2414     # check-strings-equal(curr-addr, "code", msg)
2415     # . . push args
2416     68/push  "F - test-maybe-get-slice/2"/imm32
2417     68/push  "code"/imm32
2418     50/push-eax
2419     # . . call
2420     e8/call  check-strings-equal/disp32
2421     # . . discard args
2422     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2423 $test-maybe-get-slice:failure:
2424     # - search for a new key
2425     # (eax..edx) = "data"
2426     b8/copy-to-eax  "data"/imm32
2427     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
2428     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
2429     05/add-to-eax  4/imm32
2430     # var slice/edx: slice = {eax, edx}
2431     52/push-edx
2432     50/push-eax
2433     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
2434     # eax = maybe-get-slice(table, "data" slice, 12 bytes/row)
2435     # . . push args
2436     68/push  0xc/imm32/row-size
2437     52/push-edx
2438     51/push-ecx
2439     # . . call
2440     e8/call  maybe-get-slice/disp32
2441     # . . discard args
2442     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2443     # check-ints-equal(eax, 0, msg)
2444     # . . push args
2445     68/push  "F - test-maybe-get-slice/3"/imm32
2446     68/push  0/imm32
2447     50/push-eax
2448     # . . call
2449     e8/call  check-ints-equal/disp32
2450     # . . discard args
2451     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
2452 $test-maybe-get-slice:end:
2453     # . epilogue
2454     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
2455     5d/pop-to-ebp
2456     c3/return
2457 
2458 # . . vim:nowrap:textwidth=0