diff --git a/subx/056write.subx b/subx/056write.subx new file mode 100644 index 00000000..46cc985b --- /dev/null +++ b/subx/056write.subx @@ -0,0 +1,251 @@ +# write: like _write, but also support in-memory output streams (`ostream`s) +# in addition to file descriptors. +# +# Our first dependency-injected and testable primitive. We can pass it either +# a file descriptor or an address to an output stream or ostream. If a file +# descriptor is passed in, we _write to it using the right syscall. If a 'fake +# file descriptor' or ostream is passed in, we append to the output stream. +# This lets us redirect output in tests and check it later. +# +# We assume our data segment will never begin at an address shorter than +# 0x08000000, so any smaller arguments are assumed to be real file descriptors. +# +# An ostream looks like this: +# write: int # index at which writes go +# data: (array byte) # prefixed by length as usual + +== data + +# In-memory ostream for tests to write() to. +# Also illustrates the layout of ostreams. +Test-ostream: + # current write index + 00 00 00 00 + # length (= 8) + 08 00 00 00 + # data + 00 00 00 00 00 00 00 00 # 8 bytes + +== 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 test-write-appends/disp32 + e8/call run-tests/disp32 # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'. + # 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 + +write: # f : fd or (address stream), s : (address array byte) -> + # prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # save registers + 50/push-EAX + 51/push-ECX + 52/push-EDX + 53/push-EBX + 56/push-ESI + # if (f < 0x08000000) _write(f, s), return # f can't be a user-mode address, so treat it as a kernel file descriptor + 81 7/subop/compare 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none . . 8/disp8 0x08000000/imm32 # compare *(EBP+8) + 7f/jump-if-greater $write:else/disp8 + # push args + ff 6/subop/push 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none . . 8/disp8 . # push *(EBP+8) + ff 6/subop/push 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none . . 0xc/disp8 . # push *(EBP+12) + # call + e8/call _write/disp32 + # discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + eb/jump $write:end/disp8 +$write:else: + # otherwise, treat 'f' as a stream to append to + # + # pseudocode: + # destend = &f.data[t.length] + # oldw = f.write + # if s.length == 0 return + # f.write += s.length + # dest = &f.data[oldw] + # srcend = &s.data[s.length] + # src = &s.data[0] + # while true: + # if src >= srcend break + # if dest >= destend break # for now silently ignore filled up ostream buffer + # *dest = *src + # ++src + # ++dest + # + # key registers to set up for the loop: + # EAX/dest, ECX/destend, EBX/src, ESI/srcend + # we save EDX for byte operations (has to be one of the first 4 registers) + # + # register setup before the loop: + # EAX = *(EBP+8) # f + # EBX = *(EBP+12) # s + # ECX = *(EAX+4) # f.length + # ECX = EAX+8+ECX # destend = &f.data[f.length] + # ESI = *EAX # oldw = f.write + # EDX = *EBX # s.length + # *EAX = *EAX + EDX # update f.write (allowed to go past f.length) + # # do this here just because it's convenient + # EAX = EAX+8+ESI # dest = &f.data[f.write] + # ESI = EBX+4+EDX # srcend = &s.data[s.length] + # EBX = EBX+4 # src = &s.data[0] + # + # EAX = f + 8b/copy 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none 0/r32/EAX 8/disp8 . # copy *(EBP+8) to EAX + # EBX = s + 8b/copy 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none 3/r32/EBX 0xc/disp8 . # copy *(EBP+12) to EBX + # ECX = f.length + 8b/copy 1/mod/*+disp8 0/rm32/EAX . . . 1/r32/ECX 4/disp8 . # copy *(EAX+4) to ECX + # ECX/destend = &f.data[f.length] + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 8/disp8 . # copy EAX+ECX+8 to ECX + # ESI/oldw = f.write + 8b/copy 0/mod/indirect 0/rm32/EAX . . . 6/r32/ESI . . # copy *EAX to ESI + # EDX = s.length + 8b/copy 0/mod/indirect 3/rm32/EBX . . . 2/r32/EDX . . # copy *EBX to EDX + # if EDX == 0 return + 81 7/subop/compare 3/mod/direct 2/rm32/EDX . . . . . 0/imm32 # compare EDX + 74/jump-if-equal $write:end/disp8 + # f.write += s.length + 01/add 0/mod/indirect 0/rm32/EAX . . . 2/r32/EDX . . # add EDX to *EAX + # EAX/dest = &f.data[oldw] + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 6/index/ESI . 0/r32/EAX 8/disp8 . # copy EAX+ESI+8 to EAX + # ESI/srcend = &s.data[s.length] + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 3/base/EBX 2/index/EDX . 6/r32/ESI 4/disp8 . # copy EBX+EDX+4 to ESI + # EBX/src = &s.data + 81 0/subop/add 3/mod/direct 3/rm32/EBX . . . . . 4/imm32 # add to EBX + # while (true) +$write:loop: + # if EBX/src >= ESI/srcend break + 39/compare 3/mod/direct 3/rm32/EBX . . . 6/r32/ESI . . # compare EBX with ESI + 7d/jump-if-greater-or-equal $write:end/disp8 + # if EAX/dest >= ECX/destend break (for now silently ignore filled up ostream buffer) + 39/compare 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # compare EAX with ECX + 7d/jump-if-greater-or-equal $write:end/disp8 + # copy one byte + 8a/copy-byte 0/mod/indirect 3/rm32/EBX . . . 2/r32/DL . . # copy byte at *EBX to DL + 88/copy-byte 0/mod/indirect 0/rm32/EAX . . . 2/r32/DL . . # copy byte at DL to *EAX + # updates + 40/increment-EAX + 43/increment-EBX + eb/jump $write:loop/disp8 +$write:end: + # restore registers + 5e/pop-to-ESI + 5b/pop-to-EBX + 5a/pop-to-EDX + 59/pop-to-ECX + 58/pop-to-EAX + # epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +clear-ostream: # f : (address ostream) + # prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # save registers + 50/push-EAX + 51/push-ECX + # EAX = f + 8b/copy 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none 0/r32/EAX 8/disp8 . # copy *(EBP+8) to EAX + # ECX = f.length + 8b/copy 1/mod/*+disp8 0/rm32/EAX . . . 1/r32/ECX 4/disp8 . # copy *(EAX+4) to ECX + # ECX = &f.data[f.length] + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 8/disp8 . # copy EAX+ECX+8 to ECX + # f.write = 0 + c7/copy 0/mod/direct 0/rm32/EAX . . . . . 0/imm32 # copy to *EAX + # EAX = f.data + 81 0/subop/add 3/mod/direct 0/rm32/EAX . . . . . 8/imm32 # add to EAX + # while (true) +$clear-ostream:loop: + # if EAX >= ECX break + 39/compare 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # compare EAX with ECX + 7d/jump-if-greater-or-equal $clear-ostream:end/disp8 + # *EAX = 0 + c7/copy 0/mod/direct 0/rm32/EAX . . . . . 0/imm32 # copy to *EAX + # EAX += 4 + 81 0/subop/add 3/mod/direct 0/rm32/EAX . . . . . 4/imm32 # add to EAX + eb/jump $clear-ostream:loop/disp8 +$clear-ostream:end: + # restore registers + 59/pop-to-ECX + 58/pop-to-EAX + # epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-write-single: + # clear-ostream(Test-ostream) + # push args + 68/push Test-ostream/imm32 + # call + e8/call clear-ostream/disp32 + # discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # write(Test-ostream, "Ab") + # push args + 68/push "Ab"/imm32 + 68/push Test-ostream/imm32 + # call + e8/call write/disp32 + # discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(*Test-ostream.data, 41/A 62/b 00 00, msg) + # push args + 68/push "F - test-write-single"/imm32 + 68/push 0x006241/imm32/Ab + # push *Test-ostream.data + b8/copy-to-EAX Test-ostream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 8/disp8 . # push *(EAX+8) + # 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-write-appends: + # clear-ostream(Test-ostream) + # push args + 68/push Test-ostream/imm32 + # call + e8/call clear-ostream/disp32 + # discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # write(Test-ostream, "C") + # push args + 68/push "C"/imm32 + 68/push Test-ostream/imm32 + # call + e8/call write/disp32 + # discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # write(Test-ostream, "D") + # push args + 68/push "D"/imm32 + 68/push Test-ostream/imm32 + # call + e8/call write/disp32 + # discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(*Test-ostream.data, 43/C 44/D 00 00, msg) + # push args + 68/push "F - test-write-appends"/imm32 + 68/push 0x00004443/imm32/C-D + # push *Test-ostream.data + b8/copy-to-EAX Test-ostream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 8/disp8 . # push *(EAX+8) + # 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 diff --git a/subx/apps/crenshaw2-1 b/subx/apps/crenshaw2-1 index a924209d..52814a1d 100755 Binary files a/subx/apps/crenshaw2-1 and b/subx/apps/crenshaw2-1 differ diff --git a/subx/apps/factorial b/subx/apps/factorial index 44a3cd30..a1fa3905 100755 Binary files a/subx/apps/factorial and b/subx/apps/factorial differ