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