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