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     # syscall(exit, Num-test-failures)
 38     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 39     b8/copy-to-EAX  1/imm32
 40     cd/syscall  0x80/imm8
 41 
 42 # return next byte value in EAX, with top 3 bytes cleared.
 43 # On EOF, return 0xffffffff.
 44 read-byte:  # f : (address buffered-file) -> byte-or-eof/EAX
 45     # . prolog
 46     55/push-EBP
 47     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 48     # . save registers
 49     51/push-ECX
 50     56/push-ESI
 51     # ESI = f
 52     8b/copy                         1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 53     # ECX = f->read
 54     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(ESI+8) to ECX
 55     # if (f->read >= f->write) populate stream from file
 56     3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # compare ECX with *(ESI+4)
 57     7c/jump-if-lesser  $read-byte:from-stream/disp8
 58     # . clear-stream(stream = f+4)
 59     # . . push args
 60     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy ESI+4 to EAX
 61     50/push-EAX
 62     # . . call
 63     e8/call  clear-stream/disp32
 64     # . . discard args
 65     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 66     # . EAX = read(f->fd, stream = f+4)
 67     # . . push args
 68     50/push-EAX
 69     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
 70     # . . call
 71     e8/call  read/disp32
 72     # . . discard args
 73     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 74     # if EAX = 0 return 0xffffffff
 75     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
 76     75/jump-if-not-equal  $read-byte:from-stream/disp8
 77     b8/copy-to-EAX  0xffffffff/imm32
 78     eb/jump  $read-byte:end/disp8
 79 $read-byte:from-stream:
 80     # read byte from stream
 81     # AL = f->data[f->read]
 82     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 83     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
 84     # ++f->read
 85     ff          0/subop/increment   1/mod/*+disp8   6/rm32/ESI    .           .             .           .           8/disp8         .                 # increment *(ESI+8)
 86 $read-byte:end:
 87     # . restore registers
 88     5e/pop-to-ESI
 89     59/pop-to-ECX
 90     # . epilog
 91     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 92     5d/pop-to-EBP
 93     c3/return
 94 
 95 # - tests
 96 
 97 test-read-byte-single:
 98     # - check that read-byte returns first byte of 'file'
 99     # setup
100     # . clear-stream(_test-stream)
101     # . . push args
102     68/push  _test-stream/imm32
103     # . . call
104     e8/call  clear-stream/disp32
105     # . . discard args
106     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
107     # . clear-stream(_test-buffered-file+4)
108     # . . push args
109     b8/copy-to-EAX  _test-buffered-file/imm32
110     05/add-to-EAX  4/imm32
111     50/push-EAX
112     # . . call
113     e8/call  clear-stream/disp32
114     # . . discard args
115     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
116     # . write(_test-stream, "Ab")
117     # . . push args
118     68/push  "Ab"/imm32
119     68/push  _test-stream/imm32
120     # . . call
121     e8/call  write/disp32
122     # . . discard args
123     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
124     # read-byte(_test-buffered-file)
125     # . . push args
126     68/push  _test-buffered-file/imm32
127     # . . call
128     e8/call  read-byte/disp32
129     # . . discard args
130     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
131     # check-ints-equal(EAX, 'A', msg)
132     # . . push args
133     68/push  "F - test-read-byte-single"/imm32
134     68/push  0x41/imm32
135     50/push-EAX
136     # . . call
137     e8/call  check-ints-equal/disp32
138     # . . discard args
139     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
140     # . end
141     c3/return
142 
143 test-read-byte-multiple:
144     # - call read-byte twice, check that second call returns second byte
145     # setup
146     # . clear-stream(_test-stream)
147     # . . push args
148     68/push  _test-stream/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     # . clear-stream(_test-buffered-file+4)
154     # . . push args
155     b8/copy-to-EAX  _test-buffered-file/imm32
156     05/add-to-EAX  4/imm32
157     50/push-EAX
158     # . . call
159     e8/call  clear-stream/disp32
160     # . . discard args
161     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
162     # . write(_test-stream, "Ab")
163     # . . push args
164     68/push  "Ab"/imm32
165     68/push  _test-stream/imm32
166     # . . call
167     e8/call  write/disp32
168     # . . discard args
169     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
170     # read-byte(_test-buffered-file)
171     # . . push args
172     68/push  _test-buffered-file/imm32
173     # . . call
174     e8/call  read-byte/disp32
175     # . . discard args
176     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
177     # read-byte(_test-buffered-file)
178     # . . push args
179     68/push  _test-buffered-file/imm32
180     # . . call
181     e8/call  read-byte/disp32
182     # . . discard args
183     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
184     # check-ints-equal(EAX, 'b', msg)
185     # . . push args
186     68/push  "F - test-read-byte-multiple"/imm32
187     68/push  0x62/imm32
188     50/push-EAX
189     # . . call
190     e8/call  check-ints-equal/disp32
191     # . . discard args
192     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
193     # . end
194     c3/return
195 
196 test-read-byte-end-of-file:
197     # - call read-byte on an empty 'file', check that it returns 0xffffffff
198     # setup
199     # . clear-stream(_test-stream)
200     # . . push args
201     68/push  _test-stream/imm32
202     # . . call
203     e8/call  clear-stream/disp32
204     # . . discard args
205     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
206     # . clear-stream(_test-buffered-file+4)
207     # . . push args
208     b8/copy-to-EAX  _test-buffered-file/imm32
209     05/add-to-EAX  4/imm32
210     50/push-EAX
211     # . . call
212     e8/call  clear-stream/disp32
213     # . . discard args
214     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
215     # read-byte(_test-buffered-file)
216     # . . push args
217     68/push  _test-buffered-file/imm32
218     # . . call
219     e8/call  read-byte/disp32
220     # . . discard args
221     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
222     # check-ints-equal(EAX, 0xffffffff, msg)
223     # . . push args
224     68/push  "F - test-read-byte-end-of-file"/imm32
225     68/push  0xffffffff/imm32
226     50/push-EAX
227     # . . call
228     e8/call  check-ints-equal/disp32
229     # . . discard args
230     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
231     # . end
232     c3/return
233 
234 == data
235 
236 # a test buffered file for _test-stream
237 _test-buffered-file:
238     # file descriptor or (address stream)
239     _test-stream/imm32
240     # current write index
241     00 00 00 00
242     # current read index
243     00 00 00 00
244     # length (6)
245     06 00 00 00
246     # data
247     00 00 00 00 00 00  # 6 bytes
248 
249 # . . vim:nowrap:textwidth=0