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.
 14 Stdin:
 15     # file descriptor or (address stream)
 16     0/imm32  # standard input
 17     # current write index
 18     0/imm32
 19     # current read index
 20     0/imm32
 21     # length
 22     8/imm32
 23     # data
 24     00 00 00 00 00 00 00 00  # 8 bytes
 25 
 26 # TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency. But
 27 # I don't want to type in 1024 bytes here.
 28 
 29 == code
 30 #   instruction                     effective address                                                   register    displacement    immediate
 31 # . op          subop               mod             rm32          base        index         scale       r32
 32 # . 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
 33 
 34 # return next byte value in eax, with top 3 bytes cleared.
 35 # On reaching end of file, return 0xffffffff (Eof).
 36 read-byte-buffered:  # f : (address buffered-file) -> byte-or-Eof/eax
 37     # . prolog
 38     55/push-ebp
 39     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 40     # . save registers
 41     51/push-ecx
 42     56/push-esi
 43     # esi = f
 44     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 45     # ecx = f->read
 46     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(esi+8) to ecx
 47     # if (f->read >= f->write) populate stream from file
 48     3b/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # compare ecx with *(esi+4)
 49     7c/jump-if-lesser  $read-byte-buffered:from-stream/disp8
 50     # . clear-stream(stream = f+4)
 51     # . . push args
 52     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy esi+4 to eax
 53     50/push-eax
 54     # . . call
 55     e8/call  clear-stream/disp32
 56     # . . discard args
 57     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 58     # . f->read must now be 0; update its cache at ecx
 59     31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
 60     # . eax = read(f->fd, stream = f+4)
 61     # . . push args
 62     50/push-eax
 63     ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
 64     # . . call
 65     e8/call  read/disp32
 66     # . . discard args
 67     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 68     # if (eax == 0) return 0xffffffff
 69     3d/compare-eax-and  0/imm32
 70     75/jump-if-not-equal  $read-byte-buffered:from-stream/disp8
 71     b8/copy-to-eax  0xffffffff/imm32/Eof
 72     eb/jump  $read-byte-buffered:end/disp8
 73 $read-byte-buffered:from-stream:
 74     # read byte from stream
 75     # AL = f->data[f->read]
 76     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 77     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
 78     # ++f->read
 79     ff          0/subop/increment   1/mod/*+disp8   6/rm32/esi    .           .             .           .           8/disp8         .                 # increment *(esi+8)
 80 $read-byte-buffered:end:
 81     # . restore registers
 82     5e/pop-to-esi
 83     59/pop-to-ecx
 84     # . epilog
 85     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 86     5d/pop-to-ebp
 87     c3/return
 88 
 89 # - tests
 90 
 91 test-read-byte-buffered-single:
 92     # - check that read-byte-buffered returns first byte of 'file'
 93     # setup
 94     # . clear-stream(_test-stream)
 95     # . . push args
 96     68/push  _test-stream/imm32
 97     # . . call
 98     e8/call  clear-stream/disp32
 99     # . . discard args
100     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
101     # . clear-stream(_test-buffered-file+4)
102     # . . push args
103     b8/copy-to-eax  _test-buffered-file/imm32
104     05/add-to-eax  4/imm32
105     50/push-eax
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+4)
148     # . . push args
149     b8/copy-to-eax  _test-buffered-file/imm32
150     05/add-to-eax  4/imm32
151     50/push-eax
152     # . . call
153     e8/call  clear-stream/disp32
154     # . . discard args
155     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
156     # . write(_test-stream, "Ab")
157     # . . push args
158     68/push  "Ab"/imm32
159     68/push  _test-stream/imm32
160     # . . call
161     e8/call  write/disp32
162     # . . discard args
163     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
164     # read-byte-buffered(_test-buffered-file)
165     # . . push args
166     68/push  _test-buffered-file/imm32
167     # . . call
168     e8/call  read-byte-buffered/disp32
169     # . . discard args
170     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
171     # read-byte-buffered(_test-buffered-file)
172     # . . push args
173     68/push  _test-buffered-file/imm32
174     # . . call
175     e8/call  read-byte-buffered/disp32
176     # . . discard args
177     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
178     # check-ints-equal(eax, 'b', msg)
179     # . . push args
180     68/push  "F - test-read-byte-buffered-multiple"/imm32
181     68/push  0x62/imm32
182     50/push-eax
183     # . . call
184     e8/call  check-ints-equal/disp32
185     # . . discard args
186     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
187     # . end
188     c3/return
189 
190 test-read-byte-buffered-end-of-file:
191     # - call read-byte-buffered on an empty 'file', check that it returns Eof
192     # setup
193     # . clear-stream(_test-stream)
194     # . . push args
195     68/push  _test-stream/imm32
196     # . . call
197     e8/call  clear-stream/disp32
198     # . . discard args
199     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
200     # . clear-stream(_test-buffered-file+4)
201     # . . push args
202     b8/copy-to-eax  _test-buffered-file/imm32
203     05/add-to-eax  4/imm32
204     50/push-eax
205     # . . call
206     e8/call  clear-stream/disp32
207     # . . discard args
208     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
209     # read-byte-buffered(_test-buffered-file)
210     # . . push args
211     68/push  _test-buffered-file/imm32
212     # . . call
213     e8/call  read-byte-buffered/disp32
214     # . . discard args
215     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
216     # check-ints-equal(eax, 0xffffffff, msg)
217     # . . push args
218     68/push  "F - test-read-byte-buffered-end-of-file"/imm32
219     68/push  0xffffffff/imm32/Eof
220     50/push-eax
221     # . . call
222     e8/call  check-ints-equal/disp32
223     # . . discard args
224     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
225     # . end
226     c3/return
227 
228 test-read-byte-buffered-refills-buffer:
229     # - consume buffered-file's buffer, check that next read-byte-buffered still works
230     # setup
231     # . clear-stream(_test-stream)
232     # . . push args
233     68/push  _test-stream/imm32
234     # . . call
235     e8/call  clear-stream/disp32
236     # . . discard args
237     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
238     # . clear-stream(_test-buffered-file+4)
239     # . . push args
240     b8/copy-to-eax  _test-buffered-file/imm32
241     05/add-to-eax  4/imm32
242     50/push-eax
243     # . . call
244     e8/call  clear-stream/disp32
245     # . . discard args
246     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
247     # . write(_test-stream, "Abcdefgh")
248     # . . push args
249     68/push  "Abcdefgh"/imm32
250     68/push  _test-stream/imm32
251     # . . call
252     e8/call  write/disp32
253     # . . discard args
254     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
255     # pretend buffer is full
256     # . _test-buffered-file->read = 6  # >= _test-buffered-file->length
257     b8/copy-to-eax  _test-buffered-file/imm32
258     c7          0/subop/copy        1/mod/*+disp8   0/rm32/eax    .           .             .           .           8/disp8         6/imm32           # copy to *(eax+8)
259     # read-byte-buffered(_test-buffered-file)
260     # . . push args
261     68/push  _test-buffered-file/imm32
262     # . . call
263     e8/call  read-byte-buffered/disp32
264     # . . discard args
265     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
266     # check-ints-equal(eax, 'A', msg)
267     # . . push args
268     68/push  "F - test-read-byte-buffered-refills-buffer"/imm32
269     68/push  0x41/imm32
270     50/push-eax
271     # . . call
272     e8/call  check-ints-equal/disp32
273     # . . discard args
274     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
275     # . end
276     c3/return
277 
278 == data
279 
280 # a test buffered file for _test-stream
281 _test-buffered-file:
282     # file descriptor or (address stream)
283     _test-stream/imm32
284     # current write index
285     0/imm32
286     # current read index
287     0/imm32
288     # length
289     6/imm32
290     # data
291     00 00 00 00 00 00  # 6 bytes
292 
293 _test-input-stream:
294     # current write index
295     0/imm32
296     # current read index
297     0/imm32
298     # length
299     0x100/imm32  # 256 bytes
300     # data (16 lines x 16 bytes/line)
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 
318 # a test buffered file for _test-input-stream
319 _test-input-buffered-file:
320     # file descriptor or (address stream)
321     _test-input-stream/imm32
322     # current write index
323     0/imm32
324     # current read index
325     0/imm32
326     # length
327     6/imm32
328     # data
329     00 00 00 00 00 00  # 6 bytes
330 
331 # . . vim:nowrap:textwidth=0