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