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 
 15 Stdin:
 16     # file descriptor or (address stream)
 17     00 00 00 00  # 0 = standard input
 18     # current write index
 19     00 00 00 00
 20     # current read index
 21     00 00 00 00
 22     # length (8)
 23     08 00 00 00
 24     # data
 25     00 00 00 00 00 00 00 00  # 8 bytes
 26 
 27 # TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency. But
 28 # I don't want to type 1024 bytes here.
 29 
 30 == code
 31 #   instruction                     effective address                                                   register    displacement    immediate
 32 # . op          subop               mod             rm32          base        index         scale       r32
 33 # . 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
 34 
 35 # main:
 36     e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
 37 #?     e8/call  test-read-byte-multiple/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
 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   4/rm32/sib    5/base/EBP  4/index/none  .           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) read byte from stream
 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     # otherwise first populate stream from file
 60     # . clear-stream(stream = f+4)
 61     # . . push args
 62     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy ESI+4 to EAX
 63     50/push-EAX
 64     # . . call
 65     e8/call  clear-stream/disp32
 66     # . . discard args
 67     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 68     # . EAX = read(f->fd, stream = f+4)
 69     # . . push args
 70     50/push-EAX
 71     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
 72     # . . call
 73     e8/call  read/disp32
 74     # . . discard args
 75     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 76     # if EAX = 0 return 0xffffffff
 77     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
 78     75/jump-if-not-equal  $read-byte:from-stream/disp8
 79     b8/copy-to-EAX  0xffffffff/imm32
 80     eb/jump  $read-byte:end/disp8
 81 $read-byte:from-stream:
 82     # reading from stream
 83     # AL = f->data[f->read]
 84     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 85     8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/AL    0x10/disp8      .                 # copy *(ESI+ECX+16) to AL
 86     # ++f->read
 87     ff          0/subop/increment   1/mod/*+disp8   6/rm32/ESI    .           .             .           .           8/disp8         .                 # increment *(ESI+8)
 88 $read-byte:end:
 89     # . restore registers
 90     5e/pop-to-ESI
 91     59/pop-to-ECX
 92     # . epilog
 93     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 94     5d/pop-to-EBP
 95     c3/return
 96 
 97 # todo: how should write-byte look? What should it do when the output has no
 98 # space remaining? Maybe return an error code.
 99 
100 # - tests
101 
102 test-read-byte-single:
103     # - check that read-byte returns first byte of 'file'
104     # setup
105     # . clear-stream(_test-stream)
106     # . . push args
107     68/push  _test-stream/imm32
108     # . . call
109     e8/call  clear-stream/disp32
110     # . . discard args
111     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
112     # . clear-stream(_test-buffered-file+4)
113     # . . push args
114     b8/copy-to-EAX  _test-buffered-file/imm32
115     05/add-to-EAX  4/imm32
116     50/push-EAX
117     # . . call
118     e8/call  clear-stream/disp32
119     # . . discard args
120     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
121     # . write(_test-stream, "Ab")
122     # . . push args
123     68/push  "Ab"/imm32
124     68/push  _test-stream/imm32
125     # . . call
126     e8/call  write/disp32
127     # . . discard args
128     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
129     # read-byte(_test-buffered-file)
130     # . . push args
131     68/push  _test-buffered-file/imm32
132     # . . call
133     e8/call  read-byte/disp32
134     # . . discard args
135     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
136     # check-ints-equal(EAX, 'A', msg)
137     # . . push args
138     68/push  "F - test-read-byte-single"/imm32
139     68/push  0x41/imm32
140     50/push-EAX
141     # . . call
142     e8/call  check-ints-equal/disp32
143     # . . discard args
144     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
145     # . end
146     c3/return
147 
148 test-read-byte-multiple:
149     # - call read-byte twice, check that second call returns second byte
150     # setup
151     # . clear-stream(_test-stream)
152     # . . push args
153     68/push  _test-stream/imm32
154     # . . call
155     e8/call  clear-stream/disp32
156     # . . discard args
157     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
158     # . clear-stream(_test-buffered-file+4)
159     # . . push args
160     b8/copy-to-EAX  _test-buffered-file/imm32
161     05/add-to-EAX  4/imm32
162     50/push-EAX
163     # . . call
164     e8/call  clear-stream/disp32
165     # . . discard args
166     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
167     # . write(_test-stream, "Ab")
168     # . . push args
169     68/push  "Ab"/imm32
170     68/push  _test-stream/imm32
171     # . . call
172     e8/call  write/disp32
173     # . . discard args
174     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
175     # read-byte(_test-buffered-file)
176     # . . push args
177     68/push  _test-buffered-file/imm32
178     # . . call
179     e8/call  read-byte/disp32
180     # . . discard args
181     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
182     # read-byte(_test-buffered-file)
183     # . . push args
184     68/push  _test-buffered-file/imm32
185     # . . call
186     e8/call  read-byte/disp32
187     # . . discard args
188     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
189     # check-ints-equal(EAX, 'b', msg)
190     # . . push args
191     68/push  "F - test-read-byte-multiple"/imm32
192     68/push  0x62/imm32
193     50/push-EAX
194     # . . call
195     e8/call  check-ints-equal/disp32
196     # . . discard args
197     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
198     # . end
199     c3/return
200 
201 test-read-byte-end-of-file:
202     # - call read-byte on an empty 'file', check that it returns -1
203     # setup
204     # . clear-stream(_test-stream)
205     # . . push args
206     68/push  _test-stream/imm32
207     # . . call
208     e8/call  clear-stream/disp32
209     # . . discard args
210     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
211     # . clear-stream(_test-buffered-file+4)
212     # . . push args
213     b8/copy-to-EAX  _test-buffered-file/imm32
214     05/add-to-EAX  4/imm32
215     50/push-EAX
216     # . . call
217     e8/call  clear-stream/disp32
218     # . . discard args
219     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
220     # read-byte(_test-buffered-file)
221     # . . push args
222     68/push  _test-buffered-file/imm32
223     # . . call
224     e8/call  read-byte/disp32
225     # . . discard args
226     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
227     # check-ints-equal(EAX, -1, msg)
228     # . . push args
229     68/push  "F - test-read-byte-end-of-file"/imm32
230     68/push  -1/imm32
231     50/push-EAX
232     # . . call
233     e8/call  check-ints-equal/disp32
234     # . . discard args
235     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
236     # . end
237     c3/return
238 
239 == data
240 
241 _test-buffered-file:
242     # file descriptor or (address stream)
243     _test-stream/imm32
244     # current write index
245     00 00 00 00
246     # current read index
247     00 00 00 00
248     # length (8)
249     08 00 00 00
250     # data
251     00 00 00 00 00 00 00 00  # 8 bytes
252 
253 # . . vim:nowrap:textwidth=0