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.
 28 
 29 == code
 30 # instruction                     effective address                                                   operand     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/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) read byte from stream
 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   # AL = f->data[f->read]
 81   31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 82   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
 83   # ++f->read
 84   ff          0/subop/increment   1/mod/*+disp8   6/rm32/ESI    .           .             .           .           8/disp8         .                 # increment *(ESI+8)
 85 $read-byte:end:
 86   # restore registers
 87   5e/pop-to-ESI
 88   59/pop-to-ECX
 89   # epilog
 90   89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 91   5d/pop-to-EBP
 92   c3/return
 93 
 94 # todo: how should write-byte look? What should it do when the output has no
 95 # space remaining? Maybe return an error code.
 96 
 97 ## tests
 98 
 99 test-read-byte-single:
100   ## check that read-byte returns first byte of 'file'
101   # clear-stream(_test-stream)
102     # push args
103   68/push  _test-stream/imm32
104     # call
105   e8/call  clear-stream/disp32
106     # discard args
107   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
108   # clear-stream(_test-buffered-file+4)
109     # push args
110   b8/copy-to-EAX  _test-buffered-file/imm32
111   05/add-to-EAX  4/imm32
112   50/push-EAX
113     # call
114   e8/call  clear-stream/disp32
115     # discard args
116   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
117   # write(_test-stream, "Ab")
118     # push args
119   68/push  "Ab"/imm32
120   68/push  _test-stream/imm32
121     # call
122   e8/call  write/disp32
123     # discard args
124   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
125   # read-byte(_test-buffered-file)
126     # push args
127   68/push  _test-buffered-file/imm32
128     # call
129   e8/call  read-byte/disp32
130     # discard args
131   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
132   # check-ints-equal(EAX, 'A')
133     # push args
134   68/push  "F - test-read-byte-single"/imm32
135   68/push  0x41/imm32
136   50/push-EAX
137     # call
138   e8/call  check-ints-equal/disp32
139     # discard args
140   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
141   # end
142   c3/return
143 
144 test-read-byte-multiple:
145   ## call read-byte twice, check that second call returns second byte
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')
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 -1
198   # clear-stream(_test-stream)
199     # push args
200   68/push  _test-stream/imm32
201     # call
202   e8/call  clear-stream/disp32
203     # discard args
204   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
205   # clear-stream(_test-buffered-file+4)
206     # push args
207   b8/copy-to-EAX  _test-buffered-file/imm32
208   05/add-to-EAX  4/imm32
209   50/push-EAX
210     # call
211   e8/call  clear-stream/disp32
212     # discard args
213   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
214   # read-byte(_test-buffered-file)
215     # push args
216   68/push  _test-buffered-file/imm32
217     # call
218   e8/call  read-byte/disp32
219     # discard args
220   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
221   # check-ints-equal(EAX, -1)
222     # push args
223   68/push  "F - test-read-byte-end-of-file"/imm32
224   68/push  -1/imm32
225   50/push-EAX
226     # call
227   e8/call  check-ints-equal/disp32
228     # discard args
229   81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
230   # end
231   c3/return
232 
233 == data
234 
235 _test-buffered-file:
236   # file descriptor or (address stream)
237   _test-stream/imm32
238   # current write index
239   00 00 00 00
240   # current read index
241   00 00 00 00
242   # length (8)
243   08 00 00 00
244   # data
245   00 00 00 00 00 00 00 00  # 8 bytes
246 
247 # vim:nowrap:textwidth=0