https://github.com/akkartik/mu/blob/master/081table.subx
   1 # A table is a stream of (key, value) rows.
   2 #
   3 # Each row consists of a 4-byte key -- a 'string_key' which is (addr array
   4 # byte) -- and a variable-size value.
   5 #
   6 # Accessing the table performs a linear scan for a key string, and always
   7 # requires passing in the row size.
   8 #
   9 # Table primitives have the form <variant>(stream, <arg>, row-size, ...) -> address/eax
  10 #
  11 # The following table shows available options for <variant>:
  12 #   if not found:           | arg=string              arg=slice
  13 #   ------------------------+---------------------------------------------------
  14 #   abort                   | get                     get-slice
  15 #   insert key              | get-or-insert           leaky-get-or-insert-slice
  16 #   stop                    | get-or-stop             get-slice-or-stop
  17 #   return null             | maybe-get               maybe-get-slice
  18 # Some variants may take extra args.
  19 
  20 == code
  21 #   instruction                     effective address                                                   register    displacement    immediate
  22 # . op          subop               mod             rm32          base        index         scale       r32
  23 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
  24 
  25 # if no row is found, abort
  26 # type string_key = (addr array byte)
  27 get:  # table: (addr stream {string_key, T}), key: string_key, row-size: int, abort-message-prefix: (addr array byte) -> eax: (addr T)
  28     # pseudocode:
  29     #   curr = table->data
  30     #   max = &table->data[table->write]
  31     #   while curr < max
  32     #     if string-equal?(key, *curr)
  33     #       return curr+4
  34     #     curr += row-size
  35     #   abort
  36     #
  37     # . prologue
  38     55/push-ebp
  39     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
  40     # . save registers
  41     51/push-ecx
  42     52/push-edx
  43     56/push-esi
  44     # esi = table
  45     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
  46     # var curr/ecx: (addr string_key) = table->data
  47     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
  48     # var max/edx: (addr byte) = &table->data[table->write]
  49     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
  50     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
  51 $get:search-loop:
  52     # if (curr >= max) abort
  53     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
  54     73/jump-if-addr>=  $get:abort/disp8
  55     # if (string-equal?(key, *curr)) return curr+4
  56     # . eax = string-equal?(key, *curr)
  57     # . . push args
  58     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
  59     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
  60     # . . call
  61     e8/call  string-equal?/disp32
  62     # . . discard args
  63     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
  64     # . if (eax != false) return eax = curr+4
  65     3d/compare-eax-and  0/imm32/false
  66     74/jump-if-=  $get:mismatch/disp8
  67     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy ecx+4 to eax
  68     eb/jump  $get:end/disp8
  69 $get:mismatch:
  70     # curr += row-size
  71     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
  72     # loop
  73     eb/jump  $get:search-loop/disp8
  74 $get:end:
  75     # . restore registers
  76     5e/pop-to-esi
  77     5a/pop-to-edx
  78     59/pop-to-ecx
  79     # . epilogue
  80     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
  81     5d/pop-to-ebp
  82     c3/return
  83 
  84 $get:abort:
  85     # . _write(2/stderr, abort-message-prefix)
  86     # . . push args
  87     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
  88     68/push  2/imm32/stderr
  89     # . . call
  90     e8/call  _write/disp32
  91     # . . discard args
  92     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
  93     # . _write(2/stderr, error)
  94     # . . push args
  95     68/push  ": get: key not found: "/imm32
  96     68/push  2/imm32/stderr
  97     # . . call
  98     e8/call  _write/disp32
  99     # . . discard args
 100     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 101     # . _write(2/stderr, key)
 102     # . . push args
 103     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 104     68/push  2/imm32/stderr
 105     # . . call
 106     e8/call  _write/disp32
 107     # . . discard args
 108     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 109     # . _write(2/stderr, "\n")
 110     # . . push args
 111     68/push  Newline/imm32
 112     68/push  2/imm32/stderr
 113     # . . call
 114     e8/call  _write/disp32
 115     # . . discard args
 116     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 117     # . syscall(exit, 1)
 118     bb/copy-to-ebx  1/imm32
 119     b8/copy-to-eax  1/imm32/exit
 120     cd/syscall  0x80/imm8
 121     # never gets here
 122 
 123 test-get:
 124     # . prologue
 125     55/push-ebp
 126     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 127     # - setup: create a table with a couple of keys
 128     # var table/ecx: (stream {string, number} 16)  # 2 rows * 8 bytes/row
 129     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # subtract from esp
 130     68/push  0x10/imm32/length
 131     68/push  0/imm32/read
 132     68/push  0/imm32/write
 133     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
 134     # insert(table, "code", 8 bytes per row)
 135     # . . push args
 136     68/push  8/imm32/row-size
 137     68/push  "code"/imm32
 138     51/push-ecx
 139     # . . call
 140     e8/call  get-or-insert/disp32
 141     # . . discard args
 142     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 143     # insert(table, "data", 8 bytes per row)
 144     # . . push args
 145     68/push  8/imm32/row-size
 146     68/push  "data"/imm32
 147     51/push-ecx
 148     # . . call
 149     e8/call  get-or-insert/disp32
 150     # . . discard args
 151     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 152 $test-get:check1:
 153     # eax = get(table, "code", 8 bytes per row)
 154     # . . push args
 155     68/push  8/imm32/row-size
 156     68/push  "code"/imm32
 157     51/push-ecx
 158     # . . call
 159     e8/call  get/disp32
 160     # . . discard args
 161     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 162     # check-ints-equal(eax - table->data, 4, msg)
 163     # . check-ints-equal(eax - table, 16, msg)
 164     # . . push args
 165     68/push  "F - test-get/0"/imm32
 166     68/push  0x10/imm32
 167     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 168     50/push-eax
 169     # . . call
 170     e8/call  check-ints-equal/disp32
 171     # . . discard args
 172     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 173 $test-get:check2:
 174     # eax = get(table, "data", 8 bytes per row)
 175     # . . push args
 176     68/push  8/imm32/row-size
 177     68/push  "data"/imm32
 178     51/push-ecx
 179     # . . call
 180     e8/call  get/disp32
 181     # . . discard args
 182     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 183     # check-ints-equal(eax - table->data, 12, msg)
 184     # . check-ints-equal(eax - table, 24, msg)
 185     # . . push args
 186     68/push  "F - test-get/1"/imm32
 187     68/push  0x18/imm32
 188     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 189     50/push-eax
 190     # . . call
 191     e8/call  check-ints-equal/disp32
 192     # . . discard args
 193     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 194 $test-get:end:
 195     # . epilogue
 196     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 197     5d/pop-to-ebp
 198     c3/return
 199 
 200 # if no row is found, abort
 201 get-slice:  # table: (addr stream {string_key, T}), key: (addr slice), row-size: int, abort-message-prefix: (addr array byte) -> eax: (addr T)
 202     # pseudocode:
 203     #   curr = table->data
 204     #   max = &table->data[table->write]
 205     #   while curr < max
 206     #     if slice-equal?(key, *curr)
 207     #       return curr+4
 208     #     curr += row-size
 209     #   abort
 210     #
 211     # . prologue
 212     55/push-ebp
 213     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 214     # . save registers
 215     51/push-ecx
 216     52/push-edx
 217     56/push-esi
 218     # esi = table
 219     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 220     # var curr/ecx: (addr string_key) = table->data
 221     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
 222     # var max/edx: (addr byte) = &table->data[table->write]
 223     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
 224     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
 225 $get-slice:search-loop:
 226     # if (curr >= max) abort
 227     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
 228     73/jump-if-addr>=  $get-slice:abort/disp8
 229     # if (slice-equal?(key, *curr)) return curr+4
 230     # . eax = slice-equal?(key, *curr)
 231     # . . push args
 232     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 233     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 234     # . . call
 235     e8/call  slice-equal?/disp32
 236     # . . discard args
 237     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 238     # . if (eax != false) return eax = curr+4
 239     3d/compare-eax-and  0/imm32/false
 240     74/jump-if-=  $get-slice:mismatch/disp8
 241     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy ecx+4 to eax
 242     eb/jump  $get-slice:end/disp8
 243 $get-slice:mismatch:
 244     # curr += row-size
 245     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
 246     # loop
 247     eb/jump  $get-slice:search-loop/disp8
 248 $get-slice:end:
 249     # . restore registers
 250     5e/pop-to-esi
 251     5a/pop-to-edx
 252     59/pop-to-ecx
 253     # . epilogue
 254     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 255     5d/pop-to-ebp
 256     c3/return
 257 
 258 $get-slice:abort:
 259     # . _write(2/stderr, abort-message-prefix)
 260     # . . push args
 261     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
 262     68/push  2/imm32/stderr
 263     # . . call
 264     e8/call  _write/disp32
 265     # . . discard args
 266     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 267     # . _write(2/stderr, error)
 268     # . . push args
 269     68/push  ": get-slice: key not found: "/imm32
 270     68/push  2/imm32/stderr
 271     # . . call
 272     e8/call  _write/disp32
 273     # . . discard args
 274     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 275     # . write-slice-buffered(Stderr, key)
 276     # . . push args
 277     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 278     68/push  Stderr/imm32
 279     # . . call
 280     e8/call  write-slice-buffered/disp32
 281     # . . discard args
 282     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 283     # . flush(Stderr)
 284     # . . push args
 285     68/push  Stderr/imm32
 286     # . . call
 287     e8/call  flush/disp32
 288     # . . discard args
 289     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 290     # . _write(2/stderr, "\n")
 291     # . . push args
 292     68/push  Newline/imm32
 293     68/push  2/imm32/stderr
 294     # . . call
 295     e8/call  _write/disp32
 296     # . . discard args
 297     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 298     # . syscall(exit, 1)
 299     bb/copy-to-ebx  1/imm32
 300     b8/copy-to-eax  1/imm32/exit
 301     cd/syscall  0x80/imm8
 302     # never gets here
 303 
 304 test-get-slice:
 305     # . prologue
 306     55/push-ebp
 307     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 308     # - setup: create a table with a couple of keys
 309     # var table/ecx: (stream {string, number} 16)  # 2 rows * 8 bytes/row
 310     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # subtract from esp
 311     68/push  0x10/imm32/length
 312     68/push  0/imm32/read
 313     68/push  0/imm32/write
 314     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
 315     # insert(table, "code", 8 bytes per row)
 316     # . . push args
 317     68/push  8/imm32/row-size
 318     68/push  "code"/imm32
 319     51/push-ecx
 320     # . . call
 321     e8/call  get-or-insert/disp32
 322     # . . discard args
 323     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 324     # insert(table, "data", 8 bytes per row)
 325     # . . push args
 326     68/push  8/imm32/row-size
 327     68/push  "data"/imm32
 328     51/push-ecx
 329     # . . call
 330     e8/call  get-or-insert/disp32
 331     # . . discard args
 332     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 333 $test-get-slice:check1:
 334     # (eax..edx) = "code"
 335     b8/copy-to-eax  "code"/imm32
 336     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
 337     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  2/index/edx   .           2/r32/edx   4/disp8         .                 # copy eax+edx+4 to edx
 338     05/add-to-eax  4/imm32
 339     # var slice/edx: slice = {eax, edx}
 340     52/push-edx
 341     50/push-eax
 342     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
 343     # eax = get-slice(table, "code", 8 bytes per row)
 344     # . . push args
 345     68/push  8/imm32/row-size
 346     52/push-edx
 347     51/push-ecx
 348     # . . call
 349     e8/call  get-slice/disp32
 350     # . . discard args
 351     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 352     # check-ints-equal(eax - table->data, 4, msg)  # first row's value slot returned
 353     # . check-ints-equal(eax - table, 16, msg)
 354     # . . push args
 355     68/push  "F - test-get-slice/0"/imm32
 356     68/push  0x10/imm32
 357     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 358     50/push-eax
 359     # . . call
 360     e8/call  check-ints-equal/disp32
 361     # . . discard args
 362     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 363 $test-get-slice:check2:
 364     # (eax..edx) = "data"
 365     b8/copy-to-eax  "data"/imm32
 366     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy *eax to edx
 367     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  2/index/edx   .           2/r32/edx   4/disp8         .                 # copy eax+edx+4 to edx
 368     05/add-to-eax  4/imm32
 369     # var slice/edx: slice = {eax, edx}
 370     52/push-edx
 371     50/push-eax
 372     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
 373     # eax = get-slice(table, "data" slice, 8 bytes per row)
 374     # . . push args
 375     68/push  8/imm32/row-size
 376     52/push-edx
 377     51/push-ecx
 378     # . . call
 379     e8/call  get-slice/disp32
 380     # . . discard args
 381     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 382     # check-ints-equal(eax - table->data, 12, msg)
 383     # . check-ints-equal(eax - table, 24, msg)
 384     # . . push args
 385     68/push  "F - test-get-slice/1"/imm32
 386     68/push  0x18/imm32
 387     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 388     50/push-eax
 389     # . . call
 390     e8/call  check-ints-equal/disp32
 391     # . . discard args
 392     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 393 $test-get-slice:end:
 394     # . epilogue
 395     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 396     5d/pop-to-ebp
 397     c3/return
 398 
 399 # if no row is found, save 'key' to the next available row
 400 # if there are no rows free, abort
 401 # return the address of the value
 402 # Beware: assume keys are immutable; they're inserted by reference
 403 # TODO: pass in an allocation descriptor
 404 get-or-insert:  # table: (addr stream {string_key, T}), key: string_key, row-size: int -> eax: (addr T)
 405     # pseudocode:
 406     #   curr = table->data
 407     #   max = &table->data[table->write]
 408     #   while curr < max
 409     #     if string-equal?(key, *curr)
 410     #       return curr+4
 411     #     curr += row-size
 412     #   if table->write >= table->length
 413     #     abort
 414     #   zero-out(max, row-size)
 415     #   *max = key
 416     #   table->write += row-size
 417     #   return max+4
 418     #
 419     # . prologue
 420     55/push-ebp
 421     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 422     # . save registers
 423     51/push-ecx
 424     52/push-edx
 425     56/push-esi
 426     # esi = table
 427     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 428     # var curr/ecx: (addr string_key) = table->data
 429     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy esi+12 to ecx
 430     # var max/edx: (addr string_key) = &table->data[table->write]
 431     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
 432     8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   .               .                 # copy ecx+edx to edx
 433 $get-or-insert:search-loop:
 434     # if (curr >= max) break
 435     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
 436     73/jump-if-addr>=  $get-or-insert:not-found/disp8
 437     # if (string-equal?(key, *curr)) return curr+4
 438     # . eax = string-equal?(key, *curr)
 439     # . . push args
 440     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 441     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 442     # . . call
 443     e8/call  string-equal?/disp32
 444     # . . discard args
 445     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 446     # . if (eax != false) return eax = curr+4
 447     3d/compare-eax-and  0/imm32/false
 448     74/jump-if-=  $get-or-insert:mismatch/disp8
 449     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy ecx+4 to eax
 450     eb/jump  $get-or-insert:end/disp8
 451 $get-or-insert:mismatch:
 452     # curr += row-size
 453     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # add *(ebp+16) to ecx
 454     # loop
 455     eb/jump  $get-or-insert:search-loop/disp8
 456 $get-or-insert:not-found:
 457     # result/eax = 0
 458     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 459     # if (table->write >= table->length) abort
 460     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
 461     3b/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   8/disp8         .                 # compare ecx with *(esi+8)
 462     73/jump-if-addr>=  $get-or-insert:abort/disp8
 463     # zero-out(max, row-size)
 464     # . . push args
 465     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
 466     52/push-edx
 467     # . . call
 468     e8/call  zero-out/disp32
 469     # . . discard args
 470     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 471     # *max = key
 472     # . eax = key
 473     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
 474     # . *max = eax
 475     89/copy                         0/mod/indirect  2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy eax to *edx
 476     # table->write += row-size
 477     # . eax = row-size
 478     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
 479     # . table->write += eax
 480     01/add                          0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # add eax to *esi
 481     # return max+4
 482     # . eax = max
 483     89/copy                         3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # copy edx to eax
 484     # . eax += 4
 485     05/add-to-eax  4/imm32
 486 $get-or-insert:end:
 487     # . restore registers
 488     5e/pop-to-esi
 489     5a/pop-to-edx
 490     59/pop-to-ecx
 491     # . epilogue
 492     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 493     5d/pop-to-ebp
 494     c3/return
 495 
 496 $get-or-insert:abort:
 497     # . _write(2/stderr, error)
 498     # . . push args
 499     68/push  "get-or-insert: table is full\n"/imm32
 500     68/push  2/imm32/stderr
 501     # . . call
 502     e8/call  _write/disp32
 503     # . . discard args
 504     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 505     # . syscall(exit, 1)
 506     bb/copy-to-ebx  1/imm32
 507     b8/copy-to-eax  1/imm32/exit
 508     cd/syscall  0x80/imm8
 509     # never gets here
 510 
 511 test-get-or-insert:
 512     # . prologue
 513     55/push-ebp
 514     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 515     # var table/ecx: (stream {string, number} 16)  # 2 rows * 8 bytes/row
 516     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # subtract from esp
 517     68/push  0x10/imm32/length
 518     68/push  0/imm32/read
 519     68/push  0/imm32/write
 520     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
 521 $test-get-or-insert:first-call:
 522     # - start with an empty table, insert one key, verify that it was inserted
 523     # eax = get-or-insert(table, "code", 8 bytes per row)
 524     # . . push args
 525     68/push  8/imm32/row-size
 526     68/push  "code"/imm32
 527     51/push-ecx
 528     # . . call
 529     e8/call  get-or-insert/disp32
 530     # . . discard args
 531     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 532     # check-ints-equal(eax - table->data, 4, msg)  # first row's value slot returned
 533     # . check-ints-equal(eax - table, 16, msg)
 534     # . . push args
 535     68/push  "F - test-get-or-insert/0"/imm32
 536     68/push  0x10/imm32
 537     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 538     50/push-eax
 539     # . . call
 540     e8/call  check-ints-equal/disp32
 541     # . . discard args
 542     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 543 $test-get-or-insert:check2:
 544     # check-ints-equal(table->write, row-size = 8, msg)
 545     # . . push args
 546     68/push  "F - test-get-or-insert/1"/imm32
 547     68/push  8/imm32/row-size
 548     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 549     # . . call
 550     e8/call  check-ints-equal/disp32
 551     # . . discard args
 552     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 553     # check-strings-equal(*table->data, "code", msg)
 554     # . . push args
 555     68/push  "F - test-get-or-insert/2"/imm32
 556     68/push  "code"/imm32
 557     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
 558     # . . call
 559     e8/call  check-strings-equal/disp32
 560     # . . discard args
 561     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 562 $test-get-or-insert:second-call:
 563     # - insert the same key again, verify that it was reused
 564     # eax = get-or-insert(table, "code", 8 bytes per row)
 565     # . . push args
 566     68/push  8/imm32/row-size
 567     68/push  "code"/imm32
 568     51/push-ecx
 569     # . . call
 570     e8/call  get-or-insert/disp32
 571     # . . discard args
 572     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 573     # check-ints-equal(eax - table->data, 4, msg)
 574     # . check-ints-equal(eax - table, 16, msg)
 575     # . . push args
 576     68/push  "F - test-get-or-insert/3"/imm32
 577     68/push  0x10/imm32
 578     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 579     50/push-eax
 580     # . . call
 581     e8/call  check-ints-equal/disp32
 582     # . . discard args
 583     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 584     # no new row inserted
 585     # . check-ints-equal(table->write, row-size = 8, msg)
 586     # . . push args
 587     68/push  "F - test-get-or-insert/4"/imm32
 588     68/push  8/imm32/row-size
 589     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 590     # . . call
 591     e8/call  check-ints-equal/disp32
 592     # . . discard args
 593     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 594     # check-strings-equal(*table->data, "code", msg)
 595     # . . push args
 596     68/push  "F - test-get-or-insert/5"/imm32
 597     68/push  "code"/imm32
 598     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0xc/disp8       .                 # push *(ecx+12)
 599     # . . call
 600     e8/call  check-strings-equal/disp32
 601     # . . discard args
 602     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 603 $test-get-or-insert:third-call:
 604     # - insert a new key, verify that it was inserted
 605     # eax = get-or-insert(table, "data", 8 bytes per row)
 606     # . . push args
 607     68/push  8/imm32/row-size
 608     68/push  "data"/imm32
 609     51/push-ecx
 610     # . . call
 611     e8/call  get-or-insert/disp32
 612     # . . discard args
 613     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 614     # table gets a new row
 615     # check-ints-equal(eax - table->data, 12, msg)  # second row's value slot returned
 616     # . check-ints-equal(eax - table, 24, msg)
 617     # . . push args
 618     68/push  "F - test-get-or-insert/6"/imm32
 619     68/push  0x18/imm32
 620     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
 621     50/push-eax
 622     # . . call
 623     e8/call  check-ints-equal/disp32
 624     # . . discard args
 625     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 626     # check-ints-equal(table->write, 2 rows = 16, msg)
 627     # . . push args
 628     68/push  "F - test-get-or-insert/7"/imm32
 629     68/push  0x10/imm32/two-rows
 630     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
 631     # . . call
 632     e8/call  check-ints-equal/disp32
 633     # . . discard args
 634     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 635     # check-strings-equal(*table->data+8, "data", msg)
 636     # check-strings-equal(*(table+20), "data", msg)
 637     # . . push args
 638     68/push  "F - test-get-or-insert/8"/imm32
 639     68/push  "data"/imm32
 640     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           0x14/disp8      .                 # push *(ecx+20)
 641     # . . call
 642     e8/call  check-strings-equal/disp32
 643     # . . discard args
 644     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 645 $test-get-or-insert:end:
 646     # . epilogue
 647     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 648     5d/pop-to-ebp
 649     c3/return
 650 
 651 # if no row is found, save 'key' in the next available row
 652 # if there are no rows free, abort
 653 # WARNING: leaks memory
 654 # TODO: pass in an allocation descriptor
 655 leaky-get-or-insert-slice:  # table: (addr stream {string_key, T}), key: (addr slice), row-size: int -> eax: (addr T)
 656     # pseudocode:
 657     #   curr = table->data
 658     #   max = &table->data[table->write]
 659     #   while curr < max
 660     #     if slice-equal?(key, *curr)
 661     #       return curr+4
 662     #     curr += row-size
 663     #   if table->write >= table->length
 664     #     abort
 665     #   zero-out(max, row-size)
 666     #   *max = slice-to-string(Heap, key)
 667     #   table->write += row-size
 668     #   return max+4
 669     #
 670     # . prologue
 671     55/push-ebp
 672     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 673     # . save registers
 674     51/push-ecx
 675     52/push-edx
 676     56/push-esi
j < SIZE(inst.words);  ++j) {
      const word& curr = inst.words.at(j);
      if (!contains_key(address, curr.data)) {
        if (!looks_like_hex_int(curr.data))
          raise << "missing reference to global '" << curr.data << "'\n" << end();
        new_inst.words.push_back(curr);
        continue;
      }
      if (!valid_use_of_global_variable(curr)) {
        raise << "'" << to_string(inst) << "': can't refer to global variable '" << curr.data << "'\n" << end();
        return;
      }
      emit_hex_bytes(new_inst, get(address, curr.data), 4);
    }
    inst.words.swap(new_inst.words);
    trace(99, "transform") << "instruction after transform: '" << data_to_string(inst) << "'" << end();
  }
}

void replace_global_variables_in_data_segment(segment& data, const map<string, uint32_t>& address) {
  for (int i = 0;  i < SIZE(data.lines);  ++i) {
    line& l = data.lines.at(i);
    line new_l;
    for (int j = 0;  j < SIZE(l.words);  ++j) {
      const word& curr = l.words.at(j);
      if (!contains_key(address, curr.data)) {
        if (looks_like_hex_int(curr.data)) {
          if (has_operand_metadata(curr, "imm32"))
            emit_hex_bytes(new_l, curr, 4);
          else if (has_operand_metadata(curr, "imm16"))
            emit_hex_bytes(new_l, curr, 2);
          else if (has_operand_metadata(curr, "imm8"))
            emit_hex_bytes(new_l, curr, 1);
          else if (has_operand_metadata(curr, "disp8"))
            raise << "can't use /disp8 in a non-code segment\n" << end();
          else if (has_operand_metadata(curr, "disp16"))
            raise << "can't use /disp16 in a non-code segment\n" << end();
          else if (has_operand_metadata(curr, "disp32"))
            raise << "can't use /disp32 in a non-code segment\n" << end();
          else
            new_l.words.push_back(curr);
        }
        else {
          raise << "missing reference to global '" << curr.data << "'\n" << end();
          new_l.words.push_back(curr);
        }
        continue;
      }
      trace(99, "transform") << curr.data << " maps to " << HEXWORD << get(address, curr.data) << end();
      emit_hex_bytes(new_l, get(address, curr.data), 4);
    }
    l.words.swap(new_l.words);
    trace(99, "transform") << "after transform: '" << data_to_string(l) << "'" << end();
  }
}

bool valid_use_of_global_variable(const word& curr) {
  if (has_operand_metadata(curr, "imm32")) return true;
  // End Valid Uses Of Global Variable(curr)
  return false;
}

//:: a more complex sanity check for how we use global variables
//: requires first saving some data early before we pack operands

:(after "Begin Level-2 Transforms")
Transform.push_back(correlate_disp32_with_mod);
:(code)
void correlate_disp32_with_mod(program& p) {
  if (p.segments.empty()) return;
  segment& code = *find(p, "code");
  for (int i = 0;  i < SIZE(code.lines);  ++i) {
    line& inst = code.lines.at(i);
    for (int j = 0;  j < SIZE(inst.words);  ++j) {
      word& curr = inst.words.at(j);
      if (has_operand_metadata(curr, "disp32")
          && has_operand_metadata(inst, "mod"))
        curr.metadata.push_back("has_mod");
    }
  }
}

:(before "End Valid Uses Of Global Variable(curr)")
if (has_operand_metadata(curr, "disp32"))
  return has_metadata(curr, "has_mod");
// todo: more sophisticated check, to ensure we don't use global variable
// addresses as a real displacement added to other operands.

:(code)
bool has_metadata(const word& w, const string& m) {
  for (int i = 0;  i < SIZE(w.metadata);  ++i)
    if (w.metadata.at(i) == m) return true;
  return false;
}

void test_global_variable_disallowed_in_jump() {
  Hide_errors = true;
  run(
      "== code 0x1\n"
      "eb/jump  x/disp8\n"
      "== data 0x2000\n"
      "x:\n"
      "  00 00 00 00\n"
  );
  CHECK_TRACE_CONTENTS(
      "error: 'eb/jump x/disp8': can't refer to global variable 'x'\n"
      // sub-optimal error message; should be
//?       "error: can't jump to data (variable 'x')\n"
  );
}

void test_global_variable_disallowed_in_call() {
  Hide_errors = true;
  run(
      "== code 0x1\n"
      "e8/call  x/disp32\n"
      "== data 0x2000\n"
      "x:\n"
      "  00 00 00 00\n"
  );
  CHECK_TRACE_CONTENTS(
      "error: 'e8/call x/disp32': can't refer to global variable 'x'\n"
      // sub-optimal error message; should be
//?       "error: can't call to the data segment ('x')\n"
  );
}

void test_global_variable_in_data_segment() {
  run(
      "== code 0x1\n"
      "b9  x/imm32\n"
      "== data 0x2000\n"
      "x:\n"
      "  y/imm32\n"
      "y:\n"
      "  00 00 00 00\n"
  );
  // check that we loaded 'x' with the address of 'y'
  CHECK_TRACE_CONTENTS(
      "load: 0x00002000 -> 04\n"
      "load: 0x00002001 -> 20\n"
      "load: 0x00002002 -> 00\n"
      "load: 0x00002003 -> 00\n"
  );
  CHECK_TRACE_COUNT("error", 0);
}

void test_raw_number_with_imm32_in_data_segment() {
  run(
      "== code 0x1\n"
      "b9  x/imm32\n"
      "== data 0x2000\n"
      "x:\n"
      "  1/imm32\n"
  );
  // check that we loaded 'x' with the address of 1
  CHECK_TRACE_CONTENTS(
      "load: 0x00002000 -> 01\n"
      "load: 0x00002001 -> 00\n"
      "load: 0x00002002 -> 00\n"
      "load: 0x00002003 -> 00\n"
  );
  CHECK_TRACE_COUNT("error", 0);
}

void test_duplicate_global_variable() {
  Hide_errors = true;
  run(
      "== code 0x1\n"
      "40/increment-EAX\n"
      "== data 0x2000\n"
      "x:\n"
      "x:\n"
      "  00\n"
  );
  CHECK_TRACE_CONTENTS(
      "error: duplicate global 'x'\n"
  );
}

void test_global_variable_disp32_with_modrm() {
  run(
      "== code 0x1\n"
      "8b/copy 0/mod/indirect 5/rm32/.disp32 2/r32/EDX x/disp32\n"
      "== data 0x2000\n"
      "x:\n"
      "  00 00 00 00\n"
  );
  CHECK_TRACE_COUNT("error", 0);
}

void test_global_variable_disp32_with_call() {
  transform(
      "== code 0x1\n"
      "foo:\n"
      "  e8/call bar/disp32\n"
      "bar:\n"
  );
  CHECK_TRACE_COUNT("error", 0);
}

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