https://github.com/akkartik/mu/blob/master/subx/061read-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     0/imm32  # standard input
     17     # current write index
     18     0/imm32
     19     # current read index
     20     0/imm32
     21     # length
     22     8/imm32
     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 #? Entry:  # run a single test, while debugging
     35 #?     e8/call test-read-byte-multiple/disp32
     36 #?     e8/call test-read-byte-refills-buffer/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/exit
     40 #?     cd/syscall  0x80/imm8
     41
     42 # return next byte value in EAX, with top 3 bytes cleared.
     43 # On reaching end of file, return 0xffffffff (Eof).
     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   5/rm32/EBP    .           .             .           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     # . f->read must now be 0; update its cache at ECX
     67     31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
     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     3d/compare-EAX-and  0/imm32
     78     75/jump-if-not-equal  $read-byte:from-stream/disp8
     79     b8/copy-to-EAX  0xffffffff/imm32/Eof
     80     eb/jump  $read-byte:end/disp8
     81 $read-byte:from-stream:
     82     # read byte 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 byte at *(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 # - tests
     98
     99 test-read-byte-single:
    100     # - check that read-byte returns first byte of 'file'
    101     # setup
    102     # . clear-stream(_test-stream)
    103     # . . push args
    104     68/push  _test-stream/imm32
    105     # . . call
    106     e8/call  clear-stream/disp32
    107     # . . discard args
    108     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    109     # . clear-stream(_test-buffered-file+4)
    110     # . . push args
    111     b8/copy-to-EAX  _test-buffered-file/imm32
    112     05/add-to-EAX  4/imm32
    113     50/push-EAX
    114     # . . call
    115     e8/call  clear-stream/disp32
    116     # . . discard args
    117     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    118     # . write(_test-stream, "Ab")
    119     # . . push args
    120     68/push  "Ab"/imm32
    121     68/push  _test-stream/imm32
    122     # . . call
    123     e8/call  write/disp32
    124     # . . discard args
    125     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    126     # read-byte(_test-buffered-file)
    127     # . . push args
    128     68/push  _test-buffered-file/imm32
    129     # . . call
    130     e8/call  read-byte/disp32
    131     # . . discard args
    132     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    133     # check-ints-equal(EAX, 'A', msg)
    134     # . . push args
    135     68/push  "F - test-read-byte-single"/imm32
    136     68/push  0x41/imm32
    137     50/push-EAX
    138     # . . call
    139     e8/call  check-ints-equal/disp32
    140     # . . discard args
    141     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    142     # . end
    143     c3/return
    144
    145 test-read-byte-multiple:
    146     # - call read-byte twice, check that second call returns second byte
    147     # setup
    148     # . clear-stream(_test-stream)
    149     # . . push args
    150     68/push  _test-stream/imm32
    151     # . . call
    152     e8/call  clear-stream/disp32
    153     # . . discard args
    154     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    155     # . clear-stream(_test-buffered-file+4)
    156     # . . push args
    157     b8/copy-to-EAX  _test-buffered-file/imm32
    158     05/add-to-EAX  4/imm32
    159     50/push-EAX
    160     # . . call
    161     e8/call  clear-stream/disp32
    162     # . . discard args
    163     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    164     # . write(_test-stream, "Ab")
    165     # . . push args
    166     68/push  "Ab"/imm32
    167     68/push  _test-stream/imm32
    168     # . . call
    169     e8/call  write/disp32
    170     # . . discard args
    171     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    172     # read-byte(_test-buffered-file)
    173     # . . push args
    174     68/push  _test-buffered-file/imm32
    175     # . . call
    176     e8/call  read-byte/disp32
    177     # . . discard args
    178     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    179     # read-byte(_test-buffered-file)
    180     # . . push args
    181     68/push  _test-buffered-file/imm32
    182     # . . call
    183     e8/call  read-byte/disp32
    184     # . . discard args
    185     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    186     # check-ints-equal(EAX, 'b', msg)
    187     # . . push args
    188     68/push  "F - test-read-byte-multiple"/imm32
    189     68/push  0x62/imm32
    190     50/push-EAX
    191     # . . call
    192     e8/call  check-ints-equal/disp32
    193     # . . discard args
    194     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    195     # . end
    196     c3/return
    197
    198 test-read-byte-end-of-file:
    199     # - call read-byte on an empty 'file', check that it returns 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     # read-byte(_test-buffered-file)
    218     # . . push args
    219     68/push  _test-buffered-file/imm32
    220     # . . call
    221     e8/call  read-byte/disp32
    222     # . . discard args
    223     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    224     # check-ints-equal(EAX, 0xffffffff, msg)
    225     # . . push args
    226     68/push  "F - test-read-byte-end-of-file"/imm32
    227     68/push  0xffffffff/imm32/Eof
    228     50/push-EAX
    229     # . . call
    230     e8/call  check-ints-equal/disp32
    231     # . . discard args
    232     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    233     # . end
    234     c3/return
    235
    236 test-read-byte-refills-buffer:
    237     # - consume buffered-file's buffer, check that next read-byte still works
    238     # setup
    239     # . clear-stream(_test-stream)
    240     # . . push args
    241     68/push  _test-stream/imm32
    242     # . . call
    243     e8/call  clear-stream/disp32
    244     # . . discard args
    245     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    246     # . clear-stream(_test-buffered-file+4)
    247     # . . push args
    248     b8/copy-to-EAX  _test-buffered-file/imm32
    249     05/add-to-EAX  4/imm32
    250     50/push-EAX
    251     # . . call
    252     e8/call  clear-stream/disp32
    253     # . . discard args
    254     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    255     # . write(_test-stream, "Abcdefgh")
    256     # . . push args
    257     68/push  "Abcdefgh"/imm32
    258     68/push  _test-stream/imm32
    259     # . . call
    260     e8/call  write/disp32
    261     # . . discard args
    262     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    263     # pretend buffer is full
    264     # . _test-buffered-file->read = 6  # >= _test-buffered-file->length
    265     b8/copy-to-EAX  _test-buffered-file/imm32
    266     c7          0/subop/copy        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         6/imm32           # copy to *(EAX+8)
    267     # read-byte(_test-buffered-file)
    268     # . . push args
    269     68/push  _test-buffered-file/imm32
    270     # . . call
    271     e8/call  read-byte/disp32
    272     # . . discard args
    273     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    274     # check-ints-equal(EAX, 'A', msg)
    275     # . . push args
    276     68/push  "F - test-read-byte-refills-buffer"/imm32
    277     68/push  0x41/imm32
    278     50/push-EAX
    279     # . . call
    280     e8/call  check-ints-equal/disp32
    281     # . . discard args
    282     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    283     # . end
    284     c3/return
    285
    286 == data
    287
    288 # a test buffered file for _test-stream
    289 _test-buffered-file:
    290     # file descriptor or (address stream)
    291     _test-stream/imm32
    292     # current write index
    293     0/imm32
    294     # current read index
    295     0/imm32
    296     # length
    297     6/imm32
    298     # data
    299     00 00 00 00 00 00  # 6 bytes
    300
    301 # . . vim:nowrap:textwidth=0