about summary refs log tree commit diff stats
path: root/subx/059read-byte.subx
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2018-11-19 23:11:00 -0800
committerKartik Agaram <vc@akkartik.com>2018-11-19 23:11:00 -0800
commitbd6f1928d7030ba618bbc9eade737a2cc5420e51 (patch)
tree5c1267b30527045fbf1a55c120f3f0e61fd5eee3 /subx/059read-byte.subx
parenta6061b9fd2e1476172ebe67376077bd09b516e41 (diff)
downloadmu-bd6f1928d7030ba618bbc9eade737a2cc5420e51.tar.gz
4755 - read-byte (sometimes called getchar)
Diffstat (limited to 'subx/059read-byte.subx')
-rw-r--r--subx/059read-byte.subx243
1 files changed, 243 insertions, 0 deletions
diff --git a/subx/059read-byte.subx b/subx/059read-byte.subx
new file mode 100644
index 00000000..da6f8409
--- /dev/null
+++ b/subx/059read-byte.subx
@@ -0,0 +1,243 @@
+# read-byte: one higher-level abstraction atop 'read'.
+#
+# There are many situations where 'read' is a lot to manage, and we need
+# to abstract some details away. One of them is when we want to read a file
+# character by character. In this situation we follow C's FILE data structure,
+# which manages the underlying file descriptor together with the buffer it
+# reads into. We call our version 'buffered-file'. Should be useful with other
+# primitives as well, in later layers.
+
+== data
+
+# The buffered file for standard input. Also illustrates the layout for
+# buffered-file.
+
+Stdin:
+  # file descriptor or (address stream)
+  00 00 00 00  # 0 = standard input
+  # current write index
+  00 00 00 00
+  # current read index
+  00 00 00 00
+  # length (8)
+  08 00 00 00
+  # data
+  00 00 00 00 00 00 00 00  # 8 bytes
+
+# TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency.
+
+== code
+
+# instruction                     effective address                                                   operand     displacement    immediate
+# op          subop               mod             rm32          base        index         scale       r32
+# 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
+
+# main:  (manual test if this is the last file loaded)
+  e8/call  run-tests/disp32  # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.
+#?   e8/call  test-read-byte-multiple/disp32
+  # syscall(exit, Num-test-failures)
+  8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           1/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
+  b8/copy-to-EAX  1/imm32
+  cd/syscall  0x80/imm8
+
+# return next byte value in EAX, with top 3 bytes cleared.
+# On EOF, return 0xffffffff.
+read-byte:  # f : (address buffered-file) -> byte/EAX
+  # prolog
+  55/push-EBP
+  89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+  # save registers
+  51/push-ECX
+  56/push-ESI
+  # ESI = f
+  8b/copy                         1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
+  # ECX = f.read
+  8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # copy *(ESI+8) to ECX
+  ## if (f.read < f.write) read byte from stream
+  3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # compare ECX with *(ESI+4)
+  7c/jump-if-lesser  $read-byte:from-stream/disp8
+  # clear-stream(stream = f+4)
+    # push args
+  8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy ESI+4 to EAX
+  50/push-EAX
+    # call
+  e8/call  clear-stream/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+  # EAX = read(f.fd, stream = f+4)
+    # push args
+  50/push-EAX
+  ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
+    # call
+  e8/call  read/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+  # if EAX = 0 return 0xffffffff
+  81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
+  75/jump-if-not-equal  $read-byte:from-stream/disp8
+  b8/copy-to-EAX  0xffffffff/imm32
+  eb/jump  $read-byte:end/disp8
+$read-byte:from-stream:
+  # AL = f.data[f.read]
+  31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
+  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
+  # ++f.read
+  ff          0/subop/increment   1/mod/*+disp8   6/rm32/ESI    .           .             .           .           8/disp8         .                 # increment *(ESI+8)
+$read-byte:end:
+  # restore registers
+  5e/pop-to-ESI
+  59/pop-to-ECX
+  # epilog
+  89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+  5d/pop-to-EBP
+  c3/return
+
+## tests
+
+test-read-byte-single:
+  ## check that read-byte returns first byte of 'file'
+  # clear-stream(_test-stream)
+    # push args
+  68/push  _test-stream/imm32
+    # call
+  e8/call  clear-stream/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+  # clear-stream(_test-buffered-file+4)
+    # push args
+  b8/copy-to-EAX  _test-buffered-file/imm32
+  05/add-to-EAX  4/imm32
+  50/push-EAX
+    # call
+  e8/call  clear-stream/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+  # write(_test-stream, "Ab")
+    # push args
+  68/push  "Ab"/imm32
+  68/push  _test-stream/imm32
+    # call
+  e8/call  write/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+  # read-byte(_test-buffered-file)
+    # push args
+  68/push  _test-buffered-file/imm32
+    # call
+  e8/call  read-byte/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+  # check-ints-equal(EAX, 'A')
+    # push args
+  68/push  "F - test-read-byte-single"/imm32
+  68/push  0x41/imm32
+  50/push-EAX
+    # call
+  e8/call  check-ints-equal/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+  # end
+  c3/return
+
+test-read-byte-multiple:
+  ## call read-byte twice, check that second call returns second byte
+  # clear-stream(_test-stream)
+    # push args
+  68/push  _test-stream/imm32
+    # call
+  e8/call  clear-stream/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+  # clear-stream(_test-buffered-file+4)
+    # push args
+  b8/copy-to-EAX  _test-buffered-file/imm32
+  05/add-to-EAX  4/imm32
+  50/push-EAX
+    # call
+  e8/call  clear-stream/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+  # write(_test-stream, "Ab")
+    # push args
+  68/push  "Ab"/imm32
+  68/push  _test-stream/imm32
+    # call
+  e8/call  write/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+  # read-byte(_test-buffered-file)
+    # push args
+  68/push  _test-buffered-file/imm32
+    # call
+  e8/call  read-byte/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+  # read-byte(_test-buffered-file)
+    # push args
+  68/push  _test-buffered-file/imm32
+    # call
+  e8/call  read-byte/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+  # check-ints-equal(EAX, 'b')
+    # push args
+  68/push  "F - test-read-byte-multiple"/imm32
+  68/push  0x62/imm32
+  50/push-EAX
+    # call
+  e8/call  check-ints-equal/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+  # end
+  c3/return
+
+test-read-byte-end-of-file:
+  ## call read-byte on an empty 'file', check that it returns -1
+  # clear-stream(_test-stream)
+    # push args
+  68/push  _test-stream/imm32
+    # call
+  e8/call  clear-stream/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+  # clear-stream(_test-buffered-file+4)
+    # push args
+  b8/copy-to-EAX  _test-buffered-file/imm32
+  05/add-to-EAX  4/imm32
+  50/push-EAX
+    # call
+  e8/call  clear-stream/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+  # read-byte(_test-buffered-file)
+    # push args
+  68/push  _test-buffered-file/imm32
+    # call
+  e8/call  read-byte/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+  # check-ints-equal(EAX, -1)
+    # push args
+  68/push  "F - test-read-byte-end-of-file"/imm32
+  68/push  -1/imm32
+  50/push-EAX
+    # call
+  e8/call  check-ints-equal/disp32
+    # discard args
+  81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+  # end
+  c3/return
+
+== data
+
+_test-buffered-file:
+  # file descriptor or (address stream)
+  _test-stream/imm32
+  # current write index
+  00 00 00 00
+  # current read index
+  00 00 00 00
+  # length (8)
+  08 00 00 00
+  # data
+  00 00 00 00 00 00 00 00  # 8 bytes