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