https://github.com/akkartik/mu/blob/master/subx/071read-line.subx
  1 == code
  2 #   instruction                     effective address                                                   register    displacement    immediate
  3 # . op          subop               mod             rm32          base        index         scale       r32
  4 # . 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
  5 
  6 # main:
  7     e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
  8     # syscall(exit, Num-test-failures)
  9     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 10     b8/copy-to-EAX  1/imm32/exit
 11     cd/syscall  0x80/imm8
 12 
 13 # read bytes from 'f' until (and including) a newline and store them into 's'
 14 # return true if no data found, false otherwise
 15 # just abort if 's' is too small
 16 read-line:  # f : (address buffered-file), s : (address stream byte) -> eof?/EAX
 17     # pseudocode:
 18     #   loop:
 19     #     if (s->write >= s->length) abort
 20     #     if (f->read >= f->write) populate stream from file
 21     #     if (f->write == 0) break
 22     #     AL = f->data[f->read]
 23     #     s->data[s->write] = AL
 24     #     ++f->read
 25     #     ++s->write
 26     #     if (AL == '\n') break
 27     # . prolog
 28     55/push-EBP
 29     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 30     # . save registers
 31     51/push-ECX
 32     52/push-EDX
 33     56/push-ESI
 34     57/push-EDI
 35     # ESI = f
 36     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 37     # ECX = f->read
 38     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(ESI+8) to ECX
 39     # EDI = s
 40     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
 41     # EDX = s->write
 42     8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy *EDI to EDX
 43 $read-line:loop:
 44     # if (s->write >= s->length) abort
 45     3b/compare                      1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # compare EDX with *(EDI+8)
 46     7d/jump-if-greater-or-equal  $read-line:abort/disp8
 47     # if (f->read >= f->write) populate stream from file
 48     3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # compare ECX with *(ESI+4)
 49     7c/jump-if-lesser  $read-line:from-stream/disp8
 50     # . clear-stream(stream = f+4)
 51     # . . push args
 52     8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy ESI+4 to EAX
 53     50/push-EAX
 54     # . . call
 55     e8/call  clear-stream/disp32
 56     # . . discard args
 57     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 58     # . f->read must now be 0; update its cache at ECX
 59     31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
 60     # . EAX = read(f->fd, stream = f+4)
 61     # . . push args
 62     50/push-EAX
 63     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
 64     # . . call
 65     e8/call  read/disp32
 66     # . . discard args
 67     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 68     # if (f->write == 0) return true
 69     # . if (EAX == 0) return true
 70     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
 71     75/jump-if-not-equal  $read-line:from-stream/disp8
 72     b8/copy-to-EAX  0xffffffff/imm32
 73     eb/jump  $read-line:end/disp8
 74 $read-line:from-stream:
 75     # AL = f->data[f->read]
 76     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 77     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
 78     # s->data[s->write] = AL
 79     88/copy-byte                    1/mod/*+disp8   4/rm32/sib    7/base/EDI  2/index/EDX   .           0/r32/AL    0xc/disp8       .                 # copy AL to *(EDI+EDX+12)
 80     # ++f->read
 81     41/increment-ECX
 82     # ++s->write
 83     42/increment-EDX
 84     # if (AL == '\n') return false
 85     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xa/imm32         # compare EAX
 86     75/jump-if-not-equal  $read-line:loop/disp8
 87     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 88 $read-line:end:
 89     # save f->read
 90     89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # copy ECX to *(ESI+8)
 91     # save s->write
 92     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           2/r32/EDX   .               .                 # copy EDX to *EDI
 93     # . restore registers
 94     5f/pop-to-EDI
 95     5e/pop-to-ESI
 96     5a/pop-to-EDX
 97     59/pop-to-ECX
 98     # . epilog
 99     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
100     5d/pop-to-EBP
101     c3/return
102 
103 $read-line:abort:
104     # . _write(2/stderr, error)
105     # . . push args
106     68/push  "read-line: line too long"/imm32
107     68/push  2/imm32/stderr
108     # . . call
109     e8/call  _write/disp32
110     # . . discard args
111     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
112     # . syscall(exit, 1)
113     bb/copy-to-EBX  1/imm32
114     b8/copy-to-EAX  1/imm32/exit
115     cd/syscall  0x80/imm8
116     # never gets here
117 
118 test-read-line:
119     # - check that read-line stops at a newline
120     # setup
121     # . clear-stream(_test-stream)
122     # . . push args
123     68/push  _test-stream/imm32
124     # . . call
125     e8/call  clear-stream/disp32
126     # . . discard args
127     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
128     # . clear-stream(_test-buffered-file+4)
129     # . . push args
130     b8/copy-to-EAX  _test-buffered-file/imm32
131     05/add-to-EAX  4/imm32
132     50/push-EAX
133     # . . call
134     e8/call  clear-stream/disp32
135     # . . discard args
136     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
137     # . clear-stream(_test-tmp-stream)
138     # . . push args
139     68/push  _test-tmp-stream/imm32
140     # . . call
141     e8/call  clear-stream/disp32
142     # . . discard args
143     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
144     # write(_test-stream, "ab\ncd")
145     # . write(_test-stream, "ab")
146     # . . push args
147     68/push  "ab"/imm32
148     68/push  _test-stream/imm32
149     # . . call
150     e8/call  write/disp32
151     # . . discard args
152     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
153     # . write(_test-stream, "\n")
154     # . . push args
155     68/push Newline/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     # . write(_test-stream, "cd")
162     # . . push args
163     68/push  "cd"/imm32
164     68/push  _test-stream/imm32
165     # . . call
166     e8/call  write/disp32
167     # . . discard args
168     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
169     # read a line from _test-stream (buffered by _test-buffered-file) into _test-tmp-stream
170     # . EAX = read-line(_test-buffered-file, _test-tmp-stream)
171     # . . push args
172     68/push  _test-tmp-stream/imm32
173     68/push  _test-buffered-file/imm32
174     # . . call
175     e8/call  read-line/disp32
176     # . . discard args
177     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
178     # check-ints-equal(EAX, 0/not-at-eof, msg)
179     # . . push args
180     68/push  "F - test-read-line: return value"/imm32
181     68/push  0/imm32/not-at-eof
182     50/push-EAX
183     # . . call
184     e8/call  check-ints-equal/disp32
185     # . . discard args
186     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
187     # check-next-stream-line-equal(_test-tmp-stream, "ab", msg)
188     # . . push args
189     68/push  "F - test-read-line"/imm32
190     68/push  "ab"/imm32
191     68/push  _test-tmp-stream/imm32
192     # . . call
193     e8/call  check-next-stream-line-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-line-returns-true-on-eof:
200     # setup
201     # . clear-stream(_test-stream)
202     # . . push args
203     68/push  _test-stream/imm32
204     # . . call
205     e8/call  clear-stream/disp32
206     # . . discard args
207     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
208     # . clear-stream(_test-buffered-file+4)
209     # . . push args
210     b8/copy-to-EAX  _test-buffered-file/imm32
211     05/add-to-EAX  4/imm32
212     50/push-EAX
213     # . . call
214     e8/call  clear-stream/disp32
215     # . . discard args
216     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
217     # . clear-stream(_test-tmp-stream)
218     # . . push args
219     68/push  _test-tmp-stream/imm32
220     # . . call
221     e8/call  clear-stream/disp32
222     # . . discard args
223     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
224     # write nothing
225     # EAX = read-line(_test-buffered-file, _test-tmp-stream)
226     # . . push args
227     68/push  _test-tmp-stream/imm32
228     68/push  _test-buffered-file/imm32
229     # . . call
230     e8/call  read-line/disp32
231     # . . discard args
232     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
233     # check-ints-equal(EAX, eof, msg)
234     # . . push args
235     68/push  "F - test-read-line-returns-true-on-eof"/imm32
236     68/push  0xffffffff/imm32/not-at-eof
237     50/push-EAX
238     # . . call
239     e8/call  check-ints-equal/disp32
240     # . . discard args
241     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
242     # end
243     c3/return
244 
245 test-read-line-reads-final-line-until-eof:
246     # setup
247     # . clear-stream(_test-stream)
248     # . . push args
249     68/push  _test-stream/imm32
250     # . . call
251     e8/call  clear-stream/disp32
252     # . . discard args
253     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
254     # . clear-stream(_test-buffered-file+4)
255     # . . push args
256     b8/copy-to-EAX  _test-buffered-file/imm32
257     05/add-to-EAX  4/imm32
258     50/push-EAX
259     # . . call
260     e8/call  clear-stream/disp32
261     # . . discard args
262     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
263     # . clear-stream(_test-tmp-stream)
264     # . . push args
265     68/push  _test-tmp-stream/imm32
266     # . . call
267     e8/call  clear-stream/disp32
268     # . . discard args
269     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
270     # write(_test-stream, "cd")
271     # . . push args
272     68/push  "cd"/imm32
273     68/push  _test-stream/imm32
274     # . . call
275     e8/call  write/disp32
276     # . . discard args
277     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
278     # read a line from _test-stream (buffered by _test-buffered-file) into _test-tmp-stream
279     # . EAX = read-line(_test-buffered-file, _test-tmp-stream)
280     # . . push args
281     68/push  _test-tmp-stream/imm32
282     68/push  _test-buffered-file/imm32
283     # . . call
284     e8/call  read-line/disp32
285     # . . discard args
286     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
287     # check-ints-equal(EAX, eof, msg)
288     # . . push args
289     68/push  "F - test-read-line-reads-final-line-until-eof: return value"/imm32
290     68/push  0xffffffff/imm32/not-at-eof
291     50/push-EAX
292     # . . call
293     e8/call  check-ints-equal/disp32
294     # . . discard args
295     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
296     # check-stream-equal(_test-tmp-stream, "cd", msg)
297     # . . push args
298     68/push  "F - test-read-line-reads-final-line-until-eof"/imm32
299     68/push  "cd"/imm32
300     68/push  _test-tmp-stream/imm32
301     # . . call
302     e8/call  check-stream-equal/disp32
303     # . . discard args
304     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
305     # end
306     c3/return
307 
308 # . . vim:nowrap:textwidth=0