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