https://github.com/akkartik/mu/blob/master/subx/059read-byte.subx
  1 # read-byte: 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     00 00 00 00  # 0 = standard input
 17     # current write index
 18     00 00 00 00
 19     # current read index
 20     00 00 00 00
 21     # length (8)
 22     08 00 00 00
 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 # main:
 35     e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
 36 #?     e8/call test-read-byte-multiple/disp32
 37 #?     e8/call test-read-byte-refills-buffer/disp32
 38     # syscall(exit, Num-test-failures)
 39     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 40     b8/copy-to-EAX  1/imm32/exit
 41     cd/syscall  0x80/imm8
 42 
 43 # return next byte value in EAX, with top 3 bytes cleared.
 44 # On EOF, return 0xffffffff.
 45 read-byte:  # f : (address buffered-file) -> byte-or-eof/EAX
 46     # . prolog
 47     55/push-EBP
 48     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 49     # . save registers
 50     51/push-ECX
 51     56/push-ESI
 52     # ESI = f
 53     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 54     # ECX = f->read
 55     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(ESI+8) to ECX
 56     # if (f->read >= f->write) populate stream from file
 57     3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # compare ECX with *(ESI+4)
 58     7c/jump-if-lesser  $read-byte:from-stream/disp8
 59     # . clear-stream(stream = f+4)
 60     # . . push args
 61     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy ESI+4 to EAX
 62     50/push-EAX
 63     # . . call
 64     e8/call  clear-stream/disp32
 65     # . . discard args
 66     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 67     # . f->read must now be 0; update ECX
 68     31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
 69     # . EAX = read(f->fd, stream = f+4)
 70     # . . push args
 71     50/push-EAX
 72     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
 73     # . . call
 74     e8/call  read/disp32
 75     # . . discard args
 76     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 77     # if EAX = 0 return 0xffffffff
 78     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
 79     75/jump-if-not-equal  $read-byte:from-stream/disp8
 80     b8/copy-to-EAX  0xffffffff/imm32
 81     eb/jump  $read-byte:end/disp8
 82 $read-byte:from-stream:
 83     # read byte from stream
 84     # AL = f->data[f->read]
 85     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 86     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
 87     # ++f->read
 88     ff          0/subop/increment   1/mod/*+disp8   6/rm32/ESI    .           .             .           .           8/disp8         .                 # increment *(ESI+8)
 89 $read-byte:end:
 90     # . restore registers
 91     5e/pop-to-ESI
 92     59/pop-to-ECX
 93     # . epilog
 94     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 95     5d/pop-to-EBP
 96     c3/return
 97 
 98 # - tests
 99 
100 test-read-byte-single:
101     # - check that read-byte returns first byte of 'file'
102     # setup
103     # . clear-stream(_test-stream)
104     # . . push args
105     68/push  _test-stream/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     # . clear-stream(_test-buffered-file+4)
111     # . . push args
112     b8/copy-to-EAX  _test-buffered-file/imm32
113     05/add-to-EAX  4/imm32
114     50/push-EAX
115     # . . call
116     e8/call  clear-stream/disp32
117     # . . discard args
118     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
119     # . write(_test-stream, "Ab")
120     # . . push args
121     68/push  "Ab"/imm32
122     68/push  _test-stream/imm32
123     # . . call
124     e8/call  write/disp32
125     # . . discard args
126     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
127     # read-byte(_test-buffered-file)
128     # . . push args
129     68/push  _test-buffered-file/imm32
130     # . . call
131     e8/call  read-byte/disp32
132     # . . discard args
133     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
134     # check-ints-equal(EAX, 'A', msg)
135     # . . push args
136     68/push  "F - test-read-byte-single"/imm32
137     68/push  0x41/imm32
138     50/push-EAX
139     # . . call
140     e8/call  check-ints-equal/disp32
141     # . . discard args
142     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
143     # . end
144     c3/return
145 
146 test-read-byte-multiple:
147     # - call read-byte twice, check that second call returns second byte
148     # setup
149     # . clear-stream(_test-stream)
150     # . . push args
151     68/push  _test-stream/imm32
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     # . clear-stream(_test-buffered-file+4)
157     # . . push args
158     b8/copy-to-EAX  _test-buffered-file/imm32
159     05/add-to-EAX  4/imm32
160     50/push-EAX
161     # . . call
162     e8/call  clear-stream/disp32
163     # . . discard args
164     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
165     # . write(_test-stream, "Ab")
166     # . . push args
167     68/push  "Ab"/imm32
168     68/push  _test-stream/imm32
169     # . . call
170     e8/call  write/disp32
171     # . . discard args
172     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
173     # read-byte(_test-buffered-file)
174     # . . push args
175     68/push  _test-buffered-file/imm32
176     # . . call
177     e8/call  read-byte/disp32
178     # . . discard args
179     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
180     # read-byte(_test-buffered-file)
181     # . . push args
182     68/push  _test-buffered-file/imm32
183     # . . call
184     e8/call  read-byte/disp32
185     # . . discard args
186     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
187     # check-ints-equal(EAX, 'b', msg)
188     # . . push args
189     68/push  "F - test-read-byte-multiple"/imm32
190     68/push  0x62/imm32
191     50/push-EAX
192     # . . call
193     e8/call  check-ints-equal/disp32
194     # . . discard args
195     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
196     # . end
197     c3/return
198 
199 test-read-byte-end-of-file:
200     # - call read-byte on an empty 'file', check that it returns 0xffffffff
201     # setup
202     # . clear-stream(_test-stream)
203     # . . push args
204     68/push  _test-stream/imm32
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     # . clear-stream(_test-buffered-file+4)
210     # . . push args
211     b8/copy-to-EAX  _test-buffered-file/imm32
212     05/add-to-EAX  4/imm32
213     50/push-EAX
214     # . . call
215     e8/call  clear-stream/disp32
216     # . . discard args
217     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
218     # read-byte(_test-buffered-file)
219     # . . push args
220     68/push  _test-buffered-file/imm32
221     # . . call
222     e8/call  read-byte/disp32
223     # . . discard args
224     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
225     # check-ints-equal(EAX, 0xffffffff, msg)
226     # . . push args
227     68/push  "F - test-read-byte-end-of-file"/imm32
228     68/push  0xffffffff/imm32
229     50/push-EAX
230     # . . call
231     e8/call  check-ints-equal/disp32
232     # . . discard args
233     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
234     # . end
235     c3/return
236 
237 test-read-byte-refills-buffer:
238     # - consume buffered-file's buffer, check that next read-byte still works
239     # setup
240     # . clear-stream(_test-stream)
241     # . . push args
242     68/push  _test-stream/imm32
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     # . clear-stream(_test-buffered-file+4)
248     # . . push args
249     b8/copy-to-EAX  _test-buffered-file/imm32
250     05/add-to-EAX  4/imm32
251     50/push-EAX
252     # . . call
253     e8/call  clear-stream/disp32
254     # . . discard args
255     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
256     # . write(_test-stream, "Abcdefgh")
257     # . . push args
258     68/push  "Abcdefgh"/imm32
259     68/push  _test-stream/imm32
260     # . . call
261     e8/call  write/disp32
262     # . . discard args
263     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
264     # pretend buffer is full
265     # . _test-buffered-file->read = 6  # >= _test-buffered-file->length
266     b8/copy-to-EAX  _test-buffered-file/imm32
267     c7          0/subop/copy        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         6/imm32           # copy to *(EAX+8)
268     # read-byte(_test-buffered-file)
269     # . . push args
270     68/push  _test-buffered-file/imm32
271     # . . call
272     e8/call  read-byte/disp32
273     # . . discard args
274     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
275     # check-ints-equal(EAX, 'A', msg)
276     # . . push args
277     68/push  "F - test-read-byte-refills-buffer"/imm32
278     68/push  0x41/imm32
279     50/push-EAX
280     # . . call
281     e8/call  check-ints-equal/disp32
282     # . . discard args
283     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
284     # . end
285     c3/return
286 
287 == data
288 
289 # a test buffered file for _test-stream
290 _test-buffered-file:
291     # file descriptor or (address stream)
292     _test-stream/imm32
293     # current write index
294     00 00 00 00
295     # current read index
296     00 00 00 00
297     # length (6)
298     06 00 00 00
299     # data
300     00 00 00 00 00 00  # 6 bytes
301 
302 # . . vim:nowrap:textwidth=0