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