https://github.com/akkartik/mu/blob/master/107trace.subx
  1 # primitives for emitting traces to a 'trace' stream, and for tests to make assertions on its contents
  2 #
  3 # A trace stream looks like a regular stream:
  4 #   write: int  # index at which writes go
  5 #   read: int  # index that we've read until
  6 #   data: (array byte)  # prefixed by size as usual
  7 # Usually the trace stream will be in a separate segment set aside for the purpose.
  8 #
  9 # primitives for operating on traces (arguments in quotes):
 10 #   - initialize-trace-stream: populates Trace-stream with a new segment of the given 'size'
 11 #   - trace: adds a 'line' to Trace-stream
 12 #   - check-trace-contains: scans from Trace-stream's start for a matching 'line', prints a 'message' to stderr on failure
 13 #   - check-trace-scans-to: scans from Trace-stream's read pointer for a matching 'line', prints a 'message' to stderr on failure
 14 
 15 == data
 16 
 17 # Handles are addresses created on the heap.
 18 # In safe Mu they'll be fat pointers. But in SubX they're just addresses, since
 19 # SubX programs never reclaim memory.
 20 
 21 Trace-stream:  # (handle stream byte)
 22     0/imm32
 23     # we don't have safe handles (fat pointers) yet
 24 
 25 Trace-segment:
 26     0/imm32/curr
 27     0/imm32/limit
 28 
 29 # Fake trace-stream for tests.
 30 # Also illustrates the layout of the real trace-stream (segment).
 31 _test-trace-stream:  # (stream byte)
 32     # current write index
 33     0/imm32
 34     # current read index
 35     0/imm32
 36     # size
 37     8/imm32
 38     # data
 39     00 00 00 00 00 00 00 00  # 8 bytes
 40 
 41 == code
 42 #   instruction                     effective address                                                   register    displacement    immediate
 43 # . op          subop               mod             rm32          base        index         scale       r32
 44 # . 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
 45 
 46 # Allocate a new segment for the trace stream, initialize its size, and save its address to Trace-stream.
 47 # The Trace-stream segment will consist of variable-length lines separated by newlines (0x0a)
 48 initialize-trace-stream:  # n: int
 49     # . prologue
 50     55/push-ebp
 51     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 52     # . save registers
 53     50/push-eax
 54     51/push-ecx
 55     # ecx = n
 56     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
 57     # Trace-segment = new-segment(n)
 58     # . . push args
 59     68/push  Trace-segment/imm32
 60     51/push-ecx
 61     # . . call
 62     e8/call  new-segment/disp32
 63     # . . discard args
 64     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 65     # copy Trace-segment->curr to *Trace-stream
 66     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Trace-segment/disp32              # copy *Trace-segment to eax
 67 #?     # watch point to catch Trace-stream leaks
 68 #? $watch-1:
 69     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Trace-stream/disp32               # copy eax to *Trace-stream
 70     # Trace-stream->size = n - 12
 71     # . ecx -= 12
 72     81          5/subop/subtract    3/mod/direct    1/rm32/ecx    .           .             .           .           .               0xc/imm32         # subtract from ecx
 73     # . Trace-stream->size = ecx
 74     89/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           1/r32/ecx   8/disp8         .                 # copy ecx to *(eax+8)
 75 $initialize-trace-stream:end:
 76     # . restore registers
 77     59/pop-to-ecx
 78     58/pop-to-eax
 79     # . epilogue
 80     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 81     5d/pop-to-ebp
 82     c3/return
 83 
 84 # Append a string to the given trace stream.
 85 # Silently give up if it's already full. Or truncate the string if there isn't enough room.
 86 trace:  # line: (addr array byte)
 87     # . prologue
 88     55/push-ebp
 89     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 90     # . save registers
 91     50/push-eax
 92     51/push-ecx
 93     52/push-edx
 94     53/push-ebx
 95     56/push-esi
 96     57/push-edi
 97     # var edi: (addr stream byte) = *Trace-stream
 98     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           7/r32/edi   Trace-stream/disp32               # copy *Trace-stream to edi
 99     # esi = line
100     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
101     # var ecx: int = t->write
102     8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # copy *edi to ecx
103     # var edx: int = t->size
104     8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           2/r32/edx   8/disp8         .                 # copy *(edi+8) to edx
105     # eax = _append-3(&t->data[t->write], &t->data[t->size], line)
106     # . . push line
107     56/push-esi
108     # . . push &t->data[t->size]
109     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  2/index/edx   .           3/r32/ebx   0xc/disp8       .                 # copy edi+edx+12 to ebx
110     53/push-ebx
111     # . . push &t->data[t->write]
112     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  1/index/ecx   .           3/r32/ebx   0xc/disp8       .                 # copy edi+ecx+12 to ebx
113     53/push-ebx
114     # . . call
115     e8/call  _append-3/disp32
116     # . . discard args
117     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
118     # if (eax == 0) return
119     3d/compare-eax-and  0/imm32
120     74/jump-if-=  $trace:end/disp8
121     # t->write += eax
122     01/add                          0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # add eax to *edi
123     # refresh ecx = t->write
124     8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # copy *edi to ecx
125     # eax = _append-3(&t->data[t->write], &t->data[t->size], line)
126     # . . push line
127     68/push  Newline/imm32
128     # . . push &t->data[t->size]
129     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  2/index/edx   .           3/r32/ebx   0xc/disp8       .                 # copy edi+edx+12 to ebx
130     53/push-ebx
131     # . . push &t->data[t->write]
132     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  1/index/ecx   .           3/r32/ebx   0xc/disp8       .                 # copy edi+ecx+12 to ebx
133     53/push-ebx
134     # . . call
135     e8/call  _append-3/disp32
136     # . . discard args
137     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
138     # t->write += eax
139     01/add                          0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # add eax to *edi
140 $trace:end:
141     # . restore registers
142     5f/pop-to-edi
143     5e/pop-to-esi
144     5b/pop-to-ebx
145     5a/pop-to-edx
146     59/pop-to-ecx
147     58/pop-to-eax
148     # . epilogue
149     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
150     5d/pop-to-ebp
151     c3/return
152 
153 test-trace-single:
154     # push *Trace-stream
155     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
156     # *Trace-stream = _test-trace-stream
157     b8/copy-to-eax  _test-trace-stream/imm32
158     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Trace-stream/disp32               # copy eax to *Trace-stream
159     # clear-trace-stream()
160     e8/call  clear-trace-stream/disp32
161     # trace("Ab")
162     # . . push args
163     68/push  "Ab"/imm32
164     # . . call
165     e8/call  trace/disp32
166     # . . discard args
167     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
168     # check-ints-equal(*_test-trace-stream->data, 41/A 62/b 0a/newline 00, msg)
169     # . . push args
170     68/push  "F - test-trace-single"/imm32
171     68/push  0x0a6241/imm32/Ab-newline
172     # . . push *_test-trace-stream->data
173     b8/copy-to-eax  _test-trace-stream/imm32
174     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           0xc/disp8       .                 # push *(eax+12)
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     # pop into *Trace-stream
180     8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
181     # end
182     c3/return
183 
184 test-trace-appends:
185     # push *Trace-stream
186     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
187     # *Trace-stream = _test-trace-stream
188     b8/copy-to-eax  _test-trace-stream/imm32
189     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Trace-stream/disp32               # copy eax to *Trace-stream
190     # clear-trace-stream()
191     e8/call  clear-trace-stream/disp32
192     # trace("C")
193     # . . push args
194     68/push  "C"/imm32
195     # . . call
196     e8/call  trace/disp32
197     # . . discard args
198     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
199     # trace("D")
200     # . . push args
201     68/push  "D"/imm32
202     # . . call
203     e8/call  trace/disp32
204     # . . discard args
205     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
206     # check-ints-equal(*_test-trace-stream->data, 43/C 0a/newline 44/D 0a/newline, msg)
207     # . . push args
208     68/push  "F - test-trace-appends"/imm32
209     68/push  0x0a440a43/imm32/C-newline-D-newline
210     # . . push *_test-trace-stream->data
211     b8/copy-to-eax  _test-trace-stream/imm32
212     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           0xc/disp8       .                 # push *(eax+12)
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     # pop into *Trace-stream
218     8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
219     # end
220     c3/return
221 
222 test-trace-empty-line:
223     # push *Trace-stream
224     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
225     # *Trace-stream = _test-trace-stream
226     b8/copy-to-eax  _test-trace-stream/imm32
227     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Trace-stream/disp32               # copy eax to *Trace-stream
228     # clear-trace-stream()
229     e8/call  clear-trace-stream/disp32
230     # trace("")
231     # . . push args
232     68/push  ""/imm32
233     # . . call
234     e8/call  trace/disp32
235     # . . discard args
236     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
237     # check-ints-equal(*_test-trace-stream->data, 0, msg)
238     # . . push args
239     68/push  "F - test-trace-empty-line"/imm32
240     68/push  0/imm32
241     # . . push *_test-trace-stream->data
242     b8/copy-to-eax  _test-trace-stream/imm32
243     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           0xc/disp8       .                 # push *(eax+12)
244     # . . call
245     e8/call  check-ints-equal/disp32
246     # . . discard args
247     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
248     # pop into *Trace-stream
249     8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
250     # end
251     c3/return
252 
253 check-trace-contains:  # line: (addr string), msg: (addr string)
254     # . prologue
255     55/push-ebp
256     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
257     # rewind-stream(*Trace-stream)
258     # . . push args
259     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
260     # . . call
261     e8/call  rewind-stream/disp32
262     # . . discard args
263     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
264     # check-trace-scans-to(line, msg)
265     # . . push args
266     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
267     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
268     # . . call
269     e8/call  check-trace-scans-to/disp32
270     # . . discard args
271     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
272 $check-trace-contains:end:
273     # . epilogue
274     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
275     5d/pop-to-ebp
276     c3/return
277 
278 check-trace-scans-to:  # line: (addr string), msg: (addr string)
279     # . prologue
280     55/push-ebp
281     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
282     # . save registers
283     50/push-eax
284     # eax = trace-scan(line)
285     # . . push args
286     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
287     # . . call
288     e8/call  trace-scan/disp32
289     # . . discard args
290     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
291     # check-ints-equal(eax, 1, msg)
292     # . . push args
293     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
294     68/push  1/imm32
295     50/push-eax
296     # . . call
297     e8/call check-ints-equal/disp32
298     # . . discard args
299     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
300 $check-trace-scans-to:end:
301     # . restore registers
302     58/pop-to-eax
303     # . epilogue
304     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
305     5d/pop-to-ebp
306     c3/return
307 
308 # Start scanning from Trace-stream->read for 'line'. If found, update Trace-stream->read and return true.
309 trace-scan:  # line: (addr array byte) -> result/eax: boolean
310     # pseudocode:
311     #   push Trace-stream->read
312     #   while true:
313     #     if Trace-stream->read >= Trace-stream->write
314     #       break
315     #     if next-line-matches?(Trace-stream, line)
316     #       skip-next-line(Trace-stream)
317     #       dump saved copy of Trace-stream->read
318     #       return true
319     #     skip-next-line(Trace-stream)
320     #   pop saved copy of Trace-stream->read
321     #   return false
322     #
323     # . prologue
324     55/push-ebp
325     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
326     # . save registers
327     51/push-ecx
328     56/push-esi
329     # esi = *Trace-stream
330     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           6/r32/esi   Trace-stream/disp32               # copy *Trace-stream to esi
331     # ecx = Trace-stream->write
332     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx                   .                 # copy *esi to ecx
333     # push Trace-stream->read
334     ff          6/subop/push        1/mod/*+disp8   6/rm32/esi    .           .             .           .           4/disp8         .                 # push *(esi+4)
335 $trace-scan:loop:
336     # if (Trace-stream->read >= Trace-stream->write) return false
337     39/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # compare ecx with *(esi+4)
338     7d/jump-if->=  $trace-scan:false/disp8
339     # eax = next-line-matches?(Trace-stream, line)
340     # . . push args
341     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
342     56/push-esi
343     # . . call
344     e8/call  next-line-matches?/disp32
345     # . . discard args
346     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
347     # if (eax == false) continue
348     3d/compare-eax-and  0/imm32/false
349     74/jump-if-=  $trace-scan:continue/disp8
350 $trace-scan:true:
351     # skip-next-line(Trace-stream)
352     # . . push args
353     56/push-esi
354     # . . call
355     e8/call  skip-next-line/disp32
356     # . . discard args
357     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
358     # dump saved copy of Trace-stream->read
359     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
360     # return true
361     b8/copy-to-eax  1/imm32/true
362     eb/jump  $trace-scan:end/disp8
363 $trace-scan:continue:
364     # skip-next-line(Trace-stream)
365     # . . push args
366     56/push-esi
367     # . . call
368     e8/call  skip-next-line/disp32
369     # . . discard args
370     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
371     eb/jump  $trace-scan:loop/disp8
372 $trace-scan:false:
373     # restore saved copy of Trace-stream->read
374     8f          0/subop/pop         1/mod/*+disp8   6/rm32/esi    .           .             .           .           4/disp8         .                 # pop to *(esi+4)
375     # return false
376     b8/copy-to-eax  0/imm32/false
377 $trace-scan:end:
378     # . restore registers
379     59/pop-to-ecx
380     # . epilogue
381     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
382     5d/pop-to-ebp
383     c3/return
384 
385 test-trace-scan-first:
386     # push *Trace-stream
387     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
388     # setup
389     # . *Trace-stream = _test-trace-stream
390     b8/copy-to-eax  _test-trace-stream/imm32
391     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Trace-stream/disp32               # copy eax to *Trace-stream
392     # . clear-trace-stream()
393     e8/call  clear-trace-stream/disp32
394     # . trace("Ab")
395     # . . push args
396     68/push  "Ab"/imm32
397     # . . call
398     e8/call  trace/disp32
399     # . . discard args
400     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
401     # eax = trace-scan("Ab")
402     # . . push args
403     68/push  "Ab"/imm32
404     # . . call
405     e8/call  trace-scan/disp32
406     # . . discard args
407     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
408     # check-ints-equal(eax, 1, msg)
409     # . . push args
410     68/push  "F - test-trace-scan-first"/imm32
411     68/push  1/imm32
412     50/push-eax
413     # . . call
414     e8/call check-ints-equal/disp32
415     # . . discard args
416     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
417     # pop into *Trace-stream
418     8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
419     # . end
420     c3/return
421 
422 test-trace-scan-skips-lines-until-found:
423     # push *Trace-stream
424     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
425     # setup
426     # . *Trace-stream = _test-trace-stream
427     b8/copy-to-eax  _test-trace-stream/imm32
428     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Trace-stream/disp32               # copy eax to *Trace-stream
429     # . clear-trace-stream()
430     e8/call  clear-trace-stream/disp32
431     # . trace("Ab")
432     # . . push args
433     68/push  "Ab"/imm32
434     # . . call
435     e8/call  trace/disp32
436     # . . discard args
437     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
438     # . trace("cd")
439     # . . push args
440     68/push  "cd"/imm32
441     # . . call
442     e8/call  trace/disp32
443     # . . discard args
444     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
445     # eax = trace-scan("cd")
446     # . . push args
447     68/push  "cd"/imm32
448     # . . call
449     e8/call  trace-scan/disp32
450     # . . discard args
451     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
452     # check-ints-equal(eax, 1, msg)
453     # . . push args
454     68/push  "F - test-trace-scan-skips-lines-until-found"/imm32
455     68/push  1/imm32
456     50/push-eax
457     # . . call
458     e8/call check-ints-equal/disp32
459     # . . discard args
460     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
461     # pop into *Trace-stream
462     8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
463     # . end
464     c3/return
465 
466 test-trace-second-scan-starts-where-first-left-off:
467     # push *Trace-stream
468     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
469     # setup
470     # . *Trace-stream = _test-trace-stream
471     b8/copy-to-eax  _test-trace-stream/imm32
472     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Trace-stream/disp32               # copy eax to *Trace-stream
473     # . clear-trace-stream()
474     e8/call  clear-trace-stream/disp32
475     # . trace("Ab")
476     # . . push args
477     68/push  "Ab"/imm32
478     # . . call
479     e8/call  trace/disp32
480     # . . discard args
481     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
482     # . eax = trace-scan("Ab")
483     # . . push args
484     68/push  "Ab"/imm32
485     # . . call
486     e8/call  trace-scan/disp32
487     # . . discard args
488     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
489     # second scan fails
490     # . eax = trace-scan("Ab")
491     # . . push args
492     68/push  "Ab"/imm32
493     # . . call
494     e8/call  trace-scan/disp32
495     # . . discard args
496     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
497     # check-ints-equal(eax, 0, msg)
498     # . . push args
499     68/push  "F - test-trace-second-scan-starts-where-first-left-off"/imm32
500     68/push  0/imm32
501     50/push-eax
502     # . . call
503     e8/call check-ints-equal/disp32
504     # . . discard args
505     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
506     # pop into *Trace-stream
507     8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
508     # . end
509     c3/return
510 
511 test-trace-scan-failure-leaves-read-index-untouched:
512     # push *Trace-stream
513     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
514     # setup
515     # . *Trace-stream = _test-trace-stream
516     b8/copy-to-eax  _test-trace-stream/imm32
517     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Trace-stream/disp32               # copy eax to *Trace-stream
518     # . clear-trace-stream()
519     e8/call  clear-trace-stream/disp32
520     # . trace("Ab")
521     # . . push args
522     68/push  "Ab"/imm32
523     # . . call
524     e8/call  trace/disp32
525     # . . discard args
526     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
527     # . check-ints-equal(_test-trace-stream->read, 0, msg)
528     # . . push args
529     68/push  "F - test-trace-second-scan-starts-where-first-left-off/precondition-failure"/imm32
530     68/push  0/imm32
531     b8/copy-to-eax  _test-trace-stream/imm32
532     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
533     # . . call
534     e8/call check-ints-equal/disp32
535     # . . discard args
536     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
537     # perform a failing scan
538     # . eax = trace-scan("Ax")
539     # . . push args
540     68/push  "Ax"/imm32
541     # . . call
542     e8/call  trace-scan/disp32
543     # . . discard args
544     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
545     # no change in read index
546     # . check-ints-equal(_test-trace-stream->read, 0, msg)
547     # . . push args
548     68/push  "F - test-trace-second-scan-starts-where-first-left-off"/imm32
549     68/push  0/imm32
550     b8/copy-to-eax  _test-trace-stream/imm32
551     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
552     # . . call
553     e8/call check-ints-equal/disp32
554     # . . discard args
555     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
556     # pop into *Trace-stream
557     8f          0/subop/pop         0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # pop into *Trace-stream
558     # . end
559     c3/return
560 
561 next-line-matches?:  # t: (addr stream byte), line: (addr array byte) -> result/eax: boolean
562     # pseudocode:
563     #   while true:
564     #     if (currl >= maxl) break
565     #     if (currt >= maxt) return false
566     #     if (*currt != *currl) return false
567     #     ++currt
568     #     ++currl
569     #   return *currt == '\n'
570     #
571     # . prologue
572     55/push-ebp
573     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
574     # . save registers
575     51/push-ecx
576     52/push-edx
577     53/push-ebx
578     56/push-esi
579     57/push-edi
580     # edx = line
581     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
582     # var currl/esi: (addr byte) = line->data
583     # . esi = line/edx->data
584     8d/copy-address                 1/mod/*+disp8   2/rm32/edx    .           .             .           6/r32/esi   4/disp8         .                 # copy edx+4 to esi
585     # var maxl/ecx: (addr byte) = &line->data[line->size]
586     # . eax = line/edx->size
587     8b/copy                         0/mod/indirect  2/rm32/edx    .           .                         0/r32/eax   .               .                 # copy *edx to eax
588     # . maxl = &line->data[line->size]
589     8d/copy-address                 0/mod/indirect  4/rm32/sib    6/base/esi  0/index/eax   .           1/r32/ecx   .               .                 # copy edx+eax to ecx
590     # edi = t
591     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
592     # var ebx: (addr byte) = t->data
593     8d/copy-address                 1/mod/*+disp8   7/rm32/edi    .           .             .           3/r32/ebx   0xc/disp8       .                 # copy edi+12 to ebx
594     # var maxt/edx: (addr byte) = &t->data[t->write]
595     # . eax = t->write
596     8b/copy                         0/mod/indirect  7/rm32/edi    .           .                         0/r32/eax   .               .                 # copy *edi to eax
597     # . maxt = &t->data[t->write]
598     8d/copy-address                 0/mod/indirect  4/rm32/sib    3/base/ebx  0/index/eax   .           2/r32/edx   .               .                 # copy ebx+eax to edx
599     # var currt/edi: (addr byte) = &t->data[t->read]
600     # . eax = t/edi->read
601     8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .                         0/r32/eax   4/disp8         .                 # copy *(edi+4) to eax
602     # . currt = &t->data[t->read]
603     8d/copy-address                 0/mod/indirect  4/rm32/sib    3/base/ebx  0/index/eax   .           7/r32/edi   .               .                 # copy ebx+eax to edi
604 $next-line-matches?:loop:
605     # if (currl >= maxl) break
606     39/compare                      3/mod/direct    6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # compare esi and ecx
607     73/jump-if-addr>=  $next-line-matches?:break/disp8
608     # if (currt >= maxt) return false
609     # . eax = false
610     b8/copy-to-eax  0/imm32/false
611     39/compare                      3/mod/direct    7/rm32/edi    .           .             .           2/r32/edx   .               .                 # compare edi and edx
612     73/jump-if-addr>=  $next-line-matches?:end/disp8
613     # if (*currt != *currl) return false
614     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
615     31/xor                          3/mod/direct    3/rm32/eax    .           .             .           3/r32/eax   .               .                 # clear ebx
616     # . eax: byte = *currt
617     8a/copy-byte                    0/mod/indirect  7/rm32/edi    .           .                         0/r32/eax   .               .                 # copy *edi to eax
618     # . ebx: byte = *currl
619     8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .                         3/r32/ebx   .               .                 # copy *esi to ebx
620     # . eax >= ebx
621     39/compare                      3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # compare eax and ebx
622     # . eax = false
623     b8/copy-to-eax  0/imm32/false
624     75/jump-if-!=  $next-line-matches?:end/disp8
625     # ++currt
626     47/increment-edi
627     # ++currl
628     46/increment-esi
629     eb/jump  $next-line-matches?:loop/disp8
630 $next-line-matches?:break:
631     # return *currt == '\n'
632     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
633     # . eax: byte = *currt
634     8a/copy-byte                    0/mod/indirect  7/rm32/edi    .           .                         0/r32/eax   .               .                 # copy *edi to eax
635     3d/compare-eax-and  0xa/imm32/newline
636     # . eax = false
637     b8/copy-to-eax  1/imm32/true
638     74/jump-if-=  $next-line-matches?:end/disp8
639     b8/copy-to-eax  0/imm32/true
640 $next-line-matches?:end:
641     # . restore registers
642     5f/pop-to-edi
643     5e/pop-to-esi
644     5b/pop-to-ebx
645     5a/pop-to-edx
646     59/pop-to-ecx
647     # . epilogue
648     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
649     5d/pop-to-ebp
650     c3/return
651 
652 test-next-line-matches?-no-match-1:
653     # next line of "ABABA" does not match "blah blah"
654     # . eax = next-line-matches?(_test-stream-line-ABABA, "blah blah")
655     # . . push args
656     68/push  "blah blah"/imm32
657     68/push  _test-stream-line-ABABA/imm32
658     # . . call
659     e8/call  next-line-matches?/disp32
660     # . . discard args
661     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
662     # . check-ints-equal(eax, 0, msg)
663     # . . push args
664     68/push  "F - test-next-line-matches?-no-match-1"/imm32
665     68/push  0/imm32
666     50/push-eax
667     # . . call
668     e8/call  check-ints-equal/disp32
669     # . . discard args
670     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
671     c3/return
672 
673 test-next-line-matches?-no-match-2:
674     # next line of "ABABA" does not match ""
675     # . eax = next-line-matches?(_test-stream-line-ABABA, "")
676     # . . push args
677     68/push  ""/imm32
678     68/push  _test-stream-line-ABABA/imm32
679     # . . call
680     e8/call  next-line-matches?/disp32
681     # . . discard args
682     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
683     # . check-ints-equal(eax, 0, msg)
684     # . . push args
685     68/push  "F - test-next-line-matches?-no-match-2"/imm32
686     68/push  0/imm32
687     50/push-eax
688     # . . call
689     e8/call  check-ints-equal/disp32
690     # . . discard args
691     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
692     c3/return
693 
694 test-next-line-matches?-no-match-3:
695     # next line of "ABABA" does not match  "AA"
696     # . eax = next-line-matches?(_test-stream-line-ABABA, "AA")
697     # . . push args
698     68/push  "AA"/imm32
699     68/push  _test-stream-line-ABABA/imm32
700     # . . call
701     e8/call  next-line-matches?/disp32
702     # . . discard args
703     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
704     # . check-ints-equal(eax, 0, msg)
705     # . . push args
706     68/push  "F - test-next-line-matches?-no-match-3"/imm32
707     68/push  0/imm32
708     50/push-eax
709     # . . call
710     e8/call  check-ints-equal/disp32
711     # . . discard args
712     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
713     c3/return
714 
715 test-next-line-matches?-match:
716     # next line of "ABABA" matches "ABABA"
717     # . eax = next-line-matches?(_test-stream-line-ABABA, "ABABA")
718     # . . push args
719     68/push  "ABABA"/imm32
720     68/push  _test-stream-line-ABABA/imm32
721     # . . call
722     e8/call  next-line-matches?/disp32
723     # . . discard args
724     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
725     # . check-ints-equal(eax, 1, msg)
726     # . . push args
727     68/push  "F - test-next-line-matches?-match"/imm32
728     68/push  1/imm32
729     50/push-eax
730     # . . call
731     e8/call  check-ints-equal/disp32
732     # . . discard args
733     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
734     c3/return
735 
736 # move t->read to _after_ next newline
737 skip-next-line:  # t: (addr stream byte)
738     # pseudocode:
739     #   max = &t->data[t->write]
740     #   i = t->read
741     #   curr = &t->data[t->read]
742     #   while true
743     #     if (curr >= max) break
744     #     ++i
745     #     if (*curr == '\n') break
746     #     ++curr
747     #   t->read = i
748     #
749     # . prologue
750     55/push-ebp
751     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
752     # . save registers
753     50/push-eax
754     51/push-ecx
755     52/push-edx
756     53/push-ebx
757     # ecx = t
758     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
759     # edx = t->data
760     8d/copy-address                 1/mod/*+disp8   1/rm32/ecx    .           .             .           2/r32/edx   0xc/disp8       .                 # copy ecx+12 to edx
761     # eax = t->write
762     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
763     # var max/ebx: (addr byte) = &t->data[t->write]
764     8d/copy-address                 0/mod/indirect  4/rm32/sib    2/base/edx  0/index/eax   .           3/r32/ebx   .               .                 # copy edx+eax to ebx
765     # eax = t->read
766     8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to edx
767     # var curr/ecx: (addr byte) = &t->data[t->read]
768     8d/copy-address                 0/mod/indirect  4/rm32/sib    2/base/edx  0/index/eax   .           1/r32/ecx   .               .                 # copy edx+eax to ecx
769     # var i/edx: int = t->read
770     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy eax to edx
771 $skip-next-line:loop:
772     # if (curr >= max) break
773     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # compare ecx and ebx
774     73/jump-if-addr>=  $skip-next-line:end/disp8
775     # ++i
776     42/increment-edx
777     # if (*curr == '\n') break
778     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
779     8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
780     3d/compare-eax-and  0a/imm32/newline
781     74/jump-if-=  $skip-next-line:end/disp8
782     # ++curr
783     41/increment-ecx
784     # loop
785     eb/jump  $skip-next-line:loop/disp8
786 $skip-next-line:end:
787     # ecx = t
788     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
789     # t->read = i
790     89/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           2/r32/edx   4/disp8         .                 # copy edx to *(ecx+4)
791     # . restore registers
792     5b/pop-to-ebx
793     5a/pop-to-edx
794     59/pop-to-ecx
795     58/pop-to-eax
796     # . epilogue
797     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
798     5d/pop-to-ebp
799     c3/return
800 
801 test-skip-next-line-empty:
802     # skipping next line in empty stream leaves read pointer at 0
803     # . skip-next-line(_test-stream-empty)
804     # . . push args
805     68/push  _test-stream-empty/imm32
806     # . . call
807     e8/call  skip-next-line/disp32
808     # . . discard args
809     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
810     # . check-ints-equal(_test-stream-empty->read, 0, msg)
811     # . . push args
812     68/push  "F - test-skip-next-line-empty"/imm32
813     68/push  0/imm32
814     b8/copy-to-eax  _test-stream-empty/imm32
815     8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           0/r32/eax   4/disp8         .                 # copy *(eax+4) to eax
816     50/push-eax
817     # . . call
818     e8/call  check-ints-equal/disp32
819     # . . discard args
820     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
821     c3/return
822 
823 test-skip-next-line-filled:
824     # skipping next line increments read pointer by length of line + 1 (for newline)
825     # . skip-next-line(_test-stream-filled)
826     # . . push args
827     68/push  _test-stream-filled/imm32
828     # . . call
829     e8/call  skip-next-line/disp32
830     # . . discard args
831     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
832     # . check-ints-equal(_test-stream-filled->read, 5, msg)
833     # . . push args
834     68/push  "F - test-skip-next-line-filled"/imm32
835     68/push  5/imm32
836     b8/copy-to-eax  _test-stream-filled/imm32
837     8b/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           0/r32/eax   4/disp8         .                 # copy *(eax+4) to eax
838     50/push-eax
839     # . . call
840     e8/call  check-ints-equal/disp32
841     # . . discard args
842     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
843     c3/return
844 
845 clear-trace-stream:
846     # . prologue
847     55/push-ebp
848     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
849     # clear-stream(*Trace-stream)
850     # . . push args
851     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
852     # . . call
853     e8/call  clear-stream/disp32
854     # . . discard args
855     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
856 $clear-trace-stream:end:
857     # . epilogue
858     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
859     5d/pop-to-ebp
860     c3/return
861 
862 # - helpers
863 
864 # 3-argument variant of _append
865 _append-3:  # out: (addr byte), outend: (addr byte), s: (addr array byte) -> num_bytes_appended/eax
866     # . prologue
867     55/push-ebp
868     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
869     # . save registers
870     51/push-ecx
871     # eax = _append-4(out, outend, &s->data[0], &s->data[s->size])
872     # . . push &s->data[s->size]
873     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         0/r32/eax   0x10/disp8      .                 # copy *(ebp+16) to eax
874     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
875     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
876     51/push-ecx
877     # . . push &s->data[0]
878     8d/copy-address                 1/mod/*+disp8   0/rm32/eax    .           .             .           1/r32/ecx   4/disp8         .                 # copy eax+4 to ecx
879     51/push-ecx
880     # . . push outend
881     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
882     # . . push out
883     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
884     # . . call
885     e8/call  _append-4/disp32
886     # . . discard args
887     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
888 $_append-3:end:
889     # . restore registers
890     59/pop-to-ecx
891     # . epilogue
892     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
893     5d/pop-to-ebp
894     c3/return
895 
896 # 4-argument variant of _append
897 _append-4:  # out: (addr byte), outend: (addr byte), in: (addr byte), inend: (addr byte) -> num_bytes_appended/eax: int
898     # . prologue
899     55/push-ebp
900     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
901     # . save registers
902     51/push-ecx
903     52/push-edx
904     53/push-ebx
905     56/push-esi
906     57/push-edi
907     # num_bytes_appended = 0
908     b8/copy-to-eax  0/imm32
909     # edi = out
910     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
911     # edx = outend
912     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
913     # esi = in
914     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   0x10/disp8      .                 # copy *(ebp+16) to esi
915     # ecx = inend
916     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0x14/disp8      .                 # copy *(ebp+20) to ecx
917 $_append-4:loop:
918     # if (in >= inend) break
919     39/compare                      3/mod/direct    6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # compare esi with ecx
920     73/jump-if-addr>=  $_append-4:end/disp8
921     # if (out >= outend) abort  # just to catch test failures fast
922     39/compare                      3/mod/direct    7/rm32/edi    .           .             .           2/r32/edx   .               .                 # compare edi with edx
923     73/jump-if-addr>=  $_append-4:abort/disp8
924     # *out = *in
925     8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           3/r32/BL    .               .                 # copy byte at *esi to BL
926     88/copy-byte                    0/mod/indirect  7/rm32/edi    .           .             .           3/r32/BL    .               .                 # copy byte at BL to *edi
927     # ++num_bytes_appended
928     40/increment-eax
929     # ++in
930     46/increment-esi
931     # ++out
932     47/increment-edi
933     eb/jump  $_append-4:loop/disp8
934 $_append-4:end:
935     # . restore registers
936     5f/pop-to-edi
937     5e/pop-to-esi
938     5b/pop-to-ebx
939     5a/pop-to-edx
940     59/pop-to-ecx
941     # . epilogue
942     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
943     5d/pop-to-ebp
944     c3/return
945 
946 $_append-4:abort:
947     # . _write(2/stderr, error)
948     # . . push args
949     68/push  "stream overflow\n"/imm32
950     68/push  2/imm32/stderr
951     # . . call
952     e8/call  _write/disp32
953     # . . discard args
954     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
955     # . syscall(exit, 1)
956     bb/copy-to-ebx  1/imm32
957     e8/call  syscall_exit/disp32
958     # never gets here
959 
960 == data
961 
962 _test-stream-line-ABABA:  # (stream byte)
963     # write
964     8/imm32
965     # read
966     0/imm32
967     # size
968     8/imm32
969     # data
970     41 42 41 42 41 0a 00 00  # 8 bytes
971 
972 _test-stream-empty:  # (stream byte)
973     # write
974     0/imm32
975     # read
976     0/imm32
977     # size
978     8/imm32
979     # data
980     00 00 00 00 00 00 00 00  # 8 bytes
981 
982 _test-stream-filled:  # (stream byte)
983     # write
984     8/imm32
985     # read
986     0/imm32
987     # size
988     8/imm32
989     # data
990     41 41 41 41 0a 41 41 41  # 8 bytes
991 
992 # . . vim:nowrap:textwidth=0