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