https://github.com/akkartik/mu/blob/main/linux/120allocate.subx

      1 # Helper to dynamically allocate memory on the heap.
      2 #
      3 # We'd like to be able to write tests for functions that allocate memory,
      4 # making assertions on the precise addresses used. To achieve this we'll pass
      5 # in an *allocation descriptor* to allocate from.
      6 #
      7 # Allocation descriptors are also useful outside of tests. Assembly and machine
      8 # code are of necessity unsafe languages, and one of the most insidious kinds
      9 # of bugs unsafe languages expose us to are dangling pointers to memory that
     10 # has been freed and potentially even reused for something totally different.
     11 # To reduce the odds of such "use after free" errors, SubX programs tend to not
     12 # reclaim and reuse dynamically allocated memory. (Running out of memory is far
     13 # easier to debug.) Long-running programs that want to reuse memory are mostly
     14 # on their own to be careful. However, they do get one bit of help: they can
     15 # carve out chunks of memory and then allocate from them manually using this
     16 # very same 'allocate' helper. They just need a new allocation descriptor for
     17 # their book-keeping.
     18
     19 == data
     20
     21 # Allocations are returned in a handle, which consists of an alloc-id and a payload.
     22 # The alloc-id helps detect use-after-free errors.
     23 Handle-size:  # (addr int)
     24   8/imm32
     25
     26 # A default allocation descriptor for programs to use.
     27 Heap:  # allocation-descriptor
     28   # curr
     29   0/imm32
     30   # limit
     31   0/imm32
     32
     33 # a reasonable default
     34 Heap-size:  # int
     35   0x800000/imm32/8MB
     36
     37 Next-alloc-id:  # int
     38   0x100/imm32  # save a few alloc ids for fake handles
     39
     40 == code
     41 #   instruction                     effective address                                                   register    displacement    immediate
     42 # . op          subop               mod             rm32          base        index         scale       r32
     43 # . 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
     44
     45 # Let's start initializing the default allocation descriptor.
     46
     47 Entry:
     48     # initialize heap
     49     # . Heap = new-segment(Heap-size)
     50     # . . push args
     51     68/push  Heap/imm32
     52     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Heap-size/disp32                  # push *Heap-size
     53     # . . call
     54     e8/call  new-segment/disp32
     55     # . . discard args
     56     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     57
     58     e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
     59 $array-equal-main:end:
     60     # syscall(exit, Num-test-failures)
     61     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/ebx   Num-test-failures/disp32          # copy *Num-test-failures to ebx
     62     e8/call  syscall_exit/disp32
     63
     64 # Allocate and clear 'n' bytes of memory from an allocation-descriptor 'ad'.
     65 # Abort if there isn't enough memory in 'ad'.
     66 allocate:  # ad: (addr allocation-descriptor), n: int, out: (addr handle _)
     67     # . prologue
     68     55/push-ebp
     69     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
     70     # . save registers
     71     50/push-eax
     72     # allocate-raw(ad, n, out)
     73     # . . push args
     74     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
     75     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
     76     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
     77     # . . call
     78     e8/call  allocate-raw/disp32
     79     # . . discard args
     80     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     81     # eax = out->payload + 4
     82     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
     83     8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           0/r32/eax   4/disp8         .                 # copy *(eax+4) to eax
     84     05/add-to-eax  4/imm32
     85     # zero-out(eax, n)
     86     # . . push args
     87     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
     88     50/push-eax
     89     # . . call
     90     e8/call  zero-out/disp32
     91     # . . discard args
     92     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     93 $allocate:end:
     94     # . restore registers
     95     58/pop-to-eax
     96     # . epilogue
     97     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
     98     5d/pop-to-ebp
     99     c3/return
    100
    101 # Claim the next 'n' bytes of memory starting at ad->curr and update ad->curr.
    102 # Abort if there isn't enough memory in 'ad'.
    103 allocate-raw:  # ad: (addr allocation-descriptor), n: int, out: (addr handle _)
    104     # . prologue
    105     55/push-ebp
    106     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    107     # . save registers
    108     50/push-eax
    109     51/push-ecx
    110     52/push-edx
    111     53/push-ebx
    112     56/push-esi
    113     57/push-edi
    114     # ecx = ad
    115     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
    116     # edx = out
    117     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0x10/disp8      .                 # copy *(ebp+16) to edx
    118     # ebx = n
    119     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           3/r32/ebx   0xc/disp8       .                 # copy *(ebp+12) to ebx
    120     # out->alloc-id = Next-alloc-id
    121     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Next-alloc-id/disp32              # copy *Next-alloc-id to eax
    122     89/copy                         0/mod/indirect  2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy eax to *edx
    123     # out->payload = ad->curr
    124     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
    125 $allocate-raw:save-payload-in-eax:
    126     89/copy                         1/mod/*+disp8   2/rm32/edx    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edx+4)
    127     # *out->payload = Next-alloc-id
    128     8b/copy                         1/mod/*+disp8   2/rm32/edx    .           .             .           7/r32/edi   4/disp8         .                 # copy *(edx+4) to edi
    129     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           6/r32/esi   Next-alloc-id/disp32              # copy *Next-alloc-id to esi
    130     89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           6/r32/esi   .               .                 # copy esi to *edi
    131 $allocate-raw:increment-next-alloc-id:
    132     # increment *Next-alloc-id
    133     ff          0/subop/increment   0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # increment *Next-alloc-id
    134     # check if there's enough space
    135     # TODO: move this check up before any state updates when we support error recovery
    136     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  3/index/ebx   .           0/r32/eax   4/disp8         .                 # copy eax+ebx+4 to eax
    137     3b/compare                      1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # compare eax with *(ecx+4)
    138     73/jump-if->=-signed  $allocate-raw:abort/disp8
    139 $allocate-raw:commit:
    140     # ad->curr += n+4
    141     89/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy eax to *ecx
    142 $allocate-raw:end:
    143     # . restore registers
    144     5f/pop-to-edi
    145     5e/pop-to-esi
    146     5b/pop-to-ebx
    147     5a/pop-to-edx
    148     59/pop-to-ecx
    149     58/pop-to-eax
    150     # . epilogue
    151     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    152     5d/pop-to-ebp
    153     c3/return
    154
    155 $allocate-raw:abort:
    156     # . _write(2/stderr, error)
    157     # . . push args
    158     68/push  "allocate: failed\n"/imm32
    159     68/push  2/imm32/stderr
    160     # . . call
    161     e8/call  _write/disp32
    162     # . . discard args
    163     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    164     # . syscall(exit, 1)
    165     bb/copy-to-ebx  1/imm32
    166     e8/call  syscall_exit/disp32
    167     # never gets here
    168
    169 test-allocate-raw-success:
    170     # . prologue
    171     55/push-ebp
    172     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    173     # var ad/ecx: allocation-descriptor
    174     68/push  0/imm32/limit
    175     68/push  0/imm32/curr
    176     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
    177     # ad = new-segment(512)
    178     # . . push args
    179     51/push-ecx
    180     68/push  0x200/imm32
    181     # . . call
    182     e8/call  new-segment/disp32
    183     # . . discard args
    184     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    185     # var expected-payload/ebx = ad->curr
    186     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # copy *ecx to ebx
    187     # var h/edx: handle = {0, 0}
    188     68/push  0/imm32
    189     68/push  0/imm32
    190     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
    191     # *Next-alloc-id = 0x34
    192     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x34/imm32  # copy to *Next-alloc-id
    193     # allocate-raw(ad, 3, h)
    194     # . . push args
    195     52/push-edx
    196     68/push  3/imm32
    197     51/push-ecx
    198     # . . call
    199     e8/call  allocate-raw/disp32
    200     # . . discard args
    201     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    202     # check-ints-equal(h->alloc-id, 0x34, msg)
    203     # . . push args
    204     68/push  "F - test-allocate-raw-success: sets alloc-id in handle"/imm32
    205     68/push  0x34/imm32
    206     ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
    207     # . . call
    208     e8/call  check-ints-equal/disp32
    209     # . . discard args
    210     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    211     # check-ints-equal(h->payload, expected-payload, msg)
    212     # . . push args
    213     68/push  "F - test-allocate-raw-success: sets payload in handle"/imm32
    214     53/push-ebx
    215     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
    216     # . . call
    217     e8/call  check-ints-equal/disp32
    218     # . . discard args
    219     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    220     # check-ints-equal(h->payload->alloc-id, 0x34, msg)
    221     # . . push args
    222     68/push  "F - test-allocate-raw-success: sets alloc-id in payload"/imm32
    223     68/push  0x34/imm32
    224     ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
    225     # . . call
    226     e8/call  check-ints-equal/disp32
    227     # . . discard args
    228     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    229     # check-ints-equal(*Next-alloc-id, 0x35, msg)
    230     # . . push args
    231     68/push  "F - test-allocate-raw-success: increments Next-alloc-id"/imm32
    232     68/push  0x35/imm32
    233     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # push *Next-alloc-id
    234     # . . call
    235     e8/call  check-ints-equal/disp32
    236     # . . discard args
    237     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    238     # check-ints-equal(ad->curr - expected-payload, 3 + 4 for alloc-id, msg)
    239     # . . push args
    240     68/push  "F - test-allocate-raw-success: updates allocation descriptor"/imm32
    241     68/push  7/imm32
    242     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
    243     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
    244     50/push-eax
    245     # . . call
    246     e8/call  check-ints-equal/disp32
    247     # . . discard args
    248     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    249     # clean up
    250     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x100/imm32 # copy to *Next-alloc-id
    251     # . reclaim locals
    252     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    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 lookup:  # h: (handle _T) -> result/eax: (addr _T)
    259     # . prologue
    260     55/push-ebp
    261     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    262     # . save registers
    263     51/push-ecx
    264     # eax = 0
    265     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
    266     # ecx = handle->alloc_id
    267     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
    268     # if (ecx == 0) return 0
    269     81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0/imm32           # compare ecx
    270     74/jump-if-=  $lookup:end/disp8
    271     # eax = handle->address (payload)
    272     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
    273     # if (ecx != *eax) abort
    274     39/compare                      0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare *eax and ecx
    275     75/jump-if-!=  $lookup:abort/disp8
    276     # add 4 to eax
    277     05/add-to-eax  4/imm32
    278 $lookup:end:
    279     # . restore registers
    280     59/pop-to-ecx
    281     # . epilogue
    282     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    283     5d/pop-to-ebp
    284     c3/return
    285
    286 $lookup:abort:
    287     # . _write(2/stderr, msg)
    288     # . . push args
    289     68/push  "lookup failed\n"/imm32
    290     68/push  2/imm32/stderr
    291     # . . call
    292     e8/call  _write/disp32
    293     # . . discard args
    294     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    295     # . syscall(exit, 1)
    296     bb/copy-to-ebx  1/imm32/exit-status
    297     e8/call  syscall_exit/disp32
    298
    299 test-lookup-success:
    300     # . prologue
    301     55/push-ebp
    302     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    303     # var ad/ebx: allocation-descriptor
    304     68/push  0/imm32/limit
    305     68/push  0/imm32/curr
    306     89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
    307     # ad = new-segment(512)
    308     # . . push args
    309     53/push-ebx
    310     68/push  0x200/imm32
    311     # . . call
    312     e8/call  new-segment/disp32
    313     # . . discard args
    314     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    315     # var handle/ecx: handle
    316     68/push  0/imm32/address
    317     68/push  0/imm32/alloc-id
    318     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
    319     # var old-top/edx = ad->curr
    320     8b/copy                         0/mod/indirect  3/rm32/ebx    .           .             .           2/r32/edx   .               .                 # copy *ebx to edx
    321     # allocate-raw(ad, 2, handle)
    322     # . . push args
    323     51/push-ecx
    324     68/push  2/imm32/size
    325     53/push-ebx
    326     # . . call
    327     e8/call  allocate-raw/disp32
    328     # . . discard args
    329     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    330     # eax = lookup(handle)
    331     # . . push args
    332     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
    333     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
    334     # . . call
    335     e8/call  lookup/disp32
    336     # . . discard args
    337     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    338     # eax contains old top of heap, except skipping the alloc-id in the payload
    339     # . check-ints-equal(eax, old-top+4, msg)
    340     # . . push args
    341     68/push  "F - test-lookup-success"/imm32
    342     81          0/subop/add         3/mod/direct    2/rm32/edx    .           .             .           .           .               4/imm32           # add to edx
    343     52/push-edx
    344     50/push-eax
    345     # . . call
    346     e8/call  check-ints-equal/disp32
    347     # . . discard args
    348     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    349     # clean up
    350     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x100/imm32 # copy to *Next-alloc-id
    351     # . reclaim locals
    352     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    353     # . epilogue
    354     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    355     5d/pop-to-ebp
    356     c3/return
    357
    358 test-lookup-null-returns-null:
    359     # . prologue
    360     55/push-ebp
    361     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    362     # var handle/ecx: handle
    363     68/push  0/imm32/address
    364     68/push  0/imm32/alloc-id
    365     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
    366     # eax = lookup(handle)
    367     # . . push args
    368     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
    369     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
    370     # . . call
    371     e8/call  lookup/disp32
    372     # . . discard args
    373     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    374     # check-ints-equal(eax, 0, msg)
    375     # . . push args
    376     68/push  "F - test-lookup-null-returns-null"/imm32
    377     68/push  0/imm32
    378     50/push-eax
    379     # . . call
    380     e8/call  check-ints-equal/disp32
    381     # . . discard args
    382     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    383     # . reclaim locals
    384     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    385     # . epilogue
    386     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    387     5d/pop-to-ebp
    388     c3/return
    389
    390 _pending-test-lookup-failure:
    391     # . prologue
    392     55/push-ebp
    393     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    394     # var heap/esi: allocation-descriptor
    395     68/push  0/imm32/limit
    396     68/push  0/imm32/curr
    397     89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
    398     # heap = new-segment(512)
    399     # . . push args
    400     56/push-esi
    401     68/push  0x200/imm32
    402     # . . call
    403     e8/call  new-segment/disp32
    404     # . . discard args
    405     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    406     # var h1/ecx: handle
    407     68/push  0/imm32/address
    408     68/push  0/imm32/alloc-id
    409     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
    410     # var old_top/ebx = heap->curr
    411     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           3/r32/ebx   .               .                 # copy *esi to ebx
    412     # first allocation, to h1
    413     # . allocate(heap, 2, h1)
    414     # . . push args
    415     51/push-ecx
    416     68/push  2/imm32/size
    417     56/push-esi
    418     # . . call
    419     e8/call  allocate/disp32
    420     # . . discard args
    421     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    422     # reset heap->curr to mimic reclamation
    423     89/copy                         0/mod/indirect  6/rm32/esi    .           .             .           3/r32/ebx   .               .                 # copy ebx to *esi
    424     # second allocation that returns the same address as the first
    425     # var h2/edx: handle
    426     68/push  0/imm32/address
    427     68/push  0/imm32/alloc-id
    428     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
    429     # . allocate(heap, 2, h2)
    430     # . . push args
    431     52/push-edx
    432     68/push  2/imm32/size
    433     56/push-esi
    434     # . . call
    435     e8/call  allocate/disp32
    436     # . . discard args
    437     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    438     # check-ints-equal(h1->address, h2->address, msg)
    439     # . . push args
    440     68/push  "F - test-lookup-failure"/imm32
    441     ff          6/subop/push        1/mod/*+disp8   2/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(edx+4)
    442     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
    443     # . . call
    444     e8/call  check-ints-equal/disp32
    445     # . . discard args
    446     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    447     # lookup(h1) should crash
    448     # . . push args
    449     ff          6/subop/push        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         .                 # push *(ecx+4)
    450     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
    451     # . . call
    452     e8/call  lookup/disp32
    453     # should never get past this point
    454     # . . discard args
    455     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    456     # clean up
    457     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x100/imm32 # copy to *Next-alloc-id
    458     # . reclaim locals
    459     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    460     # . epilogue
    461     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    462     5d/pop-to-ebp
    463     c3/return
    464
    465 # when comparing handles, just treat them as pure values
    466 handle-equal?:  # a: (handle _T), b: (handle _T) -> result/eax: boolean
    467     # . prologue
    468     55/push-ebp
    469     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    470     # . save registers
    471     51/push-ecx
    472     # eax = false
    473     b8/copy-to-eax  0/imm32/false
    474 $handle-equal?:compare-alloc-id:
    475     # ecx = a->alloc_id
    476     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
    477     # if (ecx != b->alloc_id) return false
    478     39/compare                      1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # compare ecx and *(ebp+16)
    479     75/jump-if-!=  $handle-equal?:end/disp8
    480 $handle-equal?:compare-address:
    481     # ecx = handle->address
    482     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
    483     # if (ecx != b->address) return false
    484     39/compare                      1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x14/disp8      .                 # compare ecx and *(ebp+20)
    485     75/jump-if-!=  $handle-equal?:end/disp8
    486 $handle-equal?:return-true:
    487     # return true
    488     b8/copy-to-eax  1/imm32/true
    489 $handle-equal?:end:
    490     # . restore registers
    491     59/pop-to-ecx
    492     # . epilogue
    493     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    494     5d/pop-to-ebp
    495     c3/return
    496
    497 copy-handle:  # src: (handle _T), dest: (addr handle _T)
    498     # . prologue
    499     55/push-ebp
    500     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    501     # . save registers
    502     50/push-eax
    503     51/push-ecx
    504     # ecx = dest
    505     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x10/disp8      .                 # copy *(ebp+16) to ecx
    506     # *dest = src
    507     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
    508     89/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy eax to *ecx
    509     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
    510     89/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(ecx+4)
    511 $copy-handle:end:
    512     # . restore registers
    513     59/pop-to-ecx
    514     58/pop-to-eax
    515     # . epilogue
    516     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    517     5d/pop-to-ebp
    518     c3/return
    519
    520 # helper: create a nested allocation descriptor (useful for tests)
    521 allocate-region:  # ad: (addr allocation-descriptor), n: int, out: (addr handle allocation-descriptor)
    522     # . prologue
    523     55/push-ebp
    524     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    525     # . save registers
    526     50/push-eax
    527     51/push-ecx
    528     # allocate(ad, n, out)
    529     # . . push args
    530     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
    531     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    532     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    533     # . . call
    534     e8/call  allocate/disp32
    535     # . . discard args
    536     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    537     # eax = out->payload
    538     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
    539     8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           0/r32/eax   4/disp8         .                 # copy *(eax+4) to eax
    540     # skip payload->allocid
    541     05/add-to-eax  4/imm32
    542     # if (eax == 0) abort
    543     3d/compare-eax-and  0/imm32
    544     74/jump-if-=  $allocate-region:abort/disp8
    545     # earmark 8 bytes at the start for a new allocation descriptor
    546     # . *eax = eax + 8
    547     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy eax to ecx
    548     81          0/subop/add         3/mod/direct    1/rm32/ecx    .           .             .           .           .               8/imm32           # add to ecx
    549     89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
    550     # . *(eax+4) = eax + n
    551     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy eax to ecx
    552     03/add                          1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # add *(ebp+12) to ecx
    553     89/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           1/r32/ecx   4/disp8         .                 # copy ecx to *(eax+4)
    554     # . restore registers
    555     59/pop-to-ecx
    556     58/pop-to-eax
    557     # . epilogue
    558     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    559     5d/pop-to-ebp
    560     c3/return
    561
    562 # We could create a more general '$abort' jump target, but then we'd need to do
    563 # a conditional jump followed by loading the error message and an unconditional
    564 # jump. Or we'd need to unconditionally load the error message before a
    565 # conditional jump, even if it's unused the vast majority of the time. This way
    566 # we bloat a potentially cold segment in RAM so we can abort with a single
    567 # instruction.
    568 $allocate-region:abort:
    569     # . _write(2/stderr, error)
    570     # . . push args
    571     68/push  "allocate-region: failed to allocate\n"/imm32
    572     68/push  2/imm32/stderr
    573     # . . call
    574     e8/call  _write/disp32
    575     # . . discard args
    576     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    577     # . syscall(exit, 1)
    578     bb/copy-to-ebx  1/imm32
    579     e8/call  syscall_exit/disp32
    580     # never gets here
    581
    582 # Claim the next 'n+4' bytes of memory and initialize the first 4 to n.
    583 # Abort if there isn't enough memory in 'ad'.
    584 allocate-array:  # ad: (addr allocation-descriptor), n: int, out: (addr handle _)
    585     # . prologue
    586     55/push-ebp
    587     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    588     # . save registers
    589     50/push-eax
    590     51/push-ecx
    591     52/push-edx
    592     # ecx = n
    593     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
    594     # var size/edx: int = n+4
    595     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           2/r32/edx   4/disp8         .                 # copy ecx+4 to edx
    596     # allocate(ad, size, out)
    597     # . . push args
    598     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
    599     52/push-edx
    600     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    601     # . . call
    602     e8/call  allocate/disp32
    603     # . . discard args
    604     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    605     # *out->payload = n
    606     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
    607     8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           0/r32/eax   4/disp8         .                 # copy *(eax+4) to eax
    608     # . skip payload->allocid
    609     05/add-to-eax  4/imm32
    610     # .
    611     89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
    612 $allocate-array:end:
    613     # . restore registers
    614     5a/pop-to-edx
    615     59/pop-to-ecx
    616     58/pop-to-eax
    617     # . epilogue
    618     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    619     5d/pop-to-ebp
    620     c3/return
    621
    622 test-allocate-array:
    623     # . prologue
    624     55/push-ebp
    625     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    626     # var ad/ecx: allocation-descriptor
    627     68/push  0/imm32/limit
    628     68/push  0/imm32/curr
    629     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
    630     # ad = new-segment(512)
    631     # . . push args
    632     51/push-ecx
    633     68/push  0x200/imm32
    634     # . . call
    635     e8/call  new-segment/disp32
    636     # . . discard args
    637     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    638     # var expected-payload/ebx = ad->curr
    639     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # copy *ecx to ebx
    640     # var h/edx: handle = {0, 0}
    641     68/push  0/imm32
    642     68/push  0/imm32
    643     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
    644     # *Next-alloc-id = 0x34
    645     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x34/imm32  # copy to *Next-alloc-id
    646     # allocate-array(ad, 3, h)
    647     # . . push args
    648     52/push-edx
    649     68/push  3/imm32
    650     51/push-ecx
    651     # . . call
    652     e8/call  allocate-array/disp32
    653     # . . discard args
    654     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    655     # check-ints-equal(h->alloc-id, 0x34, msg)
    656     # . . push args
    657     68/push  "F - test-allocate-array: sets alloc-id in handle"/imm32
    658     68/push  0x34/imm32
    659     ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
    660     # . . call
    661     e8/call  check-ints-equal/disp32
    662     # . . discard args
    663     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    664     # check-ints-equal(h->payload, expected-payload, msg)
    665     # . . push args
    666     68/push  "F - test-allocate-array: sets payload in handle"/imm32
    667     53/push-ebx
    668     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
    669     # . . call
    670     e8/call  check-ints-equal/disp32
    671     # . . discard args
    672     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    673     # check-ints-equal(h->payload->alloc-id, 0x34, msg)
    674     # . . push args
    675     68/push  "F - test-allocate-array: sets alloc-id in payload"/imm32
    676     68/push  0x34/imm32
    677     ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
    678     # . . call
    679     e8/call  check-ints-equal/disp32
    680     # . . discard args
    681     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    682     # check-ints-equal(h->payload->size, 3, msg)
    683     # . . push args
    684     68/push  "F - test-allocate-array: sets array size in payload"/imm32
    685     68/push  3/imm32
    686     ff          6/subop/push        1/mod/*+disp8   3/rm32/ebx    .           .             .           .           4/disp8         .                 # push *(ebx+4)
    687     # . . call
    688     e8/call  check-ints-equal/disp32
    689     # . . discard args
    690     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    691     # check-ints-equal(*Next-alloc-id, 0x35, msg)
    692     # . . push args
    693     68/push  "F - test-allocate-array: increments Next-alloc-id"/imm32
    694     68/push  0x35/imm32
    695     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # push *Next-alloc-id
    696     # . . call
    697     e8/call  check-ints-equal/disp32
    698     # . . discard args
    699     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    700     # check-ints-equal(ad->curr - expected-payload, 3 + 4 for alloc-id + 4 for array size, msg)
    701     # . . push args
    702     68/push  "F - test-allocate-array: updates allocation descriptor"/imm32
    703     68/push  0xb/imm32
    704     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
    705     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
    706     50/push-eax
    707     # . . call
    708     e8/call  check-ints-equal/disp32
    709     # . . discard args
    710     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    711     # clean up
    712     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  1/imm32     # copy to *Next-alloc-id
    713     # . reclaim locals
    714     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    715     # . epilogue
    716     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    717     5d/pop-to-ebp
    718     c3/return
    719
    720 copy-array:  # ad: (addr allocation-descriptor), src: (addr array _T), out: (addr handle array _T)
    721     # . prologue
    722     55/push-ebp
    723     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    724     # . save registers
    725     50/push-eax
    726     51/push-ecx
    727     52/push-edx
    728     56/push-esi
    729     # esi = src
    730     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   0xc/disp8       .                 # copy *(ebp+12) to esi
    731     # var size/ecx: int = src->size+4
    732     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
    733     81          0/subop/add         3/mod/direct    1/rm32/ecx    .           .             .           .           .               4/imm32           # add to ecx
    734     # allocate(ad, size, out)
    735     # . . push args
    736     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
    737     51/push-ecx
    738     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    739     # . . call
    740     e8/call  allocate/disp32
    741     # . . discard args
    742     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    743     # var payload/eax: (addr byte) = out->payload
    744     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
    745     8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           0/r32/eax   4/disp8         .                 # copy *(eax+4) to eax
    746     # . skip payload->allocid
    747     05/add-to-eax  4/imm32
    748     # var max/ecx: (addr byte) = payload + size
    749     01/add                          3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # add eax to ecx
    750     # _append-4(payload, max, src, &src->data[src->size])
    751     # . . push &src->data[src->size]
    752     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
    753     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  2/index/edx   .           2/r32/edx   4/disp8         .                 # copy esi+edx+4 to edx
    754     52/push-edx
    755     # . . push src
    756     56/push-esi
    757     # . . push max
    758     51/push-ecx
    759     # . . push payload
    760     50/push-eax
    761     # . . call
    762     e8/call  _append-4/disp32
    763     # . . discard args
    764     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    765 $copy-array:end:
    766     # . restore registers
    767     5e/pop-to-esi
    768     5a/pop-to-edx
    769     59/pop-to-ecx
    770     58/pop-to-eax
    771     # . epilogue
    772     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    773     5d/pop-to-ebp
    774     c3/return
    775
    776 test-copy-array:
    777     # . prologue
    778     55/push-ebp
    779     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    780     # var src/esi: (addr array int) = [3, 4, 5]
    781     68/push  5/imm32
    782     68/push  4/imm32
    783     68/push  3/imm32
    784     68/push  0xc/imm32/size
    785     89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
    786     # var ad/ecx: allocation-descriptor
    787     68/push  0/imm32/limit
    788     68/push  0/imm32/curr
    789     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
    790     # ad = new-segment(512)
    791     # . . push args
    792     51/push-ecx
    793     68/push  0x200/imm32
    794     # . . call
    795     e8/call  new-segment/disp32
    796     # . . discard args
    797     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    798     # var expected-payload/ebx = ad->curr
    799     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # copy *ecx to ebx
    800     # var h/edx: handle = {0, 0}
    801     68/push  0/imm32
    802     68/push  0/imm32
    803     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
    804     # *Next-alloc-id = 0x34
    805     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  0x34/imm32  # copy to *Next-alloc-id
    806     # copy-array(ad, src, h)
    807     # . . push args
    808     52/push-edx
    809     56/push-esi
    810     51/push-ecx
    811     # . . call
    812     e8/call  copy-array/disp32
    813     # . . discard args
    814     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    815     # check-ints-equal(h->alloc-id, 0x34, msg)
    816     # . . push args
    817     68/push  "F - test-copy-array: sets alloc-id in handle"/imm32
    818     68/push  0x34/imm32
    819     ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
    820     # . . call
    821     e8/call  check-ints-equal/disp32
    822     # . . discard args
    823     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    824     # check-ints-equal(h->payload, expected-payload, msg)
    825     # . . push args
    826     68/push  "F - test-copy-array: sets payload in handle"/imm32
    827     53/push-ebx
    828     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
    829     # . . call
    830     e8/call  check-ints-equal/disp32
    831     # . . discard args
    832     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    833     # check-ints-equal(h->payload->alloc-id, 0x34, msg)
    834     # . . push args
    835     68/push  "F - test-copy-array: sets alloc-id in payload"/imm32
    836     68/push  0x34/imm32
    837     ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
    838     # . . call
    839     e8/call  check-ints-equal/disp32
    840     # . . discard args
    841     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    842     # var payload/eax: (addr int) = lookup(h)
    843     # . . push args
    844     ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
    845     ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
    846     # . . call
    847     e8/call  lookup/disp32
    848     # . . discard args
    849     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    850     # check-ints-equal(payload->size, 0xc, msg)
    851     # . . push args
    852     68/push  "F - test-copy-array: sets array size in payload"/imm32
    853     68/push  0xc/imm32
    854     ff          6/subop/push        0/mod/indirect  0/rm32/eax    .           .             .           .           .               .                 # push *eax
    855     # . . call
    856     e8/call  check-ints-equal/disp32
    857     # . . discard args
    858     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    859     # check-ints-equal(*Next-alloc-id, 0x35, msg)
    860     # . . push args
    861     68/push  "F - test-copy-array: increments Next-alloc-id"/imm32
    862     68/push  0x35/imm32
    863     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32              # push *Next-alloc-id
    864     # . . call
    865     e8/call  check-ints-equal/disp32
    866     # . . discard args
    867     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    868     # check-ints-equal(ad->curr - expected-payload, 12 + 4 for alloc-id + 4 for size, msg)
    869     # . . push args
    870     68/push  "F - test-copy-array: updates allocation descriptor"/imm32
    871     68/push  0x14/imm32
    872     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
    873     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
    874     50/push-eax
    875     # . . call
    876     e8/call  check-ints-equal/disp32
    877     # . . discard args
    878     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    879     # clean up
    880     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-alloc-id/disp32  1/imm32     # copy to *Next-alloc-id
    881     # . reclaim locals
    882     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x20/imm32        # add to esp
    883     # . epilogue
    884     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    885     5d/pop-to-ebp
    886     c3/return
    887
    888 # Fill a region of memory with zeroes.
    889 zero-out:  # start: (addr byte), size: int
    890     # pseudocode:
    891     #   curr/esi = start
    892     #   i/ecx = 0
    893     #   while true
    894     #     if (i >= size) break
    895     #     *curr = 0
    896     #     ++curr
    897     #     ++i
    898     #
    899     # . prologue
    900     55/push-ebp
    901     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    902     # . save registers
    903     50/push-eax
    904     51/push-ecx
    905     52/push-edx
    906     56/push-esi
    907     # curr/esi = start
    908     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
    909     # var i/ecx: int = 0
    910     31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
    911     # edx = size
    912     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
    913 $zero-out:loop:
    914     # if (i >= size) break
    915     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
    916     7d/jump-if->=  $zero-out:end/disp8
    917     # *curr = 0
    918     c6          0/subop/copy-byte   0/mod/direct    6/rm32/esi    .           .             .           .           .               0/imm8            # copy byte to *esi
    919     # ++curr
    920     46/increment-esi
    921     # ++i
    922     41/increment-ecx
    923     eb/jump  $zero-out:loop/disp8
    924 $zero-out:end:
    925     # . restore registers
    926     5e/pop-to-esi
    927     5a/pop-to-edx
    928     59/pop-to-ecx
    929     58/pop-to-eax
    930     # . epilogue
    931     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    932     5d/pop-to-ebp
    933     c3/return
    934
    935 test-zero-out:
    936     # . prologue
    937     55/push-ebp
    938     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    939     # region/ecx = 34, 35, 36, 37
    940     68/push  0x37363534/imm32
    941     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
    942     # zero-out(ecx, 3)
    943     # . . push args
    944     68/push  3/imm32/size
    945     51/push-ecx
    946     # . . call
    947     e8/call  zero-out/disp32
    948     # . . discard args
    949     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    950     # first 3 bytes cleared, fourth left alone
    951     # . check-ints-equal(*ecx, 0x37000000, msg)
    952     # . . push args
    953     68/push  "F - test-zero-out"/imm32
    954     68/push  0x37000000/imm32
    955     ff          6/subop/push        0/mod/indirect  1/rm32/ecx    .           .             .           .           .               .                 # push *ecx
    956     # . . call
    957     e8/call  check-ints-equal/disp32
    958     # . . discard args
    959     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    960     # . reclaim locals
    961     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    962     # . epilogue
    963     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    964     5d/pop-to-ebp
    965     c3/return
    966
    967 # . . vim:nowrap:textwidth=0