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:
 15     # file descriptor or (address stream)
 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 : (address 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-lesser  $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-not-equal  $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     # read byte from stream
 77     # AL = f->data[f->read]
 78     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 79     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
 80     # ++f->read
 81     ff          0/subop/increment   1/mod/*+disp8   6/rm32/esi    .           .             .           .           8/disp8         .                 # increment *(esi+8)
 82 $read-byte-buffered:end:
 83     # . restore registers
 84     5e/pop-to-esi
 85     59/pop-to-ecx
 86     # . epilogue
 87     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 88     5d/pop-to-ebp
 89     c3/return
 90 
 91 # - tests
 92 
 93 test-read-byte-buffered-single:
 94     # - check that read-byte-buffered returns first byte of 'file'
 95     # setup
 96     # . clear-stream(_test-stream)
 97     # . . push args
 98     68/push  _test-stream/imm32
 99     # . . call
100     e8/call  clear-stream/disp32
101     # . . discard args
102     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
103     # . clear-stream(_test-buffered-file->buffer)
104     # . . push args
105     68/push  _test-buffered-file->buffer/imm32
106     # . . call
107     e8/call  clear-stream/disp32
108     # . . discard args
109     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
110     # . write(_test-stream, "Ab")
111     # . . push args
112     68/push  "Ab"/imm32
113     68/push  _test-stream/imm32
114     # . . call
115     e8/call  write/disp32
116     # . . discard args
117     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
118     # read-byte-buffered(_test-buffered-file)
119     # . . push args
120     68/push  _test-buffered-file/imm32
121     # . . call
122     e8/call  read-byte-buffered/disp32
123     # . . discard args
124     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
125     # check-ints-equal(eax, 'A', msg)
126     # . . push args
127     68/push  "F - test-read-byte-buffered-single"/imm32
128     68/push  0x41/imm32
129     50/push-eax
130     # . . call
131     e8/call  check-ints-equal/disp32
132     # . . discard args
133     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
134     # . end
135     c3/return
136 
137 test-read-byte-buffered-multiple:
138     # - call read-byte-buffered twice, check that second call returns second byte
139     # setup
140     # . clear-stream(_test-stream)
141     # . . push args
142     68/push  _test-stream/imm32
143     # . . call
144     e8/call  clear-stream/disp32
145     # . . discard args
146     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
147     # . clear-stream(_test-buffered-file->buffer)
148     # . . push args
149     68/push  _test-buffered-file->buffer/imm32
150     # . . call
151     e8/call  clear-stream/disp32
152     # . . discard args
153     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
154     # . write(_test-stream, "Ab")
155     # . . push args
156     68/push  "Ab"/imm32
157     68/push  _test-stream/imm32
158     # . . call
159     e8/call  write/disp32
160     # . . discard args
161     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
162     # read-byte-buffered(_test-buffered-file)
163     # . . push args
164     68/push  _test-buffered-file/imm32
165     # . . call
166     e8/call  read-byte-buffered/disp32
167     # . . discard args
168     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
169     # read-byte-buffered(_test-buffered-file)
170     # . . push args
171     68/push  _test-buffered-file/imm32
172     # . . call
173     e8/call  read-byte-buffered/disp32
174     # . . discard args
175     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
176     # check-ints-equal(eax, 'b', msg)
177     # . . push args
178     68/push  "F - test-read-byte-buffered-multiple"/imm32
179     68/push  0x62/imm32
180     50/push-eax
181     # . . call
182     e8/call  check-ints-equal/disp32
183     # . . discard args
184     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
185     # . end
186     c3/return
187 
188 test-read-byte-buffered-end-of-file:
189     # - call read-byte-buffered on an empty 'file', check that it returns Eof
190     # setup
191     # . clear-stream(_test-stream)
192     # . . push args
193     68/push  _test-stream/imm32
194     # . . call
195     e8/call  clear-stream/disp32
196     # . . discard args
197     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
198     # . clear-stream(_test-buffered-file->buffer)
199     # . . push args
200     68/push  _test-buffered-file->buffer/imm32
201     # . . call
202     e8/call  clear-stream/disp32
203     # . . discard args
204     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
205     # read-byte-buffered(_test-buffered-file)
206     # . . push args
207     68/push  _test-buffered-file/imm32
208     # . . call
209     e8/call  read-byte-buffered/disp32
210     # . . discard args
211     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
212     # check-ints-equal(eax, 0xffffffff, msg)
213     # . . push args
214     68/push  "F - test-read-byte-buffered-end-of-file"/imm32
215     68/push  0xffffffff/imm32/Eof
216     50/push-eax
217     # . . call
218     e8/call  check-ints-equal/disp32
219     # . . discard args
220     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
221     # . end
222     c3/return
223 
224 test-read-byte-buffered-refills-buffer:
225     # - consume buffered-file's buffer, check that next read-byte-buffered still works
226     # setup
227     # . clear-stream(_test-stream)
228     # . . push args
229     68/push  _test-stream/imm32
230     # . . call
231     e8/call  clear-stream/disp32
232     # . . discard args
233     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
234     # . clear-stream(_test-buffered-file->buffer)
235     # . . push args
236     68/push  _test-buffered-file->buffer/imm32
237     # . . call
238     e8/call  clear-stream/disp32
239     # . . discard args
240     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
241     # . write(_test-stream, "Abcdefgh")
242     # . . push args
243     68/push  "Abcdefgh"/imm32
244     68/push  _test-stream/imm32
245     # . . call
246     e8/call  write/disp32
247     # . . discard args
248     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
249     # pretend buffer is full
250     # . _test-buffered-file->read = 6  # >= _test-buffered-file->length
251     b8/copy-to-eax  _test-buffered-file/imm32
252     c7          0/subop/copy        1/mod/*+disp8   0/rm32/eax    .           .             .           .           8/disp8         6/imm32           # copy to *(eax+8)
253     # read-byte-buffered(_test-buffered-file)
254     # . . push args
255     68/push  _test-buffered-file/imm32
256     # . . call
257     e8/call  read-byte-buffered/disp32
258     # . . discard args
259     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
260     # check-ints-equal(eax, 'A', msg)
261     # . . push args
262     68/push  "F - test-read-byte-buffered-refills-buffer"/imm32
263     68/push  0x41/imm32
264     50/push-eax
265     # . . call
266     e8/call  check-ints-equal/disp32
267     # . . discard args
268     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
269     # . end
270     c3/return
271 
272 == data
273 
274 # a test buffered file for _test-stream
275 _test-buffered-file:
276     # file descriptor or (address stream)
277     _test-stream/imm32
278 _test-buffered-file->buffer:
279     # current write index
280     0/imm32
281     # current read index
282     0/imm32
283     # length
284     6/imm32
285     # data
286     00 00 00 00 00 00  # 6 bytes
287 
288 _test-input-stream:
289     # current write index
290     0/imm32
291     # current read index
292     0/imm32
293     # length
294     0x100/imm32  # 256 bytes
295     # data (16 lines x 16 bytes/line)
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 
313 # a test buffered file for _test-input-stream
314 _test-input-buffered-file:
315     # file descriptor or (address stream)
316     _test-input-stream/imm32
317 _test-input-buffered-file->buffer:
318     # current write index
319     0/imm32
320     # current read index
321     0/imm32
322     # length
323     6/imm32
324     # data
325     00 00 00 00 00 00  # 6 bytes
326 
327 # . . vim:nowrap:textwidth=0