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