https://github.com/akkartik/mu/blob/main/linux/112read-byte.subx

      1 # read-byte-buffered: 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: a pointer to the backing store, followed by a 'buffer' stream
     14 Stdin:  # buffered-file
     15     # file descriptor or (addr stream byte)
     16     0/imm32  # standard input
     17 $Stdin->buffer:
     18     # inlined fields for a stream
     19     #   current write index
     20     0/imm32
     21     #   current read index
     22     0/imm32
     23     #   size
     24     8/imm32
     25     #   data
     26     00 00 00 00 00 00 00 00  # 8 bytes
     27
     28 # TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency. But
     29 # I don't want to type in 1024 bytes here.
     30
     31 == code
     32 #   instruction                     effective address                                                   register    displacement    immediate
     33 # . op          subop               mod             rm32          base        index         scale       r32
     34 # . 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
     35
     36 # Return next byte value in eax, with top 3 bytes cleared.
     37 # On reaching end of file, return 0xffffffff (Eof).
     38 read-byte-buffered:  # f: (addr buffered-file) -> byte-or-Eof/eax: byte
     39     # . prologue
     40     55/push-ebp
     41     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
     42     # . save registers
     43     51/push-ecx
     44     56/push-esi
     45     # esi = f
     46     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
     47     # ecx = f->read
     48     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(esi+8) to ecx
     49     # if (f->read >= f->write) populate stream from file
     50     3b/compare                      1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # compare ecx with *(esi+4)
     51     7c/jump-if-<  $read-byte-buffered:from-stream/disp8
     52     # . clear-stream(stream = f+4)
     53     # . . push args
     54     8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy esi+4 to eax
     55     50/push-eax
     56     # . . call
     57     e8/call  clear-stream/disp32
     58     # . . discard args
     59     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
     60     # . f->read must now be 0; update its cache at ecx
     61     31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
     62     # . eax = read(f->fd, stream = f+4)
     63     # . . push args
     64     50/push-eax
     65     ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
     66     # . . call
     67     e8/call  read/disp32
     68     # . . discard args
     69     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     70     # if (eax == 0) return 0xffffffff
     71     3d/compare-eax-and  0/imm32
     72     75/jump-if-!=  $read-byte-buffered:from-stream/disp8
     73     b8/copy-to-eax  0xffffffff/imm32/Eof
     74     eb/jump  $read-byte-buffered:end/disp8
     75 $read-byte-buffered:from-stream:
     76     # byte-or-Eof = f->data[f->read]
     77     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
     78     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
     79     # ++f->read
     80     ff          0/subop/increment   1/mod/*+disp8   6/rm32/esi    .           .             .           .           8/disp8         .                 # increment *(esi+8)
     81 $read-byte-buffered:end:
     82     # . restore registers
     83     5e/pop-to-esi
     84     59/pop-to-ecx
     85     # . epilogue
     86     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
     87     5d/pop-to-ebp
     88     c3/return
     89
     90 # - tests
     91
     92 test-read-byte-buffered-single:
     93     # - check that read-byte-buffered returns first byte of 'file'
     94     # setup
     95     # . clear-stream(_test-stream)
     96     # . . push args
     97     68/push  _test-stream/imm32
     98     # . . call
     99     e8/call  clear-stream/disp32
    100     # . . discard args
    101     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    102     # . clear-stream(_test-buffered-file->buffer)
    103     # . . push args
    104     68/push  $_test-buffered-file->buffer/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     # . write(_test-stream, "Ab")
    110     # . . push args
    111     68/push  "Ab"/imm32
    112     68/push  _test-stream/imm32
    113     # . . call
    114     e8/call  write/disp32
    115     # . . discard args
    116     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    117     # read-byte-buffered(_test-buffered-file)
    118     # . . push args
    119     68/push  _test-buffered-file/imm32
    120     # . . call
    121     e8/call  read-byte-buffered/disp32
    122     # . . discard args
    123     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    124     # check-ints-equal(eax, 'A', msg)
    125     # . . push args
    126     68/push  "F - test-read-byte-buffered-single"/imm32
    127     68/push  0x41/imm32
    128     50/push-eax
    129     # . . call
    130     e8/call  check-ints-equal/disp32
    131     # . . discard args
    132     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    133     # . end
    134     c3/return
    135
    136 test-read-byte-buffered-multiple:
    137     # - call read-byte-buffered twice, check that second call returns second byte
    138     # setup
    139     # . clear-stream(_test-stream)
    140     # . . push args
    141     68/push  _test-stream/imm32
    142     # . . call
    143     e8/call  clear-stream/disp32
    144     # . . discard args
    145     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    146     # . clear-stream($_test-buffered-file->buffer)
    147     # . . push args
    148     68/push  $_test-buffered-file->buffer/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     # . write(_test-stream, "Ab")
    154     # . . push args
    155     68/push  "Ab"/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     # read-byte-buffered(_test-buffered-file)
    162     # . . push args
    163     68/push  _test-buffered-file/imm32
    164     # . . call
    165     e8/call  read-byte-buffered/disp32
    166     # . . discard args
    167     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    168     # read-byte-buffered(_test-buffered-file)
    169     # . . push args
    170     68/push  _test-buffered-file/imm32
    171     # . . call
    172     e8/call  read-byte-buffered/disp32
    173     # . . discard args
    174     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    175     # check-ints-equal(eax, 'b', msg)
    176     # . . push args
    177     68/push  "F - test-read-byte-buffered-multiple"/imm32
    178     68/push  0x62/imm32
    179     50/push-eax
    180     # . . call
    181     e8/call  check-ints-equal/disp32
    182     # . . discard args
    183     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    184     # . end
    185     c3/return
    186
    187 test-read-byte-buffered-end-of-file:
    188     # - call read-byte-buffered on an empty 'file', check that it returns Eof
    189     # setup
    190     # . clear-stream(_test-stream)
    191     # . . push args
    192     68/push  _test-stream/imm32
    193     # . . call
    194     e8/call  clear-stream/disp32
    195     # . . discard args
    196     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    197     # . clear-stream($_test-buffered-file->buffer)
    198     # . . push args
    199     68/push  $_test-buffered-file->buffer/imm32
    200     # . . call
    201     e8/call  clear-stream/disp32
    202     # . . discard args
    203     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    204     # read-byte-buffered(_test-buffered-file)
    205     # . . push args
    206     68/push  _test-buffered-file/imm32
    207     # . . call
    208     e8/call  read-byte-buffered/disp32
    209     # . . discard args
    210     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    211     # check-ints-equal(eax, 0xffffffff, msg)
    212     # . . push args
    213     68/push  "F - test-read-byte-buffered-end-of-file"/imm32
    214     68/push  0xffffffff/imm32/Eof
    215     50/push-eax
    216     # . . call
    217     e8/call  check-ints-equal/disp32
    218     # . . discard args
    219     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    220     # . end
    221     c3/return
    222
    223 test-read-byte-buffered-refills-buffer:
    224     # - consume buffered-file's buffer, check that next read-byte-buffered still works
    225     # setup
    226     # . clear-stream(_test-stream)
    227     # . . push args
    228     68/push  _test-stream/imm32
    229     # . . call
    230     e8/call  clear-stream/disp32
    231     # . . discard args
    232     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    233     # . clear-stream($_test-buffered-file->buffer)
    234     # . . push args
    235     68/push  $_test-buffered-file->buffer/imm32
    236     # . . call
    237     e8/call  clear-stream/disp32
    238     # . . discard args
    239     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    240     # . write(_test-stream, "Abcdefgh")
    241     # . . push args
    242     68/push  "Abcdefgh"/imm32
    243     68/push  _test-stream/imm32
    244     # . . call
    245     e8/call  write/disp32
    246     # . . discard args
    247     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    248     # pretend buffer is full
    249     # . _test-buffered-file->read = 6  # >= _test-buffered-file->size
    250     b8/copy-to-eax  _test-buffered-file/imm32
    251     c7          0/subop/copy        1/mod/*+disp8   0/rm32/eax    .           .             .           .           8/disp8         6/imm32           # copy to *(eax+8)
    252     # read-byte-buffered(_test-buffered-file)
    253     # . . push args
    254     68/push  _test-buffered-file/imm32
    255     # . . call
    256     e8/call  read-byte-buffered/disp32
    257     # . . discard args
    258     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    259     # check-ints-equal(eax, 'A', msg)
    260     # . . push args
    261     68/push  "F - test-read-byte-buffered-refills-buffer"/imm32
    262     68/push  0x41/imm32
    263     50/push-eax
    264     # . . call
    265     e8/call  check-ints-equal/disp32
    266     # . . discard args
    267     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    268     # . end
    269     c3/return
    270
    271 # Return next byte value in eax, with top 3 bytes cleared.
    272 # Abort on reaching end of stream.
    273 read-byte:  # s: (addr stream byte) -> result/eax: byte
    274     # . prologue
    275     55/push-ebp
    276     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    277     # . save registers
    278     51/push-ecx
    279     56/push-esi
    280     # esi = s
    281     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
    282     # ecx = s->read
    283     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
    284     # if (f->read >= f->write) abort
    285     3b/compare                      0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # compare ecx with *esi
    286     0f 8d/jump-if->=  $read-byte:abort/disp32
    287     # result = f->data[f->read]
    288     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
    289     8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
    290     # ++f->read
    291     ff          0/subop/increment   1/mod/*+disp8   6/rm32/esi    .           .             .           .           4/disp8         .                 # increment *(esi+4)
    292 $read-byte:end:
    293     # . restore registers
    294     5e/pop-to-esi
    295     59/pop-to-ecx
    296     # . epilogue
    297     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    298     5d/pop-to-ebp
    299     c3/return
    300
    301 $read-byte:abort:
    302     # . _write(2/stderr, error)
    303     # . . push args
    304     68/push  "read-byte: empty stream\n"/imm32
    305     68/push  2/imm32/stderr
    306     # . . call
    307     e8/call  _write/disp32
    308     # . . discard args
    309     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    310     # . syscall(exit, 1)
    311     bb/copy-to-ebx  1/imm32
    312     e8/call  syscall_exit/disp32
    313     # never gets here
    314
    315 == data
    316
    317 # a test buffered file for _test-stream
    318 _test-buffered-file:  # buffered-file
    319     # file descriptor or (addr stream byte)
    320     _test-stream/imm32
    321 $_test-buffered-file->buffer:
    322     # current write index
    323     0/imm32
    324     # current read index
    325     0/imm32
    326     # size
    327     6/imm32
    328     # data
    329     00 00 00 00 00 00  # 6 bytes
    330
    331 _test-input-stream:  # (stream byte)
    332     # current write index
    333     0/imm32
    334     # current read index
    335     0/imm32
    336     # size
    337     0x400/imm32  # 1024 bytes
    338     # data (64 lines x 16 bytes/line)
    339     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    340     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    341     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    342     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    343     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    344     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    345     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    346     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    347     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    348     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    349     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    350     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    351     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    352     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    353     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    354     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    355     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    356     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    357     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    358     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    359     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    360     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    361     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    362     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    363     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    364     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    365     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    366     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    367     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    368     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    369     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    370     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    371     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    372     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    373     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    374     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    375     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    376     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    377     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    378     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    379     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    380     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    381     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    382     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    383     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    384     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    385     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    386     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    387     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    388     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    389     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    390     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    391     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    392     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    393     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    394     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    395     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    396     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    397     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    398     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    399     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    400     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    401     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    402     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    403
    404 # a test buffered file for _test-input-stream
    405 _test-input-buffered-file:  # buffered-file
    406     # file descriptor or (addr stream byte)
    407     _test-input-stream/imm32
    408 $_test-input-buffered-file->buffer:
    409     # current write index
    410     0/imm32
    411     # current read index
    412     0/imm32
    413     # size
    414     6/imm32
    415     # data
    416     00 00 00 00 00 00  # 6 bytes
    417
    418 # . . vim:nowrap:textwidth=0