From bd6f1928d7030ba618bbc9eade737a2cc5420e51 Mon Sep 17 00:00:00 2001 From: Kartik Agaram Date: Mon, 19 Nov 2018 23:11:00 -0800 Subject: 4755 - read-byte (sometimes called getchar) --- subx/059read-byte.subx | 243 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 subx/059read-byte.subx (limited to 'subx/059read-byte.subx') 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 -- cgit 1.4.1-2-gfad0