https://github.com/akkartik/mu/blob/master/subx/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 # . . vim:nowrap:textwidth=0