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