https://github.com/akkartik/mu/blob/master/061read-byte.subx
  1 # read-byte-buffered: one higher-level abstraction atop 'read'.
  2 #
  3 # There are many situations where 'read' is a lot to manage, and we need
  4 # to abstract some details away. One of them is when we want to read a file
  5 # character by character. In this situation we follow C's FILE data structure,
  6 # which manages the underlying file descriptor together with the buffer it
  7 # reads into. We call our version 'buffered-file'. Should be useful with other
  8 # primitives as well, in later layers.
  9 
 10 == data
 11 
 12 # The buffered file for standard input. Also illustrates the layout for
 13 # buffered-file: a pointer to the backing store, followed by a 'buffer' stream
 14 Stdin:  # buffered-file
 15     # file descriptor or (addr stream byte)
 16     0/imm32  # standard input
 17 $Stdin->buffer:
 18     # inlined fields for a stream
 19     #   current write index
 20     0/imm32
 21     #   current read index
 22     0/imm32
 23     #   length
 24     8/imm32
 25     #   data
 26     00 00 00 00 00 00 00 00  # 8 bytes
 27 
 28 # TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency. But
 29 # I don't want to type in 1024 bytes here.
 30 
 31 == code
 32 #   instruction                     effective address                                                   register    displacement    immediate
 33 # . op          subop               mod             rm32          base        index         scale       r32
 34 # . 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
 35 
 36 # return next byte value in eax, with top 3 bytes cleared.
 37 # On reaching end of file, return 0xffffffff (Eof).
 38 read-byte-buffered:  # f: (addr buffered-file) -> byte-or-Eof/eax
 39     # . prologue
 40     55/push-ebp
 41     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 42     # . save registers
 43     51/push-ecx
 44     56/push-esi
 45     # esi = f
 46     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 47     # ecx = f->read
 48     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(esi+8) to ecx
 49     # if (f->read >= f->write) populate stream from file
 50     3b/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # compare ecx with *(esi+4)
 51     7c/jump-if-<  $read-byte-buffered:from-stream/disp8
 52     # . clear-stream(stream = f+4)
 53     # . . push args
 54     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy esi+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->read must now be 0; update its cache at ecx
 61     31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
 62     # . eax = read(f->fd, stream = f+4)
 63     # . . push args
 64     50/push-eax
 65     ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
 66     # . . call
 67     e8/call  read/disp32
 68     # . . discard args
 69     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 70     # if (eax == 0) return 0xffffffff
 71     3d/compare-eax-and  0/imm32
 72     75/jump-if-!=  $read-byte-buffered:from-stream/disp8
 73     b8/copy-to-eax  0xffffffff/imm32/Eof
 74     eb/jump  $read-byte-buffered:end/disp8
 75 $read-byte-buffered:from-stream:
 76     # byte-or-Eof = f->data[f->read]
 77     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 78     8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0x10/disp8      .                 # copy byte at *(esi+ecx+16) to AL
 79     # ++f->read
 80     ff          0/subop/increment   1/mod/*+disp8   6/rm32/esi    .           .             .           .           8/disp8         .                 # increment *(esi+8)
 81 $read-byte-buffered:end:
 82     # . restore registers
 83     5e/pop-to-esi
 84     59/pop-to-ecx
 85     # . epilogue
 86     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 87     5d/pop-to-ebp
 88     c3/return
 89 
 90 # - tests
 91 
 92 test-read-byte-buffered-single:
 93     # - check that read-byte-buffered returns first byte of 'file'
 94     # setup
 95     # . clear-stream(_test-stream)
 96     # . . push args
 97     68/push  _test-stream/imm32
 98     # . . call
 99     e8/call  clear-stream/disp32
100     # . . discard args
101     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
102     # . clear-stream(_test-buffered-file->buffer)
103     # . . push args
104     68/push  $_test-buffered-file->buffer/imm32
105     # . . call
106     e8/call  clear-stream/disp32
107     # . . discard args
108     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
109     # . write(_test-stream, "Ab")
110     # . . push args
111     68/push  "Ab"/imm32
112     68/push  _test-stream/imm32
113     # . . call
114     e8/call  write/disp32
115     # . . discard args
116     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
117     # read-byte-buffered(_test-buffered-file)
118     # . . push args
119     68/push  _test-buffered-file/imm32
120     # . . call
121     e8/call  read-byte-buffered/disp32
122     # . . discard args
123     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
124     # check-ints-equal(eax, 'A', msg)
125     # . . push args
126     68/push  "F - test-read-byte-buffered-single"/imm32
127     68/push  0x41/imm32
128     50/push-eax
129     # . . call
130     e8/call  check-ints-equal/disp32
131     # . . discard args
132     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
133     # . end
134     c3/return
135 
136 test-read-byte-buffered-multiple:
137     # - call read-byte-buffered twice, check that second call returns second byte
138     # setup
139     # . clear-stream(_test-stream)
140     # . . push args
141     68/push  _test-stream/imm32
142     # . . call
143     e8/call  clear-stream/disp32
144     # . . discard args
145     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
146     # . clear-stream($_test-buffered-file->buffer)
147     # . . push args
148     68/push  $_test-buffered-file->buffer/imm32
149     # . . call
150     e8/call  clear-stream/disp32
151     # . . discard args
152     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
153     # . write(_test-stream, "Ab")
154     # . . push args
155     68/push  "Ab"/imm32
156     68/push  _test-stream/imm32
157     # . . call
158     e8/call  write/disp32
159     # . . discard args
160     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
161     # read-byte-buffered(_test-buffered-file)
162     # . . push args
163     68/push  _test-buffered-file/imm32
164     # . . call
165     e8/call  read-byte-buffered/disp32
166     # . . discard args
167     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
168     # read-byte-buffered(_test-buffered-file)
169     # . . push args
170     68/push  _test-buffered-file/imm32
171     # . . call
172     e8/call  read-byte-buffered/disp32
173     # . . discard args
174     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
175     # check-ints-equal(eax, 'b', msg)
176     # . . push args
177     68/push  "F - test-read-byte-buffered-multiple"/imm32
178     68/push  0x62/imm32
179     50/push-eax
180     # . . call
181     e8/call  check-ints-equal/disp32
182     # . . discard args
183     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
184     # . end
185     c3/return
186 
187 test-read-byte-buffered-end-of-file:
188     # - call read-byte-buffered on an empty 'file', check that it returns Eof
189     # setup
190     # . clear-stream(_test-stream)
191     # . . push args
192     68/push  _test-stream/imm32
193     # . . call
194     e8/call  clear-stream/disp32
195     # . . discard args
196     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
197     # . clear-stream($_test-buffered-file->buffer)
198     # . . push args
199     68/push  $_test-buffered-file->buffer/imm32
200     # . . call
201     e8/call  clear-stream/disp32
202     # . . discard args
203     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
204     # read-byte-buffered(_test-buffered-file)
205     # . . push args
206     68/push  _test-buffered-file/imm32
207     # . . call
208     e8/call  read-byte-buffered/disp32
209     # . . discard args
210     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
211     # check-ints-equal(eax, 0xffffffff, msg)
212     # . . push args
213     68/push  "F - test-read-byte-buffered-end-of-file"/imm32
214     68/push  0xffffffff/imm32/Eof
215     50/push-eax
216     # . . call
217     e8/call  check-ints-equal/disp32
218     # . . discard args
219     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
220     # . end
221     c3/return
222 
223 test-read-byte-buffered-refills-buffer:
224     # - consume buffered-file's buffer, check that next read-byte-buffered still works
225     # setup
226     # . clear-stream(_test-stream)
227     # . . push args
228     68/push  _test-stream/imm32
229     # . . call
230     e8/call  clear-stream/disp32
231     # . . discard args
232     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
233     # . clear-stream($_test-buffered-file->buffer)
234     # . . push args
235     68/push  $_test-buffered-file->buffer/imm32
236     # . . call
237     e8/call  clear-stream/disp32
238     # . . discard args
239     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
240     # . write(_test-stream, "Abcdefgh")
241     # . . push args
242     68/push  "Abcdefgh"/imm32
243     68/push  _test-stream/imm32
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     # pretend buffer is full
249     # . _test-buffered-file->read = 6  # >= _test-buffered-file->length
250     b8/copy-to-eax  _test-buffered-file/imm32
251     c7          0/subop/copy        1/mod/*+disp8   0/rm32/eax    .           .             .           .           8/disp8         6/imm32           # copy to *(eax+8)
252     # read-byte-buffered(_test-buffered-file)
253     # . . push args
254     68/push  _test-buffered-file/imm32
255     # . . call
256     e8/call  read-byte-buffered/disp32
257     # . . discard args
258     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
259     # check-ints-equal(eax, 'A', msg)
260     # . . push args
261     68/push  "F - test-read-byte-buffered-refills-buffer"/imm32
262     68/push  0x41/imm32
263     50/push-eax
264     # . . call
265     e8/call  check-ints-equal/disp32
266     # . . discard args
267     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
268     # . end
269     c3/return
270 
271 == data
272 
273 # a test buffered file for _test-stream
274 _test-buffered-file:  # buffered-file
275     # file descriptor or (addr stream byte)
276     _test-stream/imm32
277 $_test-buffered-file->buffer:
278     # current write index
279     0/imm32
280     # current read index
281     0/imm32
282     # length
283     6/imm32
284     # data
285     00 00 00 00 00 00  # 6 bytes
286 
287 _test-input-stream:  # (stream byte)
288     # current write index
289     0/imm32
290     # current read index
291     0/imm32
292     # length
293     0x100/imm32  # 256 bytes
294     # data (16 lines x 16 bytes/line)
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 
312 # a test buffered file for _test-input-stream
313 _test-input-buffered-file:  # buffered-file
314     # file descriptor or (addr stream byte)
315     _test-input-stream/imm32
316 $_test-input-buffered-file->buffer:
317     # current write index
318     0/imm32
319     # current read index
320     0/imm32
321     # length
322     6/imm32
323     # data
324     00 00 00 00 00 00  # 6 bytes
325 
326 # . . vim:nowrap:textwidth=0