https://github.com/akkartik/mu/blob/master/064write-byte.subx
  1 # write-byte-buffered: add a single byte to a buffered-file.
  2 # flush: write out any buffered writes to disk.
  3 #
  4 # TODO: Come up with a way to signal failure to write to disk. This is hard
  5 # since the failure may impact previous calls that were buffered.
  6 
  7 == data
  8 
  9 # The buffered file for standard output.
 10 Stdout:
 11     # file descriptor or (address stream)
 12     1/imm32  # standard output
 13 Stdout->buffer:
 14     # inlined fields for a stream
 15     #   current write index
 16     0/imm32
 17     #   current read index
 18     0/imm32
 19     #   length
 20     8/imm32
 21     #   data
 22     00 00 00 00 00 00 00 00  # 8 bytes
 23 
 24 # TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency. But
 25 # I don't want to type in 1024 bytes here.
 26 
 27 == code
 28 #   instruction                     effective address                                                   register    displacement    immediate
 29 # . op          subop               mod             rm32          base        index         scale       r32
 30 # . 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
 31 
 32 # Write lower byte of 'n' to 'f'.
 33 write-byte-buffered:  # f : (address buffered-file), n : int
 34     # . prologue
 35     55/push-ebp
 36     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 37     # . save registers
 38     51/push-ecx
 39     57/push-edi
 40     # edi = f
 41     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
 42     # ecx = f->write
 43     8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(edi+4) to ecx
 44     # if (f->write >= f->length) flush and clear f's stream
 45     3b/compare                      1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   0xc/disp8       .                 # compare ecx with *(edi+12)
 46     7c/jump-if-lesser  $write-byte-buffered:to-stream/disp8
 47     # . flush(f)
 48     # . . push args
 49     57/push-edi
 50     # . . call
 51     e8/call  flush/disp32
 52     # . . discard args
 53     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 54     # . clear-stream(stream = f+4)
 55     # . . push args
 56     8d/copy-address                 1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy edi+4 to eax
 57     50/push-eax
 58     # . . call
 59     e8/call  clear-stream/disp32
 60     # . . discard args
 61     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 62     # . f->write must now be 0; update its cache at ecx
 63     31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
 64 $write-byte-buffered:to-stream:
 65     # write to stream
 66     # f->data[f->write] = LSB(n)
 67     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 68     8a/copy-byte                    1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/AL    0xc/disp8       .                 # copy byte at *(ebp+12) to AL
 69     88/copy-byte                    1/mod/*+disp8   4/rm32/sib    7/base/edi  1/index/ecx   .           0/r32/AL    0x10/disp8      .                 # copy AL to *(edi+ecx+16)
 70     # ++f->write
 71     ff          0/subop/increment   1/mod/*+disp8   7/rm32/edi    .           .             .           .           4/disp8         .                 # increment *(edi+4)
 72 $write-byte-buffered:end:
 73     # . restore registers
 74     5f/pop-to-edi
 75     59/pop-to-ecx
 76     # . epilogue
 77     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 78     5d/pop-to-ebp
 79     c3/return
 80 
 81 flush:  # f : (address buffered-file)
 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     # eax = f
 89     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
 90     # write-stream(f->fd, data = f+4)
 91       # . . push args
 92     8d/copy-address                 1/mod/*+disp8   0/rm32/eax    .           .             .           1/r32/ecx   4/disp8         .                 # copy eax+4 to ecx
 93     51/push-ecx
 94     ff          6/subop/push        0/mod/indirect  0/rm32/eax    .           .             .           .           .               .                 # push *eax
 95       # . . call
 96     e8/call  write-stream/disp32
 97       # . . discard args
 98     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 99 $flush:end:
100     # . restore registers
101     59/pop-to-ecx
102     58/pop-to-eax
103     # . epilogue
104     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
105     5d/pop-to-ebp
106     c3/return
107 
108 test-write-byte-buffered-single:
109     # - check that write-byte-buffered writes to first byte of 'file'
110     # setup
111     # . clear-stream(_test-stream)
112     # . . push args
113     68/push  _test-stream/imm32
114     # . . call
115     e8/call  clear-stream/disp32
116     # . . discard args
117     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
118     # . clear-stream(_test-buffered-file->buffer)
119     # . . push args
120     68/push  _test-buffered-file->buffer/imm32
121     # . . call
122     e8/call  clear-stream/disp32
123     # . . discard args
124     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
125     # write-byte-buffered(_test-buffered-file, 'A')
126     # . . push args
127     68/push  0x41/imm32
128     68/push  _test-buffered-file/imm32
129     # . . call
130     e8/call  write-byte-buffered/disp32
131     # . . discard args
132     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
133     # flush(_test-buffered-file)
134     # . . push args
135     68/push  _test-buffered-file/imm32
136     # . . call
137     e8/call  flush/disp32
138     # . . discard args
139     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
140     # check-stream-equal(_test-stream, "A", msg)
141     # . . push args
142     68/push  "F - test-write-byte-buffered-single"/imm32
143     68/push  "A"/imm32
144     68/push  _test-stream/imm32
145     # . . call
146     e8/call  check-stream-equal/disp32
147     # . . discard args
148     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
149     # . end
150     c3/return
151 
152 test-write-byte-buffered-multiple-flushes:
153     # - check that write-byte-buffered correctly flushes buffered data
154     # setup
155     # . clear-stream(_test-stream)
156     # . . push args
157     68/push  _test-stream/imm32
158     # . . call
159     e8/call  clear-stream/disp32
160     # . . discard args
161     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
162     # . clear-stream(_test-buffered-file->buffer)
163     # . . push args
164     68/push  _test-buffered-file->buffer/imm32
165     # . . call
166     e8/call  clear-stream/disp32
167     # . . discard args
168     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
169     # fill up the buffer for _test-buffered-file
170     # . write(_test-buffered-file->buffer, "abcdef")
171     # . . push args
172     68/push  "abcdef"/imm32
173     68/push  _test-buffered-file->buffer/imm32
174     # . . call
175     e8/call  write/disp32
176     # . . discard args
177     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
178     # write-byte-buffered(_test-buffered-file, 'g')
179     # . . push args
180     68/push  0x67/imm32
181     68/push  _test-buffered-file/imm32
182     # . . call
183     e8/call  write-byte-buffered/disp32
184     # . . discard args
185     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
186     # flush(_test-buffered-file)
187     # . . push args
188     68/push  _test-buffered-file/imm32
189     # . . call
190     e8/call  flush/disp32
191     # . . discard args
192     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
193     # check-stream-equal(_test-stream, "abcdefg", msg)
194     # . . push args
195     68/push  "F - test-write-byte-buffered-multiple-flushes"/imm32
196     68/push  "abcdefg"/imm32
197     68/push  _test-stream/imm32
198     # . . call
199     e8/call  check-stream-equal/disp32
200     # . . discard args
201     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
202     # . end
203     c3/return
204 
205 # - variant without buffering
206 
207 # Write lower byte of 'n' to 'f'.
208 append-byte:  # f : (address stream), n : int
209     # . prologue
210     55/push-ebp
211     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
212     # . save registers
213     51/push-ecx
214     57/push-edi
215     # edi = f
216     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
217     # ecx = f->write
218     8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # copy *edi to ecx
219     # if (f->write >= f->length) abort
220     3b/compare                      1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   8/disp8         .                 # compare ecx with *(edi+8)
221     7d/jump-if-greater-or-equal  $append-byte:abort/disp8
222 $append-byte:to-stream:
223     # write to stream
224     # f->data[f->write] = LSB(n)
225     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
226     8a/copy-byte                    1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/AL    0xc/disp8       .                 # copy byte at *(ebp+12) to AL
227     88/copy-byte                    1/mod/*+disp8   4/rm32/sib    7/base/edi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy AL to *(edi+ecx+12)
228     # ++f->write
229     ff          0/subop/increment   0/mod/indirect  7/rm32/edi    .           .             .           .           .               .                 # increment *edi
230 $append-byte:end:
231     # . restore registers
232     5f/pop-to-edi
233     59/pop-to-ecx
234     # . epilogue
235     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
236     5d/pop-to-ebp
237     c3/return
238 
239 $append-byte:abort:
240     # . _write(2/stderr, error)
241     # . . push args
242     68/push  "append-byte: out of space\n"/imm32
243     68/push  2/imm32/stderr
244     # . . call
245     e8/call  _write/disp32
246     # . . discard args
247     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
248     # . syscall(exit, 1)
249     bb/copy-to-ebx  1/imm32
250     b8/copy-to-eax  1/imm32/exit
251     cd/syscall  0x80/imm8
252     # never gets here
253 
254 test-append-byte-single:
255     # - check that append-byte writes to first byte of 'file'
256     # setup
257     # . clear-stream(_test-stream)
258     # . . push args
259     68/push  _test-stream/imm32
260     # . . call
261     e8/call  clear-stream/disp32
262     # . . discard args
263     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
264     # append-byte(_test-stream, 'A')
265     # . . push args
266     68/push  0x41/imm32
267     68/push  _test-stream/imm32
268     # . . call
269     e8/call  append-byte/disp32
270     # . . discard args
271     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
272     # check-stream-equal(_test-stream, "A", msg)
273     # . . push args
274     68/push  "F - test-append-byte-single"/imm32
275     68/push  "A"/imm32
276     68/push  _test-stream/imm32
277     # . . call
278     e8/call  check-stream-equal/disp32
279     # . . discard args
280     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
281     # . end
282     c3/return
283 
284 == data
285 
286 _test-output-stream:
287     # current write index
288     0/imm32
289     # current read index
290     0/imm32
291     # length
292     0x200/imm32  # 512 bytes
293     # data (32 lines x 16 bytes/line)
294     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
295     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
296     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
297     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
298     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
299     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
300     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
301     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
302     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
303     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
304     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
305     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
306     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
307     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
308     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
309     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
310     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
311     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
312     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
313     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
314     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
315     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
316     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
317     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
318     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
319     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
320     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
321     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
322     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
323     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
324     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
325     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
326 
327 # a test buffered file for _test-output-stream
328 _test-output-buffered-file:
329     # file descriptor or (address stream)
330     _test-output-stream/imm32
331 _test-output-buffered-file->buffer:
332     # current write index
333     0/imm32
334     # current read index
335     0/imm32
336     # length
337     6/imm32
338     # data
339     00 00 00 00 00 00  # 6 bytes
340 
341 _test-error-stream:
342     # current write index
343     0/imm32
344     # current read index
345     0/imm32
346     # line
347     0x80/imm32  # 128 bytes
348     # data (8 lines x 16 bytes/line)
349     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
350     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
351     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
352     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
353     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
354     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
355     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
356     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
357 
358 # a test buffered file for _test-error-stream
359 _test-error-buffered-file:
360     # file descriptor or (address stream)
361     _test-error-stream/imm32
362 _test-error-buffered-file->buffer:
363     # current write index
364     0/imm32
365     # current read index
366     0/imm32
367     # length
368     6/imm32
369     # data
370     00 00 00 00 00 00  # 6 bytes
371 
372 # . . vim:nowrap:textwidth=0