https://github.com/akkartik/mu/blob/main/112read-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     #   size
 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: byte
 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->size
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 # Return next byte value in eax, with top 3 bytes cleared.
272 # Abort on reaching end of stream.
273 read-byte:  # s: (addr stream byte) -> result/eax: byte
274     # . prologue
275     55/push-ebp
276     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
277     # . save registers
278     51/push-ecx
279     56/push-esi
280     # esi = s
281     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
282     # ecx = s->read
283     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
284     # if (f->read >= f->write) abort
285     3b/compare                      0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # compare ecx with *esi
286     0f 8d/jump-if->=  $read-byte:abort/disp32
287     # result = f->data[f->read]
288     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
289     8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
290     # ++f->read
291     ff          0/subop/increment   1/mod/*+disp8   6/rm32/esi    .           .             .           .           4/disp8         .                 # increment *(esi+4)
292 $read-byte:end:
293     # . restore registers
294     5e/pop-to-esi
295     59/pop-to-ecx
296     # . epilogue
297     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
298     5d/pop-to-ebp
299     c3/return
300 
301 $read-byte:abort:
302     # . _write(2/stderr, error)
303     # . . push args
304     68/push  "read-byte: empty stream\n"/imm32
305     68/push  2/imm32/stderr
306     # . . call
307     e8/call  _write/disp32
308     # . . discard args
309     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
310     # . syscall(exit, 1)
311     bb/copy-to-ebx  1/imm32
312     e8/call  syscall_exit/disp32
313     # never gets here
314 
315 == data
316 
317 # a test buffered file for _test-stream
318 _test-buffered-file:  # buffered-file
319     # file descriptor or (addr stream byte)
320     _test-stream/imm32
321 $_test-buffered-file->buffer:
322     # current write index
323     0/imm32
324     # current read index
325     0/imm32
326     # size
327     6/imm32
328     # data
329     00 00 00 00 00 00  # 6 bytes
330 
331 _test-input-stream:  # (stream byte)
332     # current write index
333     0/imm32
334     # current read index
335     0/imm32
336     # size
337     0x400/imm32  # 1024 bytes
338     # data (64 lines x 16 bytes/line)
339     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
340     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
341     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
342     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
343     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
344     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
345     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
346     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
347     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
348     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
349     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
350     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
351     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
361     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
362     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
363     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
364     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
365     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
366     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
367     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
368     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
369     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
370     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
371     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
372     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
373     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
374     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
375     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
376     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
377     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
378     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
379     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
380     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
381     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
382     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
383     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
384     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
385     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
386     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
387     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
388     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
389     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
390     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
391     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
392     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
393     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
394     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
395     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
396     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
397     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
398     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
399     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
400     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
401     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
402     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
403 
404 # a test buffered file for _test-input-stream
405 _test-input-buffered-file:  # buffered-file
406     # file descriptor or (addr stream byte)
407     _test-input-stream/imm32
408 $_test-input-buffered-file->buffer:
409     # current write index
410     0/imm32
411     # current read index
412     0/imm32
413     # size
414     6/imm32
415     # data
416     00 00 00 00 00 00  # 6 bytes
417 
418 # . . vim:nowrap:textwidth=0