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     # current write index
 14     0/imm32
 15     # current read index
 16     0/imm32
 17     # length
 18     8/imm32
 19     # data
 20     00 00 00 00 00 00 00 00  # 8 bytes
 21 
 22 # TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency. But
 23 # I don't want to type in 1024 bytes here.
 24 
 25 == code
 26 #   instruction                     effective address                                                   register    displacement    immediate
 27 # . op          subop               mod             rm32          base        index         scale       r32
 28 # . 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
 29 
 30 # Write lower byte of 'n' to 'f'.
 31 write-byte-buffered:  # f : (address buffered-file), n : int -> <void>
 32     # . prolog
 33     55/push-ebp
 34     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 35     # . save registers
 36     51/push-ecx
 37     57/push-edi
 38     # edi = f
 39     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
 40     # ecx = f->write
 41     8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(edi+4) to ecx
 42     # if (f->write >= f->length) flush and clear f's stream
 43     3b/compare                      1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   0xc/disp8       .                 # compare ecx with *(edi+12)
 44     7c/jump-if-lesser  $write-byte-buffered:to-stream/disp8
 45     # . flush(f)
 46     # . . push args
 47     57/push-edi
 48     # . . call
 49     e8/call  flush/disp32
 50     # . . discard args
 51     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 52     # . clear-stream(stream = f+4)
 53     # . . push args
 54     8d/copy-address                 1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy edi+4 to eax
 55     50/push-eax
 56     # . . call
 57     e8/call  clear-stream/disp32
 58     # . . discard args
 59     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 60     # . f->write must now be 0; update its cache at ecx
 61     31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
 62 $write-byte-buffered:to-stream:
 63     # write to stream
 64     # f->data[f->write] = LSB(n)
 65     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 66     8a/copy-byte                    1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/AL    0xc/disp8       .                 # copy byte at *(ebp+12) to AL
 67     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)
 68     # ++f->write
 69     ff          0/subop/increment   1/mod/*+disp8   7/rm32/edi    .           .             .           .           4/disp8         .                 # increment *(edi+4)
 70 $write-byte-buffered:end:
 71     # . restore registers
 72     5f/pop-to-edi
 73     59/pop-to-ecx
 74     # . epilog
 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 flush:  # f : (address buffered-file) -> <void>
 80     # . prolog
 81     55/push-ebp
 82     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 83     # . save registers
 84     50/push-eax
 85     51/push-ecx
 86     # eax = f
 87     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
 88     # write-stream(f->fd, data = f+4)
 89       # . . push args
 90     8d/copy-address                 1/mod/*+disp8   0/rm32/eax    .           .             .           1/r32/ecx   4/disp8         .                 # copy eax+4 to ecx
 91     51/push-ecx
 92     ff          6/subop/push        0/mod/indirect  0/rm32/eax    .           .             .           .           .               .                 # push *eax
 93       # . . call
 94     e8/call  write-stream/disp32
 95       # . . discard args
 96     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 97 $flush:end:
 98     # . restore registers
 99     59/pop-to-ecx
100     58/pop-to-eax
101     # . epilog
102     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
103     5d/pop-to-ebp
104     c3/return
105 
106 test-write-byte-buffered-single:
107     # - check that write-byte-buffered writes to first byte of 'file'
108     # setup
109     # . clear-stream(_test-stream)
110     # . . push args
111     68/push  _test-stream/imm32
112     # . . call
113     e8/call  clear-stream/disp32
114     # . . discard args
115     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
116     # . clear-stream(_test-buffered-file+4)
117     # . . push args
118     b8/copy-to-eax  _test-buffered-file/imm32
119     05/add-to-eax  4/imm32
120     50/push-eax
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+4)
163     # . . push args
164     b8/copy-to-eax  _test-buffered-file/imm32
165     05/add-to-eax  4/imm32
166     50/push-eax
167     # . . call
168     e8/call  clear-stream/disp32
169     # . . discard args
170     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
171     # fill up the buffer for _test-buffered-file
172     # . write(_test-buffered-file+4, 'abcdef')
173     # . . push args
174     68/push  "abcdef"/imm32
175     b8/copy-to-eax  _test-buffered-file/imm32
176     05/add-to-eax  4/imm32
177     50/push-eax
178     # . . call
179     e8/call  write/disp32
180     # . . discard args
181     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
182     # write-byte-buffered(_test-buffered-file, 'g')
183     # . . push args
184     68/push  0x67/imm32
185     68/push  _test-buffered-file/imm32
186     # . . call
187     e8/call  write-byte-buffered/disp32
188     # . . discard args
189     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
190     # flush(_test-buffered-file)
191     # . . push args
192     68/push  _test-buffered-file/imm32
193     # . . call
194     e8/call  flush/disp32
195     # . . discard args
196     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
197     # check-stream-equal(_test-stream, "abcdef", msg)
198     # . . push args
199     68/push  "F - test-write-byte-buffered-multiple-flushes: 1"/imm32
200     68/push  "abcdefg"/imm32
201     68/push  _test-stream/imm32
202     # . . call
203     e8/call  check-stream-equal/disp32
204     # . . discard args
205     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
206     # . end
207     c3/return
208 
209 # - variant without buffering
210 
211 # Write lower byte of 'n' to 'f'.
212 append-byte:  # f : (address stream), n : int -> <void>
213     # . prolog
214     55/push-ebp
215     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
216     # . save registers
217     51/push-ecx
218     57/push-edi
219     # edi = f
220     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
221     # ecx = f->write
222     8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # copy *edi to ecx
223     # if (f->write >= f->length) abort
224     3b/compare                      1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   8/disp8         .                 # compare ecx with *(edi+8)
225     7d/jump-if-greater-or-equal  $append-byte:abort/disp8
226 $append-byte:to-stream:
227     # write to stream
228     # f->data[f->write] = LSB(n)
229     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
230     8a/copy-byte                    1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/AL    0xc/disp8       .                 # copy byte at *(ebp+12) to AL
231     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)
232     # ++f->write
233     ff          0/subop/increment   0/mod/indirect  7/rm32/edi    .           .             .           .           .               .                 # increment *edi
234 $append-byte:end:
235     # . restore registers
236     5f/pop-to-edi
237     59/pop-to-ecx
238     # . epilog
239     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
240     5d/pop-to-ebp
241     c3/return
242 
243 $append-byte:abort:
244     # . _write(2/stderr, error)
245     # . . push args
246     68/push  "append-byte: out of space\n"/imm32
247     68/push  2/imm32/stderr
248     # . . call
249     e8/call  _write/disp32
250     # . . discard args
251     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
252     # . syscall(exit, 1)
253     bb/copy-to-ebx  1/imm32
254     b8/copy-to-eax  1/imm32/exit
255     cd/syscall  0x80/imm8
256     # never gets here
257 
258 test-append-byte-single:
259     # - check that append-byte writes to first byte of 'file'
260     # setup
261     # . clear-stream(_test-stream)
262     # . . push args
263     68/push  _test-stream/imm32
264     # . . call
265     e8/call  clear-stream/disp32
266     # . . discard args
267     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
268     # append-byte(_test-stream, 'A')
269     # . . push args
270     68/push  0x41/imm32
271     68/push  _test-stream/imm32
272     # . . call
273     e8/call  append-byte/disp32
274     # . . discard args
275     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
276     # check-stream-equal(_test-stream, "A", msg)
277     # . . push args
278     68/push  "F - test-append-byte-single"/imm32
279     68/push  "A"/imm32
280     68/push  _test-stream/imm32
281     # . . call
282     e8/call  check-stream-equal/disp32
283     # . . discard args
284     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
285     # . end
286     c3/return
287 
288 == data
289 
290 _test-output-stream:
291     # current write index
292     0/imm32
293     # current read index
294     0/imm32
295     # length
296     0x200/imm32  # 512 bytes
297     # data (32 lines x 16 bytes/line)
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     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
327     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
328     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
329     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
330 
331 # a test buffered file for _test-output-stream
332 _test-output-buffered-file:
333     # file descriptor or (address stream)
334     _test-output-stream/imm32
335     # current write index
336     0/imm32
337     # current read index
338     0/imm32
339     # length
340     6/imm32
341     # data
342     00 00 00 00 00 00  # 6 bytes
343 
344 _test-error-stream:
345     # current write index
346     0/imm32
347     # current read index
348     0/imm32
349     # line
350     0x80/imm32  # 128 bytes
351     # data (8 lines x 16 bytes/line)
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     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
358     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
359     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
360 
361 # a test buffered file for _test-error-stream
362 _test-error-buffered-file:
363     # file descriptor or (address stream)
364     _test-error-stream/imm32
365     # current write index
366     0/imm32
367     # current read index
368     0/imm32
369     # length
370     6/imm32
371     # data
372     00 00 00 00 00 00  # 6 bytes
373 
374 # . . vim:nowrap:textwidth=0