From 51b4f888dd20653bb218f06e1221a7007446fcd5 Mon Sep 17 00:00:00 2001 From: Kartik Agaram Date: Sun, 3 Feb 2019 23:29:46 -0800 Subject: 4950 --- subx/058stop.subx | 213 ----------- subx/059read.subx | 366 ------------------- subx/059stop.subx | 213 +++++++++++ subx/060read-byte.subx | 302 ---------------- subx/060read.subx | 366 +++++++++++++++++++ subx/061read-byte.subx | 302 ++++++++++++++++ subx/061write-stream.subx | 243 ------------- subx/062error.subx | 57 --- subx/062write-stream.subx | 243 +++++++++++++ subx/063error.subx | 57 +++ subx/063write-byte.subx | 233 ------------ subx/064hex.subx | 809 ----------------------------------------- subx/064write-byte.subx | 233 ++++++++++++ subx/065hex.subx | 809 +++++++++++++++++++++++++++++++++++++++++ subx/065print-byte.subx | 104 ------ subx/066print-byte.subx | 104 ++++++ subx/066write-buffered.subx | 220 ------------ subx/067error-byte.subx | 112 ------ subx/067write-buffered.subx | 220 ++++++++++++ subx/068allocate.subx | 207 ----------- subx/068error-byte.subx | 112 ++++++ subx/069allocate.subx | 207 +++++++++++ subx/069new-stream.subx | 124 ------- subx/070new-stream.subx | 124 +++++++ subx/070read-line.subx | 312 ---------------- subx/071read-line.subx | 312 ++++++++++++++++ subx/071slice.subx | 520 --------------------------- subx/072next-token.subx | 849 -------------------------------------------- subx/072slice.subx | 520 +++++++++++++++++++++++++++ subx/073next-token.subx | 849 ++++++++++++++++++++++++++++++++++++++++++++ 30 files changed, 4671 insertions(+), 4671 deletions(-) delete mode 100644 subx/058stop.subx delete mode 100644 subx/059read.subx create mode 100644 subx/059stop.subx delete mode 100644 subx/060read-byte.subx create mode 100644 subx/060read.subx create mode 100644 subx/061read-byte.subx delete mode 100644 subx/061write-stream.subx delete mode 100644 subx/062error.subx create mode 100644 subx/062write-stream.subx create mode 100644 subx/063error.subx delete mode 100644 subx/063write-byte.subx delete mode 100644 subx/064hex.subx create mode 100644 subx/064write-byte.subx create mode 100644 subx/065hex.subx delete mode 100644 subx/065print-byte.subx create mode 100644 subx/066print-byte.subx delete mode 100644 subx/066write-buffered.subx delete mode 100644 subx/067error-byte.subx create mode 100644 subx/067write-buffered.subx delete mode 100644 subx/068allocate.subx create mode 100644 subx/068error-byte.subx create mode 100644 subx/069allocate.subx delete mode 100644 subx/069new-stream.subx create mode 100644 subx/070new-stream.subx delete mode 100644 subx/070read-line.subx create mode 100644 subx/071read-line.subx delete mode 100644 subx/071slice.subx delete mode 100644 subx/072next-token.subx create mode 100644 subx/072slice.subx create mode 100644 subx/073next-token.subx (limited to 'subx') diff --git a/subx/058stop.subx b/subx/058stop.subx deleted file mode 100644 index f9e67f9e..00000000 --- a/subx/058stop.subx +++ /dev/null @@ -1,213 +0,0 @@ -# stop: dependency-injected wrapper around the exit() syscall -# -# We'd like to be able to write tests for functions that call exit(), and to -# make assertions about whether they exit() or not in a given situation. To -# achieve this we'll call exit() via a smarter wrapper called 'stop'. -# -# In the context of a test, calling a function X that calls 'stop' (directly -# or through further intervening calls) will unwind the stack until X returns, -# so that we can say check any further assertions after the execution of X. To -# achieve this end, we'll pass the return address of X as a 'target' argument -# into X, plumbing it through to 'stop'. When 'stop' gets a non-null target it -# unwinds the stack until the target. If it gets a null target it calls -# exit(). -# -# We'd also like to get the exit status out of 'stop', so we'll combine the -# input target with an output status parameter into a type called 'exit-descriptor'. -# -# So the exit-descriptor looks like this: -# target : address # return address for 'stop' to unwind to -# value : int # exit status stop was called with -# -# 'stop' thus takes two parameters: an exit-descriptor and the exit status. -# -# 'stop' won't bother cleaning up any other processor state besides the stack, -# such as registers. Only ESP will have a well-defined value after 'stop' -# returns. (This is a poor man's setjmp/longjmp, if you know what that is.) -# -# Before you can call any function that may call 'stop', you need to pass in an -# exit-descriptor to it. To create an exit-descriptor use 'tailor-exit-descriptor' -# below. It's not the most pleasant abstraction in the world. -# -# An exit-descriptor's target is its input, computed during 'tailor-exit-descriptor'. -# Its value is its output, computed during stop and available to the test. - -== code -# instruction effective address register 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: - 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-stop-skips-returns-on-exit/disp32 - # syscall(exit, Num-test-failures) - 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -# Configure an exit-descriptor for a call pushing 'nbytes' bytes of args to -# the stack. -# Ugly that we need to know the size of args, but so it goes. -tailor-exit-descriptor: # ed : (address exit-descriptor), nbytes : int -> - # . 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 = nbytes - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 0xc/disp8 . # copy *(EBP+12) to EAX - # Let X be the value of ESP in the caller, before the call to tailor-exit-descriptor. - # The return address for a call in the caller's body will be at: - # X-8 if the caller takes 4 bytes of args for the exit-descriptor (add 4 bytes for the return address) - # X-12 if the caller takes 8 bytes of args - # ..and so on - # That's the value we need to return: X-nbytes-4 - # - # However, we also need to account for the perturbance to ESP caused by the - # call to tailor-exit-descriptor. It pushes 8 bytes of args followed by 4 - # bytes for the return address and 4 bytes to push EBP above. - # So EBP at this point is X-16. - # - # So the return address for the next call in the caller is: - # EBP+8 if the caller takes 4 bytes of args - # EBP+4 if the caller takes 8 bytes of args - # EBP if the caller takes 12 bytes of args - # EBP-4 if the caller takes 16 bytes of args - # ..and so on - # That's EBP+12-nbytes. - # option 1: 6 + 3 bytes -#? 2d/subtract 3/mod/direct 0/rm32/EAX . . . . . 8/imm32 # subtract from EAX -#? 8d/copy-address 0/mod/indirect 4/rm32/sib 5/base/EBP 0/index/EAX . 0/r32/EAX . . # copy EBP+EAX to EAX - # option 2: 2 + 4 bytes - f7 3/subop/negate 3/mod/direct 0/rm32/EAX . . . . . . # negate EAX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 5/base/EBP 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy EBP+EAX+12 to EAX - # copy EAX to ed->target - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 8/disp8 . # copy *(EBP+8) to ECX - 89/copy 0/mod/indirect 1/rm32/ECX . . . 0/r32/EAX . . # copy EAX to *ECX - # initialize ed->value - c7 0/subop/copy 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 0/imm32 # copy to *(ECX+4) -$tailor-exit-descriptor: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 - -stop: # ed : (address exit-descriptor), value : int - # no prolog; one way or another, we're going to clobber registers - # EAX = ed - 8b/copy 1/mod/*+disp8 4/rm32/sib 4/base/ESP 4/index/none . 0/r32/EAX 4/disp8 . # copy *(ESP+4) to EAX - # exit(value) if ed->target == 0 - 81 7/subop/compare 0/mod/indirect 0/rm32/EAX . . . . . 0/imm32 # compare *EAX - 75/jump-if-not-equal $stop:fake/disp8 - # syscall(exit, value) - 8b/copy 1/mod/*+disp8 4/rm32/sib 4/base/ESP 4/index/none . 3/r32/EBX 8/disp8 . # copy *(ESP+8) to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 -$stop:fake: - # ed->value = value+1 - 8b/copy 1/mod/*+disp8 4/rm32/sib 4/base/ESP 4/index/none . 1/r32/ECX 8/disp8 . # copy *(ESP+8) to ECX - 41/inc-ECX - 89/copy 1/mod/*+disp8 0/rm32/EAX . . . 1/r32/ECX 4/disp8 . # copy ECX to *(EAX+4) - # non-local jump to ed->target - 8b/copy 0/mod/indirect 0/rm32/EAX . . . 4/r32/ESP . . # copy *EAX to ESP -$stop:end: - c3/return # doesn't return to caller - -test-stop-skips-returns-on-exit: - # This looks like the standard prolog, but is here for different reasons. - # A function calling 'stop' can't rely on EBP persisting past the call. - # - # Use EBP here as a stable base to refer to locals and arguments from in the - # presence of push/pop/call instructions. - # *Don't* use EBP as a way to restore ESP. - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # Make room for an exit descriptor on the stack. That's almost always the - # right place for it, available only as long as it's legal to use. Once this - # containing function returns we'll need a new exit descriptor. - # var ed/EAX : (address exit-descriptor) - 81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP - 89/copy 3/mod/direct 0/rm32/EAX . . . 4/r32/ESP . . # copy ESP to EAX - # Size the exit-descriptor precisely for the next call below, to _test-stop-1. - # tailor-exit-descriptor(ed, 4) - # . . push args - 68/push 4/imm32/nbytes-of-args-for-_test-stop-1 - 50/push-EAX - # . . call - e8/call tailor-exit-descriptor/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # . _test-stop-1(ed) - # . . push args - 50/push-EAX - # . . call - e8/call _test-stop-1/disp32 - # registers except ESP may be clobbered at this point - # restore args - 58/pop-to-EAX - # check that _test-stop-1 tried to call exit(1) - # check-ints-equal(ed->value, 2, msg) # i.e. stop was called with value 1 - # . . push args - 68/push "F - test-stop-skips-returns-on-exit"/imm32 - 68/push 2/imm32 - # . . push ed->value - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # . epilog - # don't restore ESP from EBP; manually reclaim locals - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - 5d/pop-to-EBP - c3/return - -_test-stop-1: # ed : (address exit-descriptor) - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # _test-stop-2(ed) - # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) - # . . call - e8/call _test-stop-2/disp32 - # should never get past this point -$_test-stop-1:dead-end: - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # signal test failed: check-ints-equal(1, 0, msg) - # . . push args - 68/push "F - test-stop-skips-returns-on-exit"/imm32 - 68/push 0/imm32 - 68/push 1/imm32 - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -_test-stop-2: # ed : (address exit-descriptor) - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # . stop(ed, 1) - # . . push args - 68/push 1/imm32 - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) - # . . call - e8/call stop/disp32 - # should never get past this point -$_test-stop-2:dead-end: - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -# . . vim:nowrap:textwidth=0 diff --git a/subx/059read.subx b/subx/059read.subx deleted file mode 100644 index 42056a13..00000000 --- a/subx/059read.subx +++ /dev/null @@ -1,366 +0,0 @@ -# read: analogously to write, support reading from in-memory streams in -# addition to file descriptors. -# -# We can pass it either a file descriptor or an address to a stream. If a -# file descriptor is passed in, we _read from it using the right syscall. If a -# stream is passed in (a fake file descriptor), we read from it instead. This -# lets us initialize input for tests. -# -# A little counter-intuitively, the output of 'read' ends up in.. a stream. So -# tests end up doing a redundant copy. Why? Well, consider the alternatives: -# -# a) Reading into a string, and returning a pointer to the end of the read -# region, or a count of bytes written. Now this count or end pointer must be -# managed separately by the caller, which can be error-prone. -# -# b) Having 'read' return a buffer that it allocates. But there's no way to -# know in advance how large to make the buffer. If you read less than the -# size of the buffer you again end up needing to manage initialized vs -# uninitialized memory. -# -# c) Creating more helpful variants like 'read-byte' or 'read-until' which -# also can take a file descriptor or stream, just like 'write'. But such -# primitives don't exist in the Linux kernel, so we'd be implementing them -# somehow, either with more internal buffering or by making multiple -# syscalls. -# -# Reading into a stream avoids these problems. The buffer is externally -# provided and the caller has control over where it's allocated, its lifetime, -# and so on. The buffer's read and write pointers are internal to it so it's -# easier to keep in a consistent state. And it can now be passed directly to -# helpers like 'read-byte' or 'read-until' that only need to support streams, -# never file descriptors. -# -# Like with 'write', 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. -# -# As a reminder, a stream looks like this: -# write: int # index at which to write to next -# read: int # index at which to read next -# data: (array byte) # prefixed by length as usual - -== code -# instruction effective address register 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: - 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -read: # f : fd or (address stream), s : (address stream) -> num-bytes-read/EAX - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # if (f < 0x08000000) return _read(f, s) # f can't be a user-mode address, so treat it as a kernel file descriptor - 81 7/subop/compare 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 0x08000000/imm32 # compare *(EBP+8) - 7d/jump-if-greater-or-equal $read:fake/disp8 - # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) - # . . call - e8/call _read/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # return - eb/jump $read:end/disp8 -$read:fake: - # otherwise, treat 'f' as a stream to scan from - # . save registers - 56/push-ESI - 57/push-EDI - # ESI = f - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI - # EDI = s - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 7/r32/EDI 0xc/disp8 . # copy *(EBP+12) to ESI - # EAX = _append-4(out = &s->data[s->write], outend = &s->data[s->length], - # in = &f->data[f->read], inend = &f->data[f->write]) - # . . push &f->data[f->write] - 8b/copy 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # copy *ESI to EAX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy ESI+EAX+12 to EAX - 50/push-EAX - # . . push &f->data[f->read] - 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 0/r32/EAX 4/disp8 . # copy *(ESI+4) to EAX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy ESI+EAX+12 to EAX - 50/push-EAX - # . . push &s->data[s->length] - 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 8/disp8 . # copy *(EDI+8) to EAX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 7/base/EDI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy EDI+EAX+12 to EAX - 50/push-EAX - # . . push &s->data[s->write] - 8b/copy 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # copy *EDI to EAX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 7/base/EDI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy EDI+EAX+12 to EAX - 50/push-EAX - # . . call - e8/call _append-4/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP - # s->write += EAX - 01/add 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # add EAX to *EDI - # f->read += EAX - 01/add 1/mod/*+disp8 6/rm32/ESI . . . 0/r32/EAX 4/disp8 . # add EAX to *(ESI+4) - # . restore registers - 5f/pop-to-EDI - 5e/pop-to-ESI -$read:end: - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -# - helpers - -# idea: a clear-if-empty method on streams that clears only if f->read == f->write -# Unclear how I'd use it, though. Callers seem to need the check anyway. -# Maybe a better helper would be 'empty-stream?' - -_read: # fd : int, s : (address stream) -> num-bytes-read/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 - 52/push-EDX - 53/push-EBX - 56/push-ESI - # ESI = s - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 0xc/disp8 . # copy *(EBP+12) to ESI - # EAX = s->write - 8b/copy 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # copy *ESI to EAX - # EDX = s->length - 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 2/r32/EDX 8/disp8 . # copy *(ESI+8) to EDX - # syscall(read, fd, &s->data[s->write], s->length - s->write) - # . . fd : EBX - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 3/r32/EBX 8/disp8 . # copy *(EBP+8) to EBX - # . . data : ECX = &s->data[s->write] - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 1/r32/ECX 0xc/disp8 . # copy ESI+EAX+12 to ECX - # . . size : EDX = s->length - s->write - 29/subtract 3/mod/direct 2/rm32/EDX . . . 0/r32/EAX . . # subtract EAX from EDX - # . . syscall - b8/copy-to-EAX 3/imm32/read - cd/syscall 0x80/imm8 - # add the result EAX to s->write - 01/add 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # add EAX to *ESI -$_read:end: - # . restore registers - 5e/pop-to-ESI - 5b/pop-to-EBX - 5a/pop-to-EDX - 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 - - # Two options: - # 1 (what we have above): - # ECX = s - # EAX = s->write - # EDX = s->length - # # syscall - # ECX = lea ECX+EAX+12 - # EDX = sub EDX EAX - # - # 2: - # ECX = s - # EDX = s->length - # ECX = &s->data - # # syscall - # ECX = add ECX, s->write - # EDX = sub EDX, s->write - # - # Not much to choose between the two? Option 2 performs a duplicate load to - # use one less register, but doesn't increase the amount of spilling (ECX - # and EDX must be used, and EAX must be clobbered anyway). - -# - tests - -test-read-single: - # - write a single character into _test-stream, then read from its buffered-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-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - # . . 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(_test-stream, _test-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call read/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 2, msg) - # . . push args - 68/push "F - test-read-single: return EAX"/imm32 - 68/push 2/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 - # check-ints-equal(*_test-stream-buffer->data, 41/A 62/b 00 00, msg) - # . . push args - 68/push "F - test-read-single"/imm32 - 68/push 0x006241/imm32/Ab - # . . push *_test-stream-buffer->data - b8/copy-to-EAX _test-stream-buffer/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) - # . . 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-is-stateful: - # - make two consecutive reads, check that their results are appended - # 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-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - # . . 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, "C") - # . . push args - 68/push "C"/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(_test-stream, _test-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call read/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # write(_test-stream, "D") - # . . push args - 68/push "D"/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(_test-stream, _test-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call read/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(*_test-stream-buffer->data, 43/C 44/D 00 00, msg) - # . . push args - 68/push "F - test-read-is-stateful"/imm32 - 68/push 0x00004443/imm32/C-D - # . . push *_test-stream-buffer->data - b8/copy-to-EAX _test-stream-buffer/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) - # . . 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-returns-0-on-end-of-file: - # - read after hitting end-of-file, check that result is 0 - # setup - # . 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-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - # . . 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 - # first read gets to end-of-file - # . read(_test-stream, _test-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call read/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # second read - # . read(_test-stream, _test-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call read/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-read-returns-0-on-end-of-file"/imm32 - 68/push 0/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-stream-buffer: - # 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 - -# . . vim:nowrap:textwidth=0 diff --git a/subx/059stop.subx b/subx/059stop.subx new file mode 100644 index 00000000..f9e67f9e --- /dev/null +++ b/subx/059stop.subx @@ -0,0 +1,213 @@ +# stop: dependency-injected wrapper around the exit() syscall +# +# We'd like to be able to write tests for functions that call exit(), and to +# make assertions about whether they exit() or not in a given situation. To +# achieve this we'll call exit() via a smarter wrapper called 'stop'. +# +# In the context of a test, calling a function X that calls 'stop' (directly +# or through further intervening calls) will unwind the stack until X returns, +# so that we can say check any further assertions after the execution of X. To +# achieve this end, we'll pass the return address of X as a 'target' argument +# into X, plumbing it through to 'stop'. When 'stop' gets a non-null target it +# unwinds the stack until the target. If it gets a null target it calls +# exit(). +# +# We'd also like to get the exit status out of 'stop', so we'll combine the +# input target with an output status parameter into a type called 'exit-descriptor'. +# +# So the exit-descriptor looks like this: +# target : address # return address for 'stop' to unwind to +# value : int # exit status stop was called with +# +# 'stop' thus takes two parameters: an exit-descriptor and the exit status. +# +# 'stop' won't bother cleaning up any other processor state besides the stack, +# such as registers. Only ESP will have a well-defined value after 'stop' +# returns. (This is a poor man's setjmp/longjmp, if you know what that is.) +# +# Before you can call any function that may call 'stop', you need to pass in an +# exit-descriptor to it. To create an exit-descriptor use 'tailor-exit-descriptor' +# below. It's not the most pleasant abstraction in the world. +# +# An exit-descriptor's target is its input, computed during 'tailor-exit-descriptor'. +# Its value is its output, computed during stop and available to the test. + +== code +# instruction effective address register 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: + 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-stop-skips-returns-on-exit/disp32 + # syscall(exit, Num-test-failures) + 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +# Configure an exit-descriptor for a call pushing 'nbytes' bytes of args to +# the stack. +# Ugly that we need to know the size of args, but so it goes. +tailor-exit-descriptor: # ed : (address exit-descriptor), nbytes : int -> + # . 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 = nbytes + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 0xc/disp8 . # copy *(EBP+12) to EAX + # Let X be the value of ESP in the caller, before the call to tailor-exit-descriptor. + # The return address for a call in the caller's body will be at: + # X-8 if the caller takes 4 bytes of args for the exit-descriptor (add 4 bytes for the return address) + # X-12 if the caller takes 8 bytes of args + # ..and so on + # That's the value we need to return: X-nbytes-4 + # + # However, we also need to account for the perturbance to ESP caused by the + # call to tailor-exit-descriptor. It pushes 8 bytes of args followed by 4 + # bytes for the return address and 4 bytes to push EBP above. + # So EBP at this point is X-16. + # + # So the return address for the next call in the caller is: + # EBP+8 if the caller takes 4 bytes of args + # EBP+4 if the caller takes 8 bytes of args + # EBP if the caller takes 12 bytes of args + # EBP-4 if the caller takes 16 bytes of args + # ..and so on + # That's EBP+12-nbytes. + # option 1: 6 + 3 bytes +#? 2d/subtract 3/mod/direct 0/rm32/EAX . . . . . 8/imm32 # subtract from EAX +#? 8d/copy-address 0/mod/indirect 4/rm32/sib 5/base/EBP 0/index/EAX . 0/r32/EAX . . # copy EBP+EAX to EAX + # option 2: 2 + 4 bytes + f7 3/subop/negate 3/mod/direct 0/rm32/EAX . . . . . . # negate EAX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 5/base/EBP 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy EBP+EAX+12 to EAX + # copy EAX to ed->target + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 8/disp8 . # copy *(EBP+8) to ECX + 89/copy 0/mod/indirect 1/rm32/ECX . . . 0/r32/EAX . . # copy EAX to *ECX + # initialize ed->value + c7 0/subop/copy 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 0/imm32 # copy to *(ECX+4) +$tailor-exit-descriptor: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 + +stop: # ed : (address exit-descriptor), value : int + # no prolog; one way or another, we're going to clobber registers + # EAX = ed + 8b/copy 1/mod/*+disp8 4/rm32/sib 4/base/ESP 4/index/none . 0/r32/EAX 4/disp8 . # copy *(ESP+4) to EAX + # exit(value) if ed->target == 0 + 81 7/subop/compare 0/mod/indirect 0/rm32/EAX . . . . . 0/imm32 # compare *EAX + 75/jump-if-not-equal $stop:fake/disp8 + # syscall(exit, value) + 8b/copy 1/mod/*+disp8 4/rm32/sib 4/base/ESP 4/index/none . 3/r32/EBX 8/disp8 . # copy *(ESP+8) to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 +$stop:fake: + # ed->value = value+1 + 8b/copy 1/mod/*+disp8 4/rm32/sib 4/base/ESP 4/index/none . 1/r32/ECX 8/disp8 . # copy *(ESP+8) to ECX + 41/inc-ECX + 89/copy 1/mod/*+disp8 0/rm32/EAX . . . 1/r32/ECX 4/disp8 . # copy ECX to *(EAX+4) + # non-local jump to ed->target + 8b/copy 0/mod/indirect 0/rm32/EAX . . . 4/r32/ESP . . # copy *EAX to ESP +$stop:end: + c3/return # doesn't return to caller + +test-stop-skips-returns-on-exit: + # This looks like the standard prolog, but is here for different reasons. + # A function calling 'stop' can't rely on EBP persisting past the call. + # + # Use EBP here as a stable base to refer to locals and arguments from in the + # presence of push/pop/call instructions. + # *Don't* use EBP as a way to restore ESP. + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # Make room for an exit descriptor on the stack. That's almost always the + # right place for it, available only as long as it's legal to use. Once this + # containing function returns we'll need a new exit descriptor. + # var ed/EAX : (address exit-descriptor) + 81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP + 89/copy 3/mod/direct 0/rm32/EAX . . . 4/r32/ESP . . # copy ESP to EAX + # Size the exit-descriptor precisely for the next call below, to _test-stop-1. + # tailor-exit-descriptor(ed, 4) + # . . push args + 68/push 4/imm32/nbytes-of-args-for-_test-stop-1 + 50/push-EAX + # . . call + e8/call tailor-exit-descriptor/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # . _test-stop-1(ed) + # . . push args + 50/push-EAX + # . . call + e8/call _test-stop-1/disp32 + # registers except ESP may be clobbered at this point + # restore args + 58/pop-to-EAX + # check that _test-stop-1 tried to call exit(1) + # check-ints-equal(ed->value, 2, msg) # i.e. stop was called with value 1 + # . . push args + 68/push "F - test-stop-skips-returns-on-exit"/imm32 + 68/push 2/imm32 + # . . push ed->value + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + # don't restore ESP from EBP; manually reclaim locals + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + 5d/pop-to-EBP + c3/return + +_test-stop-1: # ed : (address exit-descriptor) + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # _test-stop-2(ed) + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call _test-stop-2/disp32 + # should never get past this point +$_test-stop-1:dead-end: + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # signal test failed: check-ints-equal(1, 0, msg) + # . . push args + 68/push "F - test-stop-skips-returns-on-exit"/imm32 + 68/push 0/imm32 + 68/push 1/imm32 + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +_test-stop-2: # ed : (address exit-descriptor) + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # . stop(ed, 1) + # . . push args + 68/push 1/imm32 + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call stop/disp32 + # should never get past this point +$_test-stop-2:dead-end: + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +# . . vim:nowrap:textwidth=0 diff --git a/subx/060read-byte.subx b/subx/060read-byte.subx deleted file mode 100644 index c62b035e..00000000 --- a/subx/060read-byte.subx +++ /dev/null @@ -1,302 +0,0 @@ -# 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. But -# I don't want to type in 1024 bytes here. - -== code -# instruction effective address register 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: - 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 -#? e8/call test-read-byte-refills-buffer/disp32 - # syscall(exit, Num-test-failures) - 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - 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-or-eof/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 5/rm32/EBP . . . 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) populate stream from file - 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 - # . f->read must now be 0; update its cache at ECX - 31/xor 3/mod/direct 1/rm32/ECX . . . 1/r32/ECX . . # clear ECX - # . 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: - # 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 byte at *(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' - # setup - # . 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', msg) - # . . 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 - # setup - # . 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', msg) - # . . 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 0xffffffff - # setup - # . 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, 0xffffffff, msg) - # . . push args - 68/push "F - test-read-byte-end-of-file"/imm32 - 68/push 0xffffffff/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-refills-buffer: - # - consume buffered-file's buffer, check that next read-byte still works - # setup - # . 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, "Abcdefgh") - # . . push args - 68/push "Abcdefgh"/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 - # pretend buffer is full - # . _test-buffered-file->read = 6 # >= _test-buffered-file->length - b8/copy-to-EAX _test-buffered-file/imm32 - c7 0/subop/copy 1/mod/*+disp8 0/rm32/EAX . . . . 8/disp8 6/imm32 # copy to *(EAX+8) - # 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', msg) - # . . push args - 68/push "F - test-read-byte-refills-buffer"/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 - -== data - -# a test buffered file for _test-stream -_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 (6) - 06 00 00 00 - # data - 00 00 00 00 00 00 # 6 bytes - -# . . vim:nowrap:textwidth=0 diff --git a/subx/060read.subx b/subx/060read.subx new file mode 100644 index 00000000..42056a13 --- /dev/null +++ b/subx/060read.subx @@ -0,0 +1,366 @@ +# read: analogously to write, support reading from in-memory streams in +# addition to file descriptors. +# +# We can pass it either a file descriptor or an address to a stream. If a +# file descriptor is passed in, we _read from it using the right syscall. If a +# stream is passed in (a fake file descriptor), we read from it instead. This +# lets us initialize input for tests. +# +# A little counter-intuitively, the output of 'read' ends up in.. a stream. So +# tests end up doing a redundant copy. Why? Well, consider the alternatives: +# +# a) Reading into a string, and returning a pointer to the end of the read +# region, or a count of bytes written. Now this count or end pointer must be +# managed separately by the caller, which can be error-prone. +# +# b) Having 'read' return a buffer that it allocates. But there's no way to +# know in advance how large to make the buffer. If you read less than the +# size of the buffer you again end up needing to manage initialized vs +# uninitialized memory. +# +# c) Creating more helpful variants like 'read-byte' or 'read-until' which +# also can take a file descriptor or stream, just like 'write'. But such +# primitives don't exist in the Linux kernel, so we'd be implementing them +# somehow, either with more internal buffering or by making multiple +# syscalls. +# +# Reading into a stream avoids these problems. The buffer is externally +# provided and the caller has control over where it's allocated, its lifetime, +# and so on. The buffer's read and write pointers are internal to it so it's +# easier to keep in a consistent state. And it can now be passed directly to +# helpers like 'read-byte' or 'read-until' that only need to support streams, +# never file descriptors. +# +# Like with 'write', 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. +# +# As a reminder, a stream looks like this: +# write: int # index at which to write to next +# read: int # index at which to read next +# data: (array byte) # prefixed by length as usual + +== code +# instruction effective address register 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: + 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +read: # f : fd or (address stream), s : (address stream) -> num-bytes-read/EAX + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # if (f < 0x08000000) return _read(f, s) # f can't be a user-mode address, so treat it as a kernel file descriptor + 81 7/subop/compare 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 0x08000000/imm32 # compare *(EBP+8) + 7d/jump-if-greater-or-equal $read:fake/disp8 + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call _read/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # return + eb/jump $read:end/disp8 +$read:fake: + # otherwise, treat 'f' as a stream to scan from + # . save registers + 56/push-ESI + 57/push-EDI + # ESI = f + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI + # EDI = s + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 7/r32/EDI 0xc/disp8 . # copy *(EBP+12) to ESI + # EAX = _append-4(out = &s->data[s->write], outend = &s->data[s->length], + # in = &f->data[f->read], inend = &f->data[f->write]) + # . . push &f->data[f->write] + 8b/copy 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # copy *ESI to EAX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy ESI+EAX+12 to EAX + 50/push-EAX + # . . push &f->data[f->read] + 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 0/r32/EAX 4/disp8 . # copy *(ESI+4) to EAX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy ESI+EAX+12 to EAX + 50/push-EAX + # . . push &s->data[s->length] + 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 8/disp8 . # copy *(EDI+8) to EAX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 7/base/EDI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy EDI+EAX+12 to EAX + 50/push-EAX + # . . push &s->data[s->write] + 8b/copy 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # copy *EDI to EAX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 7/base/EDI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy EDI+EAX+12 to EAX + 50/push-EAX + # . . call + e8/call _append-4/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP + # s->write += EAX + 01/add 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # add EAX to *EDI + # f->read += EAX + 01/add 1/mod/*+disp8 6/rm32/ESI . . . 0/r32/EAX 4/disp8 . # add EAX to *(ESI+4) + # . restore registers + 5f/pop-to-EDI + 5e/pop-to-ESI +$read:end: + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +# - helpers + +# idea: a clear-if-empty method on streams that clears only if f->read == f->write +# Unclear how I'd use it, though. Callers seem to need the check anyway. +# Maybe a better helper would be 'empty-stream?' + +_read: # fd : int, s : (address stream) -> num-bytes-read/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 + 52/push-EDX + 53/push-EBX + 56/push-ESI + # ESI = s + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 0xc/disp8 . # copy *(EBP+12) to ESI + # EAX = s->write + 8b/copy 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # copy *ESI to EAX + # EDX = s->length + 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 2/r32/EDX 8/disp8 . # copy *(ESI+8) to EDX + # syscall(read, fd, &s->data[s->write], s->length - s->write) + # . . fd : EBX + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 3/r32/EBX 8/disp8 . # copy *(EBP+8) to EBX + # . . data : ECX = &s->data[s->write] + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 1/r32/ECX 0xc/disp8 . # copy ESI+EAX+12 to ECX + # . . size : EDX = s->length - s->write + 29/subtract 3/mod/direct 2/rm32/EDX . . . 0/r32/EAX . . # subtract EAX from EDX + # . . syscall + b8/copy-to-EAX 3/imm32/read + cd/syscall 0x80/imm8 + # add the result EAX to s->write + 01/add 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # add EAX to *ESI +$_read:end: + # . restore registers + 5e/pop-to-ESI + 5b/pop-to-EBX + 5a/pop-to-EDX + 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 + + # Two options: + # 1 (what we have above): + # ECX = s + # EAX = s->write + # EDX = s->length + # # syscall + # ECX = lea ECX+EAX+12 + # EDX = sub EDX EAX + # + # 2: + # ECX = s + # EDX = s->length + # ECX = &s->data + # # syscall + # ECX = add ECX, s->write + # EDX = sub EDX, s->write + # + # Not much to choose between the two? Option 2 performs a duplicate load to + # use one less register, but doesn't increase the amount of spilling (ECX + # and EDX must be used, and EAX must be clobbered anyway). + +# - tests + +test-read-single: + # - write a single character into _test-stream, then read from its buffered-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-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + # . . 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(_test-stream, _test-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call read/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, 2, msg) + # . . push args + 68/push "F - test-read-single: return EAX"/imm32 + 68/push 2/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 + # check-ints-equal(*_test-stream-buffer->data, 41/A 62/b 00 00, msg) + # . . push args + 68/push "F - test-read-single"/imm32 + 68/push 0x006241/imm32/Ab + # . . push *_test-stream-buffer->data + b8/copy-to-EAX _test-stream-buffer/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . 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-is-stateful: + # - make two consecutive reads, check that their results are appended + # 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-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + # . . 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, "C") + # . . push args + 68/push "C"/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(_test-stream, _test-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call read/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # write(_test-stream, "D") + # . . push args + 68/push "D"/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(_test-stream, _test-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call read/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(*_test-stream-buffer->data, 43/C 44/D 00 00, msg) + # . . push args + 68/push "F - test-read-is-stateful"/imm32 + 68/push 0x00004443/imm32/C-D + # . . push *_test-stream-buffer->data + b8/copy-to-EAX _test-stream-buffer/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . 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-returns-0-on-end-of-file: + # - read after hitting end-of-file, check that result is 0 + # setup + # . 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-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + # . . 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 + # first read gets to end-of-file + # . read(_test-stream, _test-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call read/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # second read + # . read(_test-stream, _test-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call read/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-read-returns-0-on-end-of-file"/imm32 + 68/push 0/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-stream-buffer: + # 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 + +# . . vim:nowrap:textwidth=0 diff --git a/subx/061read-byte.subx b/subx/061read-byte.subx new file mode 100644 index 00000000..c62b035e --- /dev/null +++ b/subx/061read-byte.subx @@ -0,0 +1,302 @@ +# 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. But +# I don't want to type in 1024 bytes here. + +== code +# instruction effective address register 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: + 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 +#? e8/call test-read-byte-refills-buffer/disp32 + # syscall(exit, Num-test-failures) + 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + 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-or-eof/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 5/rm32/EBP . . . 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) populate stream from file + 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 + # . f->read must now be 0; update its cache at ECX + 31/xor 3/mod/direct 1/rm32/ECX . . . 1/r32/ECX . . # clear ECX + # . 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: + # 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 byte at *(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' + # setup + # . 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', msg) + # . . 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 + # setup + # . 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', msg) + # . . 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 0xffffffff + # setup + # . 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, 0xffffffff, msg) + # . . push args + 68/push "F - test-read-byte-end-of-file"/imm32 + 68/push 0xffffffff/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-refills-buffer: + # - consume buffered-file's buffer, check that next read-byte still works + # setup + # . 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, "Abcdefgh") + # . . push args + 68/push "Abcdefgh"/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 + # pretend buffer is full + # . _test-buffered-file->read = 6 # >= _test-buffered-file->length + b8/copy-to-EAX _test-buffered-file/imm32 + c7 0/subop/copy 1/mod/*+disp8 0/rm32/EAX . . . . 8/disp8 6/imm32 # copy to *(EAX+8) + # 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', msg) + # . . push args + 68/push "F - test-read-byte-refills-buffer"/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 + +== data + +# a test buffered file for _test-stream +_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 (6) + 06 00 00 00 + # data + 00 00 00 00 00 00 # 6 bytes + +# . . vim:nowrap:textwidth=0 diff --git a/subx/061write-stream.subx b/subx/061write-stream.subx deleted file mode 100644 index f112dee9..00000000 --- a/subx/061write-stream.subx +++ /dev/null @@ -1,243 +0,0 @@ -# write-stream: like write, but write streams rather than strings - -== code -# instruction effective address register 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 -#? # write-stream(stdout, _test-stream2) -#? 68/push _test-stream2/imm32 -#? 68/push 1/imm32/stdout -#? e8/call write-stream/disp32 - # automatic test -#? e8/call test-write-stream-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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -write-stream: # f : fd or (address stream), s : (address stream) -> - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # if (f < 0x08000000) _write-stream(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 5/rm32/EBP . . . . 8/disp8 0x08000000/imm32 # compare *(EBP+8) - 7d/jump-if-greater-or-equal $write-stream:fake/disp8 - # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) - # . . call - e8/call _write-stream/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - eb/jump $write-stream:end/disp8 -$write-stream:fake: - # otherwise, treat 'f' as a stream to append to - # . save registers - 50/push-EAX - 56/push-ESI - 57/push-EDI - # EDI = f - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . 7/r32/EDI 8/disp8 . # copy *(EBP+8) to EDI - # ESI = s - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . 6/r32/ESI 0xc/disp8 . # copy *(EBP+12) to ESI - # EAX = _append-4(&f->data[f->write], &f->data[f->length], &s->data[s->read], &s->data[s->write]) - # . . push &s->data[s->write] - 8b/copy 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # copy *ESI to EAX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy ESI+EAX+12 to EAX - 50/push-EAX - # . . push &s->data[s->read] - 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 0/r32/EAX 4/disp8 . # copy *(ESI+4) to EAX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy ESI+EAX+12 to EAX - 50/push-EAX - # . . push &f->data[f->length] - 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 8/disp8 . # copy *(EDI+8) to EAX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 7/base/EDI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy EDI+EAX+12 to EAX - 50/push-EAX - # . . push &f->data[f->write] - 8b/copy 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # copy *EDI to EAX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 7/base/EDI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy EDI+EAX+12 to EAX - 50/push-EAX - # . . call - e8/call _append-4/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP - # f->write += EAX - 01/add 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # add EAX to *EDI - # s->read += EAX - 01/add 1/mod/*+disp8 6/rm32/ESI . . . 0/r32/EAX 4/disp8 . # add EAX to *(ESI+4) - # . restore registers - 5f/pop-to-EDI - 5e/pop-to-ESI - 58/pop-to-EAX -$write-stream:end: - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -_write-stream: # fd : int, s : (address stream) -> - # . 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 - 57/push-EDI - # ESI = s - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 0xc/disp8 . # copy *(EBP+12) to ESI - # EDI = s->read - 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 7/r32/EDI 4/disp8 . # copy *(ESI+4) to EDI - # EDX = s->write - 8b/copy 0/mod/indirect 6/rm32/ESI . . . 2/r32/EDX . . # copy *ESI to EDX - # syscall(write, fd, &s->data[s->read], s->write - s->read) - # . . fd : EBX - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 3/r32/EBX 8/disp8 . # copy *(EBP+8) to EBX - # . . data : ECX = &s->data[s->read] - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 7/index/EDI . 1/r32/ECX 0xc/disp8 . # copy ESI+EDI+12 to ECX - # . . size : EDX = s->write - s->read - 29/subtract 3/mod/direct 2/rm32/EDX . . . 7/r32/EDI . . # subtract EDI from EDX - # . . syscall - b8/copy-to-EAX 4/imm32/write - cd/syscall 0x80/imm8 - # . restore registers - 5f/pop-to-EDI - 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 - -test-write-stream-single: - # setup - # . 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-stream2) - # . . push args - 68/push _test-stream2/imm32 - # . . 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-stream2, "Ab") - # . . push args - 68/push "Ab"/imm32 - 68/push _test-stream2/imm32 - # . . call - e8/call write/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # write-stream(_test-stream, _test-stream2) - # . . push args - 68/push _test-stream2/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call write-stream/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(*_test-stream->data, 41/A 62/b 00 00, msg) - # . . push args - 68/push "F - test-write-stream-single"/imm32 - 68/push 0x006241/imm32/Ab - # . . push *_test-stream->data - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) - # . . 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-stream-appends: - # setup - # . 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-stream2) - # . . push args - 68/push _test-stream2/imm32 - # . . 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-stream2, "C") - # . . push args - 68/push "C"/imm32 - 68/push _test-stream2/imm32 - # . . call - e8/call write/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # first write - # . write-stream(_test-stream, _test-stream2) - # . . push args - 68/push _test-stream2/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call write-stream/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # second write - # . write(_test-stream2, "D") - # . . push args - 68/push "D"/imm32 - 68/push _test-stream2/imm32 - # . . call - e8/call write/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # . write-stream(_test-stream, _test-stream2) - # . . push args - 68/push _test-stream2/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call write-stream/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(*_test-stream->data, 43/C 44/D 00 00, msg) - # . . push args - 68/push "F - test-write-stream-appends"/imm32 - 68/push 0x00004443/imm32/C-D - # . . push *_test-stream->data - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) - # . . 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-stream2: - # current write index - 04 00 00 00 - # current read index - 01 00 00 00 - # length (= 8) - 08 00 00 00 - # data - 41 42 43 44 00 00 00 00 # 8 bytes - -# . . vim:nowrap:textwidth=0 diff --git a/subx/062error.subx b/subx/062error.subx deleted file mode 100644 index efda8ea3..00000000 --- a/subx/062error.subx +++ /dev/null @@ -1,57 +0,0 @@ -# Print an error message and exit. - -== code -# instruction effective address register 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: - 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -# write(out, "Error: "+msg+"\n") then stop(ed, 1) -error: # ed : (address exit-descriptor), out : fd or (address stream), msg : (address array byte) -> - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # write(out, "Error: ") - # . . push args - 68/push "Error: "/imm32 - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 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 - # write(out, msg) - # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x10/disp8 . # push *(EBP+16) - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 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 - # write(out, Newline) - # . . push args - 68/push Newline/imm32 - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 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 - # stop(ed, 1) - # . . push args - 68/push 1/imm32 - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) - # . . call - e8/call stop/disp32 - # should never get past this point -$error:dead-end: - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -# . . vim:nowrap:textwidth=0 diff --git a/subx/062write-stream.subx b/subx/062write-stream.subx new file mode 100644 index 00000000..f112dee9 --- /dev/null +++ b/subx/062write-stream.subx @@ -0,0 +1,243 @@ +# write-stream: like write, but write streams rather than strings + +== code +# instruction effective address register 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 +#? # write-stream(stdout, _test-stream2) +#? 68/push _test-stream2/imm32 +#? 68/push 1/imm32/stdout +#? e8/call write-stream/disp32 + # automatic test +#? e8/call test-write-stream-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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +write-stream: # f : fd or (address stream), s : (address stream) -> + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # if (f < 0x08000000) _write-stream(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 5/rm32/EBP . . . . 8/disp8 0x08000000/imm32 # compare *(EBP+8) + 7d/jump-if-greater-or-equal $write-stream:fake/disp8 + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call _write-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + eb/jump $write-stream:end/disp8 +$write-stream:fake: + # otherwise, treat 'f' as a stream to append to + # . save registers + 50/push-EAX + 56/push-ESI + 57/push-EDI + # EDI = f + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . 7/r32/EDI 8/disp8 . # copy *(EBP+8) to EDI + # ESI = s + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . 6/r32/ESI 0xc/disp8 . # copy *(EBP+12) to ESI + # EAX = _append-4(&f->data[f->write], &f->data[f->length], &s->data[s->read], &s->data[s->write]) + # . . push &s->data[s->write] + 8b/copy 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # copy *ESI to EAX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy ESI+EAX+12 to EAX + 50/push-EAX + # . . push &s->data[s->read] + 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 0/r32/EAX 4/disp8 . # copy *(ESI+4) to EAX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy ESI+EAX+12 to EAX + 50/push-EAX + # . . push &f->data[f->length] + 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 8/disp8 . # copy *(EDI+8) to EAX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 7/base/EDI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy EDI+EAX+12 to EAX + 50/push-EAX + # . . push &f->data[f->write] + 8b/copy 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # copy *EDI to EAX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 7/base/EDI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy EDI+EAX+12 to EAX + 50/push-EAX + # . . call + e8/call _append-4/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP + # f->write += EAX + 01/add 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # add EAX to *EDI + # s->read += EAX + 01/add 1/mod/*+disp8 6/rm32/ESI . . . 0/r32/EAX 4/disp8 . # add EAX to *(ESI+4) + # . restore registers + 5f/pop-to-EDI + 5e/pop-to-ESI + 58/pop-to-EAX +$write-stream:end: + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +_write-stream: # fd : int, s : (address stream) -> + # . 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 + 57/push-EDI + # ESI = s + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 0xc/disp8 . # copy *(EBP+12) to ESI + # EDI = s->read + 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 7/r32/EDI 4/disp8 . # copy *(ESI+4) to EDI + # EDX = s->write + 8b/copy 0/mod/indirect 6/rm32/ESI . . . 2/r32/EDX . . # copy *ESI to EDX + # syscall(write, fd, &s->data[s->read], s->write - s->read) + # . . fd : EBX + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 3/r32/EBX 8/disp8 . # copy *(EBP+8) to EBX + # . . data : ECX = &s->data[s->read] + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 7/index/EDI . 1/r32/ECX 0xc/disp8 . # copy ESI+EDI+12 to ECX + # . . size : EDX = s->write - s->read + 29/subtract 3/mod/direct 2/rm32/EDX . . . 7/r32/EDI . . # subtract EDI from EDX + # . . syscall + b8/copy-to-EAX 4/imm32/write + cd/syscall 0x80/imm8 + # . restore registers + 5f/pop-to-EDI + 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 + +test-write-stream-single: + # setup + # . 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-stream2) + # . . push args + 68/push _test-stream2/imm32 + # . . 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-stream2, "Ab") + # . . push args + 68/push "Ab"/imm32 + 68/push _test-stream2/imm32 + # . . call + e8/call write/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # write-stream(_test-stream, _test-stream2) + # . . push args + 68/push _test-stream2/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call write-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(*_test-stream->data, 41/A 62/b 00 00, msg) + # . . push args + 68/push "F - test-write-stream-single"/imm32 + 68/push 0x006241/imm32/Ab + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . 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-stream-appends: + # setup + # . 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-stream2) + # . . push args + 68/push _test-stream2/imm32 + # . . 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-stream2, "C") + # . . push args + 68/push "C"/imm32 + 68/push _test-stream2/imm32 + # . . call + e8/call write/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # first write + # . write-stream(_test-stream, _test-stream2) + # . . push args + 68/push _test-stream2/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call write-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # second write + # . write(_test-stream2, "D") + # . . push args + 68/push "D"/imm32 + 68/push _test-stream2/imm32 + # . . call + e8/call write/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # . write-stream(_test-stream, _test-stream2) + # . . push args + 68/push _test-stream2/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call write-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(*_test-stream->data, 43/C 44/D 00 00, msg) + # . . push args + 68/push "F - test-write-stream-appends"/imm32 + 68/push 0x00004443/imm32/C-D + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . 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-stream2: + # current write index + 04 00 00 00 + # current read index + 01 00 00 00 + # length (= 8) + 08 00 00 00 + # data + 41 42 43 44 00 00 00 00 # 8 bytes + +# . . vim:nowrap:textwidth=0 diff --git a/subx/063error.subx b/subx/063error.subx new file mode 100644 index 00000000..efda8ea3 --- /dev/null +++ b/subx/063error.subx @@ -0,0 +1,57 @@ +# Print an error message and exit. + +== code +# instruction effective address register 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: + 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +# write(out, "Error: "+msg+"\n") then stop(ed, 1) +error: # ed : (address exit-descriptor), out : fd or (address stream), msg : (address array byte) -> + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # write(out, "Error: ") + # . . push args + 68/push "Error: "/imm32 + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 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 + # write(out, msg) + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x10/disp8 . # push *(EBP+16) + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 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 + # write(out, Newline) + # . . push args + 68/push Newline/imm32 + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 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 + # stop(ed, 1) + # . . push args + 68/push 1/imm32 + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call stop/disp32 + # should never get past this point +$error:dead-end: + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +# . . vim:nowrap:textwidth=0 diff --git a/subx/063write-byte.subx b/subx/063write-byte.subx deleted file mode 100644 index 9c9b50a6..00000000 --- a/subx/063write-byte.subx +++ /dev/null @@ -1,233 +0,0 @@ -# write-byte: write a single byte to a buffered-file. The write may be buffered. -# flush: write out any buffered writes to disk. -# -# TODO: Come up with a way to signal failure to write to disk. This is hard -# since the failure may impact previous calls that were buffered. - -== data - -# The buffered file for standard output. -Stdout: - # file descriptor or (address stream) - 01 00 00 00 # 1 = standard output - # 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. But -# I don't want to type in 1024 bytes here. - -== code -# instruction effective address register 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: - 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -# Write lower byte of 'n' to 'f'. -write-byte: # f : (address buffered-file), n : int -> - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # . save registers - 51/push-ECX - 57/push-EDI - # EDI = f - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 7/r32/EDI 8/disp8 . # copy *(EBP+8) to EDI - # ECX = f->write - 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 1/r32/ECX 4/disp8 . # copy *(EDI+4) to ECX - # if (f->write >= f->length) flush and clear f's stream - 3b/compare 1/mod/*+disp8 7/rm32/EDI . . . 1/r32/ECX 0xc/disp8 . # compare ECX with *(EDI+12) - 7c/jump-if-lesser $write-byte:to-stream/disp8 - # . flush(f) - # . . push args - 57/push-EDI - # . . call - e8/call flush/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(stream = f+4) - # . . push args - 8d/copy-address 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 4/disp8 . # copy EDI+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 - # . f->write must now be 0; update its cache at ECX - 31/xor 3/mod/direct 1/rm32/ECX . . . 1/r32/ECX . . # clear ECX -$write-byte:to-stream: - # write to stream - # f->data[f->write] = LSB(n) - 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX - 8a/copy-byte 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/AL 0xc/disp8 . # copy byte at *(EBP+12) to AL - 88/copy-byte 1/mod/*+disp8 4/rm32/sib 7/base/EDI 1/index/ECX . 0/r32/AL 0x10/disp8 . # copy AL to *(EDI+ECX+16) - # ++f->write - ff 0/subop/increment 1/mod/*+disp8 7/rm32/EDI . . . . 4/disp8 . # increment *(EDI+4) -$write-byte:end: - # . restore registers - 5f/pop-to-EDI - 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 - -flush: # f : (address buffered-file) -> - # . 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 5/rm32/EBP . . . 0/r32/EAX 8/disp8 . # copy *(EBP+8) to EAX - # write-stream(f->fd, data = f+4) - # . . push args - 8d/copy-address 1/mod/*+disp8 0/rm32/EAX . . . 1/r32/ECX 4/disp8 . # copy EAX+4 to ECX - 51/push-ECX - ff 6/subop/push 0/mod/indirect 0/rm32/EAX . . . . . . # push *EAX - # . . call - e8/call write-stream/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP -$flush: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 - -# - tests - -test-write-byte-single: - # - check that write-byte writes to first byte of 'file' - # setup - # . 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-byte(_test-buffered-file, 'A') - # . . push args - 68/push 0x41/imm32 - 68/push _test-buffered-file/imm32 - # . . call - e8/call write-byte/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # flush(_test-buffered-file) - # . . push args - 68/push _test-buffered-file/imm32 - # . . call - e8/call flush/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(*_test-stream->data, 'A', msg) - # . . push args - 68/push "F - test-write-byte-single"/imm32 - 68/push 0x41/imm32 - # . . push *_test-stream->data - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) - # . . 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-byte-multiple-flushes: - # - check that write-byte correctly flushes buffered data - # setup - # . 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 - # fill up the buffer for _test-buffered-file - # . write(_test-buffered-file+4, 'abcdef') - # . . push args - 68/push "abcdef"/imm32 - b8/copy-to-EAX _test-buffered-file/imm32 - 05/add-to-EAX 4/imm32 - 50/push-EAX - # . . call - e8/call write/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # write-byte(_test-buffered-file, 'g') - # . . push args - 68/push 0x67/imm32 - 68/push _test-buffered-file/imm32 - # . . call - e8/call write-byte/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # flush(_test-buffered-file) - # . . push args - 68/push _test-buffered-file/imm32 - # . . call - e8/call flush/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(_test-stream->data[0..3], 'abcd', msg) - # . . push args - 68/push "F - test-write-byte-multiple-flushes: 1"/imm32 - 68/push 0x64636261/imm32 - # . . push *_test-stream->data - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # check-ints-equal(_test-stream->data[4..8], 'efg', msg) - # . . push args - 68/push "F - test-write-byte-multiple-flushes"/imm32 - 68/push 0x00676665/imm32 - # . . push *_test-stream->data - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0x10/disp8 . # push *(EAX+16) - # . . 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 - -# . . vim:nowrap:textwidth=0 diff --git a/subx/064hex.subx b/subx/064hex.subx deleted file mode 100644 index 6ba2ef7e..00000000 --- a/subx/064hex.subx +++ /dev/null @@ -1,809 +0,0 @@ -# some utilities for converting numbers to/from hex -# lowercase letters only for now - -== code -# instruction effective address register 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: - 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -is-hex-int?: # in : (address slice) -> EAX : boolean - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # . save registers - 51/push-ECX - 52/push-EDX - 53/push-EBX - # ECX = s - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 8/disp8 . # copy *(EBP+8) to ECX - # EDX = s->end - 8b/copy 1/mod/*+disp8 1/rm32/ECX . . . 2/r32/EDX 4/disp8 . # copy *(ECX+4) to EDX - # curr/ECX = s->start - 8b/copy 0/mod/indirect 1/rm32/ECX . . . 1/r32/ECX . . # copy *ECX to ECX - # if s is empty return false - b8/copy-to-EAX 0/imm32/false - 39/compare 3/mod/direct 1/rm32/ECX . . . 2/r32/EDX . . # compare ECX with EDX - 7d/jump-if-greater-or-equal $is-hex-int?:end/disp8 - # skip past leading '-' - # . if (*curr == '-') ++curr - 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX - 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 3/r32/BL . . # copy byte at *ECX to BL - 81 7/subop/compare 3/mod/direct 3/rm32/EBX . . . . . 0x2d/imm32/- # compare EBX - 75/jump-if-not-equal $is-hex-int?:initial-0/disp8 - # . ++curr - 41/increment-ECX - # skip past leading '0x' -$is-hex-int?:initial-0: - # . if (*curr != '0') jump to loop - 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX - 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 3/r32/BL . . # copy byte at *ECX to BL - 81 7/subop/compare 3/mod/direct 3/rm32/EBX . . . . . 0x30/imm32/0 # compare EBX - 75/jump-if-not-equal $is-hex-int?:loop/disp8 - # . ++curr - 41/increment-ECX -$is-hex-int?:initial-0x: - # . if (curr >= in->end) return true - 39/compare 3/mod/direct 1/rm32/ECX . . . 2/r32/EDX . . # compare ECX with EDX - 7d/jump-if-greater-or-equal $is-hex-int?:true/disp8 - # . if (*curr != 'x') jump to loop # the previous '0' is still valid so doesn't need to be checked again - 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX - 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 3/r32/BL . . # copy byte at *ECX to BL - 81 7/subop/compare 3/mod/direct 3/rm32/EBX . . . . . 0x78/imm32/x # compare EBX - 75/jump-if-not-equal $is-hex-int?:loop/disp8 - # . ++curr - 41/increment-ECX -$is-hex-int?:loop: - # if (curr >= in->end) return true - 39/compare 3/mod/direct 1/rm32/ECX . . . 2/r32/EDX . . # compare ECX with EDX - 7d/jump-if-greater-or-equal $is-hex-int?:true/disp8 - # EAX = is-hex-digit?(*curr) - # . . push args - 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 0/r32/AL . . # copy byte at *ECX to AL - 50/push-EAX - # . . call - e8/call is-hex-digit?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # if EAX == false return false - 3d/compare-with-EAX 0/imm32 - 74/jump-if-equal $is-hex-int?:end/disp8 - # ++curr - 41/increment-ECX - # loop - eb/jump $is-hex-int?:loop/disp8 -$is-hex-int?:true: - # return true - b8/copy-to-EAX 1/imm32/true -$is-hex-int?:end: - # . restore registers - 5b/pop-to-EBX - 5a/pop-to-EDX - 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 - -test-is-hex-int: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "34" - 68/push _test-slice-hex-int-end/imm32 - 68/push _test-slice-hex-int/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = is-hex-int?(slice) - # . . push args - 51/push-ECX - # . . call - e8/call is-hex-int?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 1, msg) - # . . push args - 68/push "F - test-is-hex-int"/imm32 - 68/push 1/imm32/true - 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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-is-hex-int-handles-letters: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "34a" - 68/push _test-slice-hex-int-letters-end/imm32 - 68/push _test-slice-hex-int-letters/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = is-hex-int?(slice) - # . . push args - 51/push-ECX - # . . call - e8/call is-hex-int?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 1, msg) - # . . push args - 68/push "F - test-is-hex-int-handles-letters"/imm32 - 68/push 1/imm32/true - 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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-is-hex-int-with-trailing-char: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "34q" - 68/push _test-slice-digits-and-char-end/imm32 - 68/push _test-slice-digits-and-char/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = is-hex-int?(slice) - # . . push args - 51/push-ECX - # . . call - e8/call is-hex-int?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-is-hex-int-with-trailing-char"/imm32 - 68/push 0/imm32/false - 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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-is-hex-int-with-leading-char: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "q34" - 68/push _test-slice-char-and-digits-end/imm32 - 68/push _test-slice-char-and-digits/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = is-hex-int?(slice) - # . . push args - 51/push-ECX - # . . call - e8/call is-hex-int?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-is-hex-int-with-leading-char"/imm32 - 68/push 0/imm32/false - 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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-is-hex-int-empty: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "" - 68/push _test-slice-empty-end/imm32 - 68/push _test-slice-empty/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = is-hex-int?(slice) - # . . push args - 51/push-ECX - # . . call - e8/call is-hex-int?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-is-hex-int-empty"/imm32 - 68/push 0/imm32/false - 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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-is-hex-int-handles-0x-prefix: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "0x3a" - 68/push _test-slice-hex-int-with-0x-prefix-end/imm32 - 68/push _test-slice-hex-int-with-0x-prefix/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = is-hex-int?(slice) - # . . push args - 51/push-ECX - # . . call - e8/call is-hex-int?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 1, msg) - # . . push args - 68/push "F - test-is-hex-int-handles-0x-prefix"/imm32 - 68/push 1/imm32/true - 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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-is-hex-int-handles-negative: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "-34a" - 68/push _test-slice-hex-int-letters-end/imm32 - 68/push _test-slice-hex-int-letters-negative/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = is-hex-int?(slice) - # . . push args - 51/push-ECX - # . . call - e8/call is-hex-int?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 1, msg) - # . . push args - 68/push "F - test-is-hex-int-handles-negative"/imm32 - 68/push 1/imm32/true - 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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-is-hex-int-handles-negative-0x-prefix: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "-0x3a" - 68/push _test-slice-hex-int-with-0x-prefix-end/imm32 - 68/push _test-slice-hex-int-with-0x-prefix-negative/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = is-hex-int?(slice) - # . . push args - 51/push-ECX - # . . call - e8/call is-hex-int?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 1, msg) - # . . push args - 68/push "F - test-is-hex-int-handles-negative-0x-prefix"/imm32 - 68/push 1/imm32/true - 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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -parse-hex-int: # in : (address slice) -> result/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 - 52/push-EDX - 53/push-EBX - 56/push-ESI - # result/EBX = 0 - 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX - # ECX = s - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 8/disp8 . # copy *(EBP+8) to ECX - # EDX = s->end - 8b/copy 1/mod/*+disp8 1/rm32/ECX . . . 2/r32/EDX 4/disp8 . # copy *(ECX+4) to EDX - # curr/ECX = s->start - 8b/copy 0/mod/indirect 1/rm32/ECX . . . 1/r32/ECX . . # copy *ECX to ECX - # negate?/ESI = false - 31/xor 3/mod/direct 6/rm32/ESI . . . 6/r32/ESI . . # clear ESI -$parse-hex-int:negative: - # . if (*curr == '-') negate = true - 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX - 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 0/r32/AL . . # copy byte at *ECX to AL - 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0x2d/imm32/- # compare EAX - 75/jump-if-not-equal $parse-hex-int:initial-0/disp8 - # . ++curr - 41/increment-ECX - # . negate = true - be/copy-to-ESI 1/imm32/true -$parse-hex-int:initial-0: - # skip past leading '0x' - # . if (*curr != '0') jump to loop - 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 0/r32/AL . . # copy byte at *ECX to AL - 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0x30/imm32/0 # compare EAX - 75/jump-if-not-equal $parse-hex-int:loop/disp8 - # . ++curr - 41/increment-ECX -$parse-hex-int:initial-0x: - # . if (curr >= in->end) return result - 39/compare 3/mod/direct 1/rm32/ECX . . . 2/r32/EDX . . # compare ECX with EDX - 7d/jump-if-greater-or-equal $parse-hex-int:end/disp8 - # . if (*curr != 'x') jump to loop # the previous '0' is still valid so doesn't need to be checked again - 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX - 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 0/r32/AL . . # copy byte at *ECX to AL - 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0x78/imm32/x # compare EAX - 75/jump-if-not-equal $parse-hex-int:loop/disp8 - # . ++curr - 41/increment-ECX -$parse-hex-int:loop: - # if (curr >= in->end) break - 39/compare 3/mod/direct 1/rm32/ECX . . . 2/r32/EDX . . # compare ECX with EDX - 7d/jump-if-greater-or-equal $parse-hex-int:negate/disp8 - # EAX = from-hex-char(*curr) - # . . copy arg to EAX - 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 0/r32/AL . . # copy byte at *ECX to AL - # . . call - e8/call from-hex-char/disp32 - # result = result * 16 + EAX - c1/shift 4/subop/left 3/mod/direct 3/rm32/EBX . . . . . 4/imm8 # shift EBX left by 4 bits - 01/add 3/mod/direct 3/rm32/EBX . . . 0/r32/EAX . . # add EAX to EBX - # ++curr - 41/increment-ECX - # loop - eb/jump $parse-hex-int:loop/disp8 -$parse-hex-int:negate: - 81 7/subop/compare 3/mod/direct 6/rm32/ESI . . . . . 0/imm32 # compare ESI - 74/jump-if-equal $parse-hex-int:end/disp8 - f7 3/subop/negate 3/mod/direct 3/rm32/EBX . . . . . . # negate EBX -$parse-hex-int:end: - 89/copy 3/mod/direct 0/rm32/EAX . . . 3/r32/EBX . . # copy EBX to EAX - # . restore registers - 5e/pop-to-ESI - 5b/pop-to-EBX - 5a/pop-to-EDX - 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 - -test-parse-hex-int-single-digit: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "a" - 68/push _test-slice-hex-int-single-letter-end/imm32 - 68/push _test-slice-hex-int-single-letter/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = parse-hex-int(slice) - # . . push args - 51/push-ECX - # . . call - e8/call parse-hex-int/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0xa, msg) - # . . push args - 68/push "F - test-parse-hex-int-single-digit"/imm32 - 68/push 0xa/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-parse-hex-int-multi-digit: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "34a" - 68/push _test-slice-hex-int-letters-end/imm32 - 68/push _test-slice-hex-int-letters/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = parse-hex-int(slice) - # . . push args - 51/push-ECX - # . . call - e8/call parse-hex-int/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0x34a, msg) - # . . push args - 68/push "F - test-parse-hex-int-multi-digit"/imm32 - 68/push 0x34a/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-parse-hex-int-0x-prefix: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "0x34" - 68/push _test-slice-hex-int-with-0x-prefix-end/imm32 - 68/push _test-slice-hex-int-with-0x-prefix/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = parse-hex-int(slice) - # . . push args - 51/push-ECX - # . . call - e8/call parse-hex-int/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0x34a, msg) - # . . push args - 68/push "F - test-parse-hex-int-0x-prefix"/imm32 - 68/push 0x34/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-parse-hex-int-zero: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "0" - 68/push _test-slice-hex-int-zero-end/imm32 - 68/push _test-slice-hex-int-zero/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = parse-hex-int(slice) - # . . push args - 51/push-ECX - # . . call - e8/call parse-hex-int/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0x34a, msg) - # . . push args - 68/push "F - test-parse-hex-int-zero"/imm32 - 68/push 0/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-parse-hex-int-0-prefix: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "03" - 68/push _test-slice-hex-int-with-0-prefix-end/imm32 - 68/push _test-slice-hex-int-with-0-prefix/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = parse-hex-int(slice) - # . . push args - 51/push-ECX - # . . call - e8/call parse-hex-int/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0x3, msg) - # . . push args - 68/push "F - test-parse-hex-int-0-prefix"/imm32 - 68/push 0x3/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-parse-hex-int-negative: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = "-03" - 68/push _test-slice-hex-int-negative-with-0-prefix-end/imm32 - 68/push _test-slice-hex-int-negative-with-0-prefix/imm32 - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = parse-hex-int(slice) - # . . push args - 51/push-ECX - # . . call - e8/call parse-hex-int/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0xfffffffd, msg) - # . . push args - 68/push "F - test-parse-hex-int-negative"/imm32 - 68/push 0xfffffffd/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -is-hex-digit?: # c : byte -> EAX : boolean - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # . save registers - 51/push-ECX - # ECX = c - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 8/disp8 . # copy *(EBP+8) to ECX - # return false if c < '0' - b8/copy-to-EAX 0/imm32/false - 81 7/subop/compare 3/mod/direct 1/rm32/ECX . . . . . 0x30/imm32 # compare ECX - 7c/jump-if-lesser $is-hex-digit?:end/disp8 - # return false if c > 'f' - 81 7/subop/compare 3/mod/direct 1/rm32/ECX . . . . . 0x66/imm32 # compare ECX - 7f/jump-if-greater $is-hex-digit?:end/disp8 - # return true if c <= '9' - b8/copy-to-EAX 1/imm32/true - 81 7/subop/compare 3/mod/direct 1/rm32/ECX . . . . . 0x39/imm32 # compare ECX - 7e/jump-if-lesser-or-equal $is-hex-digit?:end/disp8 - # return true if c >= 'a' - 81 7/subop/compare 3/mod/direct 1/rm32/ECX . . . . . 0x61/imm32 # compare ECX - 7d/jump-if-greater-or-equal $is-hex-digit?:end/disp8 - # otherwise return false - b8/copy-to-EAX 0/imm32/false -$is-hex-digit?:end: - # . restore registers - 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 - -test-hex-below-0: - # EAX = is-hex-digit?(0x2f) - # . . push args - 68/push 0x2f/imm32 - # . . call - e8/call is-hex-digit?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-hex-below-0"/imm32 - 68/push 0/imm32/false - 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 - c3/return - -test-hex-0-to-9: - # EAX = is-hex-digit?(0x30) - # . . push args - 68/push 0x30/imm32 - # . . call - e8/call is-hex-digit?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 1, msg) - # . . push args - 68/push "F - test-hex-at-0"/imm32 - 68/push 1/imm32/true - 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 - # EAX = is-hex-digit?(0x39) - # . . push args - 68/push 0x39/imm32 - # . . call - e8/call is-hex-digit?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 1, msg) - # . . push args - 68/push "F - test-hex-at-9"/imm32 - 68/push 1/imm32/true - 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 - c3/return - -test-hex-above-9-to-a: - # EAX = is-hex-digit?(0x3a) - # . . push args - 68/push 0x3a/imm32 - # . . call - e8/call is-hex-digit?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-hex-above-9-to-a"/imm32 - 68/push 0/imm32/false - 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 - c3/return - -test-hex-a-to-f: - # EAX = is-hex-digit?(0x61) - # . . push args - 68/push 0x61/imm32 - # . . call - e8/call is-hex-digit?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 1, msg) - # . . push args - 68/push "F - test-hex-at-a"/imm32 - 68/push 1/imm32/true - 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 - # EAX = is-hex-digit?(0x66) - # . . push args - 68/push 0x66/imm32 - # . . call - e8/call is-hex-digit?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 1, msg) - # . . push args - 68/push "F - test-hex-at-f"/imm32 - 68/push 1/imm32/true - 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 - c3/return - -test-hex-above-f: - # EAX = is-hex-digit?(0x67) - # . . push args - 68/push 0x67/imm32 - # . . call - e8/call is-hex-digit?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-hex-above-f"/imm32 - 68/push 0/imm32/false - 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 - c3/return - -from-hex-char: # in/EAX : byte -> out/EAX : num - # no error checking; accepts argument in EAX - # if EAX <= '9' return EAX - '0' - 3d/compare-EAX 0x39/imm32/9 - 7f/jump-if-greater $from-hex-char:else/disp8 - 2d/subtract-from-EAX 0x30/imm32/0 - c3/return -$from-hex-char:else: - # otherwise return EAX - 'a' + 10 - 2d/subtract-from-EAX 0x57/imm32/a-10 - c3/return - -to-hex-char: # in/EAX : nibble -> out/EAX : byte - # no error checking; accepts argument in EAX - # if EAX <= 9 return EAX + '0' - 3d/compare-EAX 0x9/imm32/9 - 7f/jump-if-greater $to-hex-char:else/disp8 - 05/add-to-EAX 0x30/imm32/0 - c3/return -$to-hex-char:else: - # otherwise return EAX + 'a' - 10 - 05/add-to-EAX 0x57/imm32/a-10 - c3/return - -== data - -_test-slice-empty: - # nothing -_test-slice-empty-end: - -_test-slice-hex-int: - 33/3 34/4 -_test-slice-hex-int-end: - -_test-slice-hex-int-letters-negative: - 2d/- -_test-slice-hex-int-letters: - 33/3 34/4 61/a -_test-slice-hex-int-letters-end: - -_test-slice-hex-int-single-letter: - 61/a -_test-slice-hex-int-single-letter-end: - -_test-slice-char-and-digits: - 71/q 33/3 34/4 -_test-slice-char-and-digits-end: - -_test-slice-digits-and-char: - 33/3 34/4 71/q -_test-slice-digits-and-char-end: - -_test-slice-hex-int-with-0x-prefix-negative: - 2d/- -_test-slice-hex-int-with-0x-prefix: - 30/0 78/x 33/3 34/4 -_test-slice-hex-int-with-0x-prefix-end: - -_test-slice-hex-int-zero: - 30/0 -_test-slice-hex-int-zero-end: - -_test-slice-hex-int-with-0-prefix: - 30/0 33/3 -_test-slice-hex-int-with-0-prefix-end: - -_test-slice-hex-int-negative-with-0-prefix: - 2d/- 30/0 33/3 -_test-slice-hex-int-negative-with-0-prefix-end: - -# . . vim:nowrap:textwidth=0 diff --git a/subx/064write-byte.subx b/subx/064write-byte.subx new file mode 100644 index 00000000..9c9b50a6 --- /dev/null +++ b/subx/064write-byte.subx @@ -0,0 +1,233 @@ +# write-byte: write a single byte to a buffered-file. The write may be buffered. +# flush: write out any buffered writes to disk. +# +# TODO: Come up with a way to signal failure to write to disk. This is hard +# since the failure may impact previous calls that were buffered. + +== data + +# The buffered file for standard output. +Stdout: + # file descriptor or (address stream) + 01 00 00 00 # 1 = standard output + # 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. But +# I don't want to type in 1024 bytes here. + +== code +# instruction effective address register 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: + 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +# Write lower byte of 'n' to 'f'. +write-byte: # f : (address buffered-file), n : int -> + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # . save registers + 51/push-ECX + 57/push-EDI + # EDI = f + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 7/r32/EDI 8/disp8 . # copy *(EBP+8) to EDI + # ECX = f->write + 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 1/r32/ECX 4/disp8 . # copy *(EDI+4) to ECX + # if (f->write >= f->length) flush and clear f's stream + 3b/compare 1/mod/*+disp8 7/rm32/EDI . . . 1/r32/ECX 0xc/disp8 . # compare ECX with *(EDI+12) + 7c/jump-if-lesser $write-byte:to-stream/disp8 + # . flush(f) + # . . push args + 57/push-EDI + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # . clear-stream(stream = f+4) + # . . push args + 8d/copy-address 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 4/disp8 . # copy EDI+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 + # . f->write must now be 0; update its cache at ECX + 31/xor 3/mod/direct 1/rm32/ECX . . . 1/r32/ECX . . # clear ECX +$write-byte:to-stream: + # write to stream + # f->data[f->write] = LSB(n) + 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX + 8a/copy-byte 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/AL 0xc/disp8 . # copy byte at *(EBP+12) to AL + 88/copy-byte 1/mod/*+disp8 4/rm32/sib 7/base/EDI 1/index/ECX . 0/r32/AL 0x10/disp8 . # copy AL to *(EDI+ECX+16) + # ++f->write + ff 0/subop/increment 1/mod/*+disp8 7/rm32/EDI . . . . 4/disp8 . # increment *(EDI+4) +$write-byte:end: + # . restore registers + 5f/pop-to-EDI + 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 + +flush: # f : (address buffered-file) -> + # . 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 5/rm32/EBP . . . 0/r32/EAX 8/disp8 . # copy *(EBP+8) to EAX + # write-stream(f->fd, data = f+4) + # . . push args + 8d/copy-address 1/mod/*+disp8 0/rm32/EAX . . . 1/r32/ECX 4/disp8 . # copy EAX+4 to ECX + 51/push-ECX + ff 6/subop/push 0/mod/indirect 0/rm32/EAX . . . . . . # push *EAX + # . . call + e8/call write-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP +$flush: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 + +# - tests + +test-write-byte-single: + # - check that write-byte writes to first byte of 'file' + # setup + # . 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-byte(_test-buffered-file, 'A') + # . . push args + 68/push 0x41/imm32 + 68/push _test-buffered-file/imm32 + # . . call + e8/call write-byte/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # flush(_test-buffered-file) + # . . push args + 68/push _test-buffered-file/imm32 + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(*_test-stream->data, 'A', msg) + # . . push args + 68/push "F - test-write-byte-single"/imm32 + 68/push 0x41/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . 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-byte-multiple-flushes: + # - check that write-byte correctly flushes buffered data + # setup + # . 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 + # fill up the buffer for _test-buffered-file + # . write(_test-buffered-file+4, 'abcdef') + # . . push args + 68/push "abcdef"/imm32 + b8/copy-to-EAX _test-buffered-file/imm32 + 05/add-to-EAX 4/imm32 + 50/push-EAX + # . . call + e8/call write/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # write-byte(_test-buffered-file, 'g') + # . . push args + 68/push 0x67/imm32 + 68/push _test-buffered-file/imm32 + # . . call + e8/call write-byte/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # flush(_test-buffered-file) + # . . push args + 68/push _test-buffered-file/imm32 + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(_test-stream->data[0..3], 'abcd', msg) + # . . push args + 68/push "F - test-write-byte-multiple-flushes: 1"/imm32 + 68/push 0x64636261/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # check-ints-equal(_test-stream->data[4..8], 'efg', msg) + # . . push args + 68/push "F - test-write-byte-multiple-flushes"/imm32 + 68/push 0x00676665/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0x10/disp8 . # push *(EAX+16) + # . . 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 + +# . . vim:nowrap:textwidth=0 diff --git a/subx/065hex.subx b/subx/065hex.subx new file mode 100644 index 00000000..6ba2ef7e --- /dev/null +++ b/subx/065hex.subx @@ -0,0 +1,809 @@ +# some utilities for converting numbers to/from hex +# lowercase letters only for now + +== code +# instruction effective address register 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: + 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +is-hex-int?: # in : (address slice) -> EAX : boolean + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # . save registers + 51/push-ECX + 52/push-EDX + 53/push-EBX + # ECX = s + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 8/disp8 . # copy *(EBP+8) to ECX + # EDX = s->end + 8b/copy 1/mod/*+disp8 1/rm32/ECX . . . 2/r32/EDX 4/disp8 . # copy *(ECX+4) to EDX + # curr/ECX = s->start + 8b/copy 0/mod/indirect 1/rm32/ECX . . . 1/r32/ECX . . # copy *ECX to ECX + # if s is empty return false + b8/copy-to-EAX 0/imm32/false + 39/compare 3/mod/direct 1/rm32/ECX . . . 2/r32/EDX . . # compare ECX with EDX + 7d/jump-if-greater-or-equal $is-hex-int?:end/disp8 + # skip past leading '-' + # . if (*curr == '-') ++curr + 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX + 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 3/r32/BL . . # copy byte at *ECX to BL + 81 7/subop/compare 3/mod/direct 3/rm32/EBX . . . . . 0x2d/imm32/- # compare EBX + 75/jump-if-not-equal $is-hex-int?:initial-0/disp8 + # . ++curr + 41/increment-ECX + # skip past leading '0x' +$is-hex-int?:initial-0: + # . if (*curr != '0') jump to loop + 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX + 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 3/r32/BL . . # copy byte at *ECX to BL + 81 7/subop/compare 3/mod/direct 3/rm32/EBX . . . . . 0x30/imm32/0 # compare EBX + 75/jump-if-not-equal $is-hex-int?:loop/disp8 + # . ++curr + 41/increment-ECX +$is-hex-int?:initial-0x: + # . if (curr >= in->end) return true + 39/compare 3/mod/direct 1/rm32/ECX . . . 2/r32/EDX . . # compare ECX with EDX + 7d/jump-if-greater-or-equal $is-hex-int?:true/disp8 + # . if (*curr != 'x') jump to loop # the previous '0' is still valid so doesn't need to be checked again + 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX + 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 3/r32/BL . . # copy byte at *ECX to BL + 81 7/subop/compare 3/mod/direct 3/rm32/EBX . . . . . 0x78/imm32/x # compare EBX + 75/jump-if-not-equal $is-hex-int?:loop/disp8 + # . ++curr + 41/increment-ECX +$is-hex-int?:loop: + # if (curr >= in->end) return true + 39/compare 3/mod/direct 1/rm32/ECX . . . 2/r32/EDX . . # compare ECX with EDX + 7d/jump-if-greater-or-equal $is-hex-int?:true/disp8 + # EAX = is-hex-digit?(*curr) + # . . push args + 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 0/r32/AL . . # copy byte at *ECX to AL + 50/push-EAX + # . . call + e8/call is-hex-digit?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # if EAX == false return false + 3d/compare-with-EAX 0/imm32 + 74/jump-if-equal $is-hex-int?:end/disp8 + # ++curr + 41/increment-ECX + # loop + eb/jump $is-hex-int?:loop/disp8 +$is-hex-int?:true: + # return true + b8/copy-to-EAX 1/imm32/true +$is-hex-int?:end: + # . restore registers + 5b/pop-to-EBX + 5a/pop-to-EDX + 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 + +test-is-hex-int: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "34" + 68/push _test-slice-hex-int-end/imm32 + 68/push _test-slice-hex-int/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = is-hex-int?(slice) + # . . push args + 51/push-ECX + # . . call + e8/call is-hex-int?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 1, msg) + # . . push args + 68/push "F - test-is-hex-int"/imm32 + 68/push 1/imm32/true + 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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-is-hex-int-handles-letters: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "34a" + 68/push _test-slice-hex-int-letters-end/imm32 + 68/push _test-slice-hex-int-letters/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = is-hex-int?(slice) + # . . push args + 51/push-ECX + # . . call + e8/call is-hex-int?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 1, msg) + # . . push args + 68/push "F - test-is-hex-int-handles-letters"/imm32 + 68/push 1/imm32/true + 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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-is-hex-int-with-trailing-char: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "34q" + 68/push _test-slice-digits-and-char-end/imm32 + 68/push _test-slice-digits-and-char/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = is-hex-int?(slice) + # . . push args + 51/push-ECX + # . . call + e8/call is-hex-int?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-is-hex-int-with-trailing-char"/imm32 + 68/push 0/imm32/false + 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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-is-hex-int-with-leading-char: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "q34" + 68/push _test-slice-char-and-digits-end/imm32 + 68/push _test-slice-char-and-digits/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = is-hex-int?(slice) + # . . push args + 51/push-ECX + # . . call + e8/call is-hex-int?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-is-hex-int-with-leading-char"/imm32 + 68/push 0/imm32/false + 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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-is-hex-int-empty: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "" + 68/push _test-slice-empty-end/imm32 + 68/push _test-slice-empty/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = is-hex-int?(slice) + # . . push args + 51/push-ECX + # . . call + e8/call is-hex-int?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-is-hex-int-empty"/imm32 + 68/push 0/imm32/false + 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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-is-hex-int-handles-0x-prefix: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "0x3a" + 68/push _test-slice-hex-int-with-0x-prefix-end/imm32 + 68/push _test-slice-hex-int-with-0x-prefix/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = is-hex-int?(slice) + # . . push args + 51/push-ECX + # . . call + e8/call is-hex-int?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 1, msg) + # . . push args + 68/push "F - test-is-hex-int-handles-0x-prefix"/imm32 + 68/push 1/imm32/true + 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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-is-hex-int-handles-negative: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "-34a" + 68/push _test-slice-hex-int-letters-end/imm32 + 68/push _test-slice-hex-int-letters-negative/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = is-hex-int?(slice) + # . . push args + 51/push-ECX + # . . call + e8/call is-hex-int?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 1, msg) + # . . push args + 68/push "F - test-is-hex-int-handles-negative"/imm32 + 68/push 1/imm32/true + 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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-is-hex-int-handles-negative-0x-prefix: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "-0x3a" + 68/push _test-slice-hex-int-with-0x-prefix-end/imm32 + 68/push _test-slice-hex-int-with-0x-prefix-negative/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = is-hex-int?(slice) + # . . push args + 51/push-ECX + # . . call + e8/call is-hex-int?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 1, msg) + # . . push args + 68/push "F - test-is-hex-int-handles-negative-0x-prefix"/imm32 + 68/push 1/imm32/true + 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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +parse-hex-int: # in : (address slice) -> result/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 + 52/push-EDX + 53/push-EBX + 56/push-ESI + # result/EBX = 0 + 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX + # ECX = s + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 8/disp8 . # copy *(EBP+8) to ECX + # EDX = s->end + 8b/copy 1/mod/*+disp8 1/rm32/ECX . . . 2/r32/EDX 4/disp8 . # copy *(ECX+4) to EDX + # curr/ECX = s->start + 8b/copy 0/mod/indirect 1/rm32/ECX . . . 1/r32/ECX . . # copy *ECX to ECX + # negate?/ESI = false + 31/xor 3/mod/direct 6/rm32/ESI . . . 6/r32/ESI . . # clear ESI +$parse-hex-int:negative: + # . if (*curr == '-') negate = true + 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX + 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 0/r32/AL . . # copy byte at *ECX to AL + 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0x2d/imm32/- # compare EAX + 75/jump-if-not-equal $parse-hex-int:initial-0/disp8 + # . ++curr + 41/increment-ECX + # . negate = true + be/copy-to-ESI 1/imm32/true +$parse-hex-int:initial-0: + # skip past leading '0x' + # . if (*curr != '0') jump to loop + 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 0/r32/AL . . # copy byte at *ECX to AL + 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0x30/imm32/0 # compare EAX + 75/jump-if-not-equal $parse-hex-int:loop/disp8 + # . ++curr + 41/increment-ECX +$parse-hex-int:initial-0x: + # . if (curr >= in->end) return result + 39/compare 3/mod/direct 1/rm32/ECX . . . 2/r32/EDX . . # compare ECX with EDX + 7d/jump-if-greater-or-equal $parse-hex-int:end/disp8 + # . if (*curr != 'x') jump to loop # the previous '0' is still valid so doesn't need to be checked again + 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX + 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 0/r32/AL . . # copy byte at *ECX to AL + 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0x78/imm32/x # compare EAX + 75/jump-if-not-equal $parse-hex-int:loop/disp8 + # . ++curr + 41/increment-ECX +$parse-hex-int:loop: + # if (curr >= in->end) break + 39/compare 3/mod/direct 1/rm32/ECX . . . 2/r32/EDX . . # compare ECX with EDX + 7d/jump-if-greater-or-equal $parse-hex-int:negate/disp8 + # EAX = from-hex-char(*curr) + # . . copy arg to EAX + 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 0/r32/AL . . # copy byte at *ECX to AL + # . . call + e8/call from-hex-char/disp32 + # result = result * 16 + EAX + c1/shift 4/subop/left 3/mod/direct 3/rm32/EBX . . . . . 4/imm8 # shift EBX left by 4 bits + 01/add 3/mod/direct 3/rm32/EBX . . . 0/r32/EAX . . # add EAX to EBX + # ++curr + 41/increment-ECX + # loop + eb/jump $parse-hex-int:loop/disp8 +$parse-hex-int:negate: + 81 7/subop/compare 3/mod/direct 6/rm32/ESI . . . . . 0/imm32 # compare ESI + 74/jump-if-equal $parse-hex-int:end/disp8 + f7 3/subop/negate 3/mod/direct 3/rm32/EBX . . . . . . # negate EBX +$parse-hex-int:end: + 89/copy 3/mod/direct 0/rm32/EAX . . . 3/r32/EBX . . # copy EBX to EAX + # . restore registers + 5e/pop-to-ESI + 5b/pop-to-EBX + 5a/pop-to-EDX + 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 + +test-parse-hex-int-single-digit: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "a" + 68/push _test-slice-hex-int-single-letter-end/imm32 + 68/push _test-slice-hex-int-single-letter/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = parse-hex-int(slice) + # . . push args + 51/push-ECX + # . . call + e8/call parse-hex-int/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0xa, msg) + # . . push args + 68/push "F - test-parse-hex-int-single-digit"/imm32 + 68/push 0xa/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-parse-hex-int-multi-digit: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "34a" + 68/push _test-slice-hex-int-letters-end/imm32 + 68/push _test-slice-hex-int-letters/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = parse-hex-int(slice) + # . . push args + 51/push-ECX + # . . call + e8/call parse-hex-int/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0x34a, msg) + # . . push args + 68/push "F - test-parse-hex-int-multi-digit"/imm32 + 68/push 0x34a/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-parse-hex-int-0x-prefix: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "0x34" + 68/push _test-slice-hex-int-with-0x-prefix-end/imm32 + 68/push _test-slice-hex-int-with-0x-prefix/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = parse-hex-int(slice) + # . . push args + 51/push-ECX + # . . call + e8/call parse-hex-int/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0x34a, msg) + # . . push args + 68/push "F - test-parse-hex-int-0x-prefix"/imm32 + 68/push 0x34/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-parse-hex-int-zero: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "0" + 68/push _test-slice-hex-int-zero-end/imm32 + 68/push _test-slice-hex-int-zero/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = parse-hex-int(slice) + # . . push args + 51/push-ECX + # . . call + e8/call parse-hex-int/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0x34a, msg) + # . . push args + 68/push "F - test-parse-hex-int-zero"/imm32 + 68/push 0/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-parse-hex-int-0-prefix: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "03" + 68/push _test-slice-hex-int-with-0-prefix-end/imm32 + 68/push _test-slice-hex-int-with-0-prefix/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = parse-hex-int(slice) + # . . push args + 51/push-ECX + # . . call + e8/call parse-hex-int/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0x3, msg) + # . . push args + 68/push "F - test-parse-hex-int-0-prefix"/imm32 + 68/push 0x3/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-parse-hex-int-negative: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = "-03" + 68/push _test-slice-hex-int-negative-with-0-prefix-end/imm32 + 68/push _test-slice-hex-int-negative-with-0-prefix/imm32 + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = parse-hex-int(slice) + # . . push args + 51/push-ECX + # . . call + e8/call parse-hex-int/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0xfffffffd, msg) + # . . push args + 68/push "F - test-parse-hex-int-negative"/imm32 + 68/push 0xfffffffd/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +is-hex-digit?: # c : byte -> EAX : boolean + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # . save registers + 51/push-ECX + # ECX = c + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 8/disp8 . # copy *(EBP+8) to ECX + # return false if c < '0' + b8/copy-to-EAX 0/imm32/false + 81 7/subop/compare 3/mod/direct 1/rm32/ECX . . . . . 0x30/imm32 # compare ECX + 7c/jump-if-lesser $is-hex-digit?:end/disp8 + # return false if c > 'f' + 81 7/subop/compare 3/mod/direct 1/rm32/ECX . . . . . 0x66/imm32 # compare ECX + 7f/jump-if-greater $is-hex-digit?:end/disp8 + # return true if c <= '9' + b8/copy-to-EAX 1/imm32/true + 81 7/subop/compare 3/mod/direct 1/rm32/ECX . . . . . 0x39/imm32 # compare ECX + 7e/jump-if-lesser-or-equal $is-hex-digit?:end/disp8 + # return true if c >= 'a' + 81 7/subop/compare 3/mod/direct 1/rm32/ECX . . . . . 0x61/imm32 # compare ECX + 7d/jump-if-greater-or-equal $is-hex-digit?:end/disp8 + # otherwise return false + b8/copy-to-EAX 0/imm32/false +$is-hex-digit?:end: + # . restore registers + 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 + +test-hex-below-0: + # EAX = is-hex-digit?(0x2f) + # . . push args + 68/push 0x2f/imm32 + # . . call + e8/call is-hex-digit?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-hex-below-0"/imm32 + 68/push 0/imm32/false + 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 + c3/return + +test-hex-0-to-9: + # EAX = is-hex-digit?(0x30) + # . . push args + 68/push 0x30/imm32 + # . . call + e8/call is-hex-digit?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 1, msg) + # . . push args + 68/push "F - test-hex-at-0"/imm32 + 68/push 1/imm32/true + 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 + # EAX = is-hex-digit?(0x39) + # . . push args + 68/push 0x39/imm32 + # . . call + e8/call is-hex-digit?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 1, msg) + # . . push args + 68/push "F - test-hex-at-9"/imm32 + 68/push 1/imm32/true + 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 + c3/return + +test-hex-above-9-to-a: + # EAX = is-hex-digit?(0x3a) + # . . push args + 68/push 0x3a/imm32 + # . . call + e8/call is-hex-digit?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-hex-above-9-to-a"/imm32 + 68/push 0/imm32/false + 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 + c3/return + +test-hex-a-to-f: + # EAX = is-hex-digit?(0x61) + # . . push args + 68/push 0x61/imm32 + # . . call + e8/call is-hex-digit?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 1, msg) + # . . push args + 68/push "F - test-hex-at-a"/imm32 + 68/push 1/imm32/true + 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 + # EAX = is-hex-digit?(0x66) + # . . push args + 68/push 0x66/imm32 + # . . call + e8/call is-hex-digit?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 1, msg) + # . . push args + 68/push "F - test-hex-at-f"/imm32 + 68/push 1/imm32/true + 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 + c3/return + +test-hex-above-f: + # EAX = is-hex-digit?(0x67) + # . . push args + 68/push 0x67/imm32 + # . . call + e8/call is-hex-digit?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-hex-above-f"/imm32 + 68/push 0/imm32/false + 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 + c3/return + +from-hex-char: # in/EAX : byte -> out/EAX : num + # no error checking; accepts argument in EAX + # if EAX <= '9' return EAX - '0' + 3d/compare-EAX 0x39/imm32/9 + 7f/jump-if-greater $from-hex-char:else/disp8 + 2d/subtract-from-EAX 0x30/imm32/0 + c3/return +$from-hex-char:else: + # otherwise return EAX - 'a' + 10 + 2d/subtract-from-EAX 0x57/imm32/a-10 + c3/return + +to-hex-char: # in/EAX : nibble -> out/EAX : byte + # no error checking; accepts argument in EAX + # if EAX <= 9 return EAX + '0' + 3d/compare-EAX 0x9/imm32/9 + 7f/jump-if-greater $to-hex-char:else/disp8 + 05/add-to-EAX 0x30/imm32/0 + c3/return +$to-hex-char:else: + # otherwise return EAX + 'a' - 10 + 05/add-to-EAX 0x57/imm32/a-10 + c3/return + +== data + +_test-slice-empty: + # nothing +_test-slice-empty-end: + +_test-slice-hex-int: + 33/3 34/4 +_test-slice-hex-int-end: + +_test-slice-hex-int-letters-negative: + 2d/- +_test-slice-hex-int-letters: + 33/3 34/4 61/a +_test-slice-hex-int-letters-end: + +_test-slice-hex-int-single-letter: + 61/a +_test-slice-hex-int-single-letter-end: + +_test-slice-char-and-digits: + 71/q 33/3 34/4 +_test-slice-char-and-digits-end: + +_test-slice-digits-and-char: + 33/3 34/4 71/q +_test-slice-digits-and-char-end: + +_test-slice-hex-int-with-0x-prefix-negative: + 2d/- +_test-slice-hex-int-with-0x-prefix: + 30/0 78/x 33/3 34/4 +_test-slice-hex-int-with-0x-prefix-end: + +_test-slice-hex-int-zero: + 30/0 +_test-slice-hex-int-zero-end: + +_test-slice-hex-int-with-0-prefix: + 30/0 33/3 +_test-slice-hex-int-with-0-prefix-end: + +_test-slice-hex-int-negative-with-0-prefix: + 2d/- 30/0 33/3 +_test-slice-hex-int-negative-with-0-prefix-end: + +# . . vim:nowrap:textwidth=0 diff --git a/subx/065print-byte.subx b/subx/065print-byte.subx deleted file mode 100644 index 3d362fa0..00000000 --- a/subx/065print-byte.subx +++ /dev/null @@ -1,104 +0,0 @@ -# Print the (hex) textual representation of the lowest byte of a number. - -== code -# instruction effective address register 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: - 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -print-byte: # f : (address buffered-file), n : int -> - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # . save registers - 50/push-EAX - # AL = convert upper nibble to hex - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 0xc/disp8 . # copy *(EBP+12) to EAX - c1/shift 5/subop/logic-right 3/mod/direct 0/rm32/EAX . . . . . 4/imm8 # shift EAX right by 4 bits, while padding zeroes - 25/and-EAX 0xf/imm32 - # . AL = to-hex-char(AL) - e8/call to-hex-char/disp32 - # write-byte(f, AL) - # . . push args - 50/push-EAX - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) - # . . call - e8/call write-byte/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # AL = convert lower nibble to hex - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 0xc/disp8 . # copy *(EBP+12) to EAX - 25/and-EAX 0xf/imm32 - # . AL = to-hex-char(AL) - e8/call to-hex-char/disp32 - # write-byte(f, AL) - # . . push args - 50/push-EAX - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) - # . . call - e8/call write-byte/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP -$print-byte:end: - # . restore registers - 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-print-byte: - # - check that print-byte prints the hex textual representation - # setup - # . 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 - # print-byte(_test-buffered-file, 0xa) # exercises digit, non-digit as well as leading zero - # . . push args - 68/push 0xa/imm32 - 68/push _test-buffered-file/imm32 - # . . call - e8/call print-byte/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # flush(_test-buffered-file) - # . . push args - 68/push _test-buffered-file/imm32 - # . . call - e8/call flush/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(*_test-stream->data, '0a', msg) - # . . push args - 68/push "F - test-print-byte"/imm32 - 68/push 0x6130/imm32/0a - # . . push *_test-stream->data - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) - # . . 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 - -# . . vim:nowrap:textwidth=0 diff --git a/subx/066print-byte.subx b/subx/066print-byte.subx new file mode 100644 index 00000000..3d362fa0 --- /dev/null +++ b/subx/066print-byte.subx @@ -0,0 +1,104 @@ +# Print the (hex) textual representation of the lowest byte of a number. + +== code +# instruction effective address register 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: + 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +print-byte: # f : (address buffered-file), n : int -> + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # . save registers + 50/push-EAX + # AL = convert upper nibble to hex + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 0xc/disp8 . # copy *(EBP+12) to EAX + c1/shift 5/subop/logic-right 3/mod/direct 0/rm32/EAX . . . . . 4/imm8 # shift EAX right by 4 bits, while padding zeroes + 25/and-EAX 0xf/imm32 + # . AL = to-hex-char(AL) + e8/call to-hex-char/disp32 + # write-byte(f, AL) + # . . push args + 50/push-EAX + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call write-byte/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # AL = convert lower nibble to hex + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 0xc/disp8 . # copy *(EBP+12) to EAX + 25/and-EAX 0xf/imm32 + # . AL = to-hex-char(AL) + e8/call to-hex-char/disp32 + # write-byte(f, AL) + # . . push args + 50/push-EAX + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call write-byte/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP +$print-byte:end: + # . restore registers + 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-print-byte: + # - check that print-byte prints the hex textual representation + # setup + # . 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 + # print-byte(_test-buffered-file, 0xa) # exercises digit, non-digit as well as leading zero + # . . push args + 68/push 0xa/imm32 + 68/push _test-buffered-file/imm32 + # . . call + e8/call print-byte/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # flush(_test-buffered-file) + # . . push args + 68/push _test-buffered-file/imm32 + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(*_test-stream->data, '0a', msg) + # . . push args + 68/push "F - test-print-byte"/imm32 + 68/push 0x6130/imm32/0a + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . 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 + +# . . vim:nowrap:textwidth=0 diff --git a/subx/066write-buffered.subx b/subx/066write-buffered.subx deleted file mode 100644 index 9ae08b21..00000000 --- a/subx/066write-buffered.subx +++ /dev/null @@ -1,220 +0,0 @@ -# write-buffered: like 'write', but for a buffered-file - -== code -# instruction effective address register 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: -#? e8/call test-write-buffered/disp32 -#? e8/call test-write-buffered-with-intermediate-flush/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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -write-buffered: # f : (address buffered-file), msg : (address array byte) -> - # pseudocode: - # in = msg->data - # inend = &msg->data[msg->length] - # while in < inend - # if f->write >= f->length - # flush(f) - # clear-stream(f) - # f->data[f->write] = *in - # ++f->write - # ++in - # - # registers: - # ESI : in - # ECX : inend - # EDI : f - # EDX : f->length - # EBX : f->write (cached copy, need to keep in sync) - # EAX : temp - # - # . 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 - 57/push-EDI - # EAX = msg - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . 0/r32/EAX 0xc/disp8 . # copy *(EBP+12) to EAX - # in/ESI = msg->data - 8d/copy-address 1/mod/*+disp8 0/rm32/EAX . . . 6/r32/ESI 4/disp8 . # copy EAX+4 to ESI - # inend/ECX = &msg->data[msg->length] - 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX - 8d/copy-address 0/mod/indirect 4/rm32/sib 6/base/ESI 1/index/ECX . 1/r32/ECX . . # copy ESI+ECX to ECX - # EDI = f - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . 7/r32/EDI 8/disp8 . # copy *(EBP+8) to EDI - # EDX = f->length - 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 2/r32/EDX 0xc/disp8 . # copy *(EDI+12) to EDX - # EBX = f->write - 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 3/r32/EBX 4/disp8 . # copy *(EDI+4) to EBX -$write-buffered:loop: - # if (in >= inend) break - 39/compare 3/mod/direct 6/rm32/ESI . . . 1/r32/ECX . . # compare ESI with ECX - 7d/jump-if-greater-or-equal $write-buffered:loop-end/disp8 - # if (f->write >= f->length) flush and clear f's stream - 39/compare 3/mod/direct 3/rm32/EBX . . . 2/r32/EDX . . # compare EBX with EDX - 7c/jump-if-lesser $write-buffered:to-stream/disp8 - # . persist f->write - 89/copy 1/mod/*+disp8 7/rm32/EDI . . . 3/r32/EBX 4/disp8 . # copy EBX to *(EDI+4) - # . flush(f) - # . . push args - 57/push-EDI - # . . call - e8/call flush/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(stream = f+4) - # . . push args - 8d/copy-address 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 4/disp8 . # copy EDI+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 - # . f->write must now be 0; update its cache at EBX - 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX -$write-buffered:to-stream: - # write to stream - # f->data[f->write] = *in - # . AL = *in - 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX - 8a/copy-byte 0/mod/indirect 6/rm32/ESI . . . 0/r32/AL . . # copy byte at *ESI to AL - # . f->data[f->write] = AL - 88/copy-byte 1/mod/*+disp8 4/rm32/sib 7/base/EDI 3/index/EBX . 0/r32/AL 0x10/disp8 . # copy AL to *(EDI+EBX+16) - # ++f->write - 43/increment-EBX - # ++in - 46/increment-ESI - eb/jump $write-buffered:loop/disp8 -$write-buffered:loop-end: - # persist necessary variables from registers - 89/copy 1/mod/*+disp8 7/rm32/EDI . . . 3/r32/EBX 4/disp8 . # copy EBX to *(EDI+4) -$write-buffered:end: - # . restore registers - 5f/pop-to-EDI - 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 - -test-write-buffered: - # - check that write-buffered writes to the file - # setup - # . 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-buffered(_test-buffered-file, "Abc") - # . . push args - 68/push "Abc"/imm32 - 68/push _test-buffered-file/imm32 - # . . call - e8/call write-buffered/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # flush(_test-buffered-file) - # . . push args - 68/push _test-buffered-file/imm32 - # . . call - e8/call flush/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(*_test-stream->data, "Abc", msg) - # . . push args - 68/push "F - test-write-buffered-single"/imm32 - 68/push 0x636241/imm32 - # . . push *_test-stream->data - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) - # . . 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-buffered-with-intermediate-flush: - # - check that write-buffered flushes in the middle if its buffer fills up - # setup - # . 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 - # _test-stream can hold 8 bytes, but _test-buffered-file can hold only 6. - # Try to write 7 bytes. - # . write-buffered(_test-buffered-file, "Abcdefg") - # . . push args - 68/push "Abcdefg"/imm32 - 68/push _test-buffered-file/imm32 - # . . call - e8/call write-buffered/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # don't flush - # 6 bytes should still have gotten to _test-stream - # . check-ints-equal(*_test-stream->write, 6, msg) - # . . push args - 68/push "F - test-write-buffered-with-intermediate-flush: flushed data"/imm32 - 68/push 6/imm32 - # . . push *_test-stream->write - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 0/mod/indirect 0/rm32/EAX . . . . . . # 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 - # and 1 byte should still be in _test-buffered-file - # . check-ints-equal(*_test-buffered-file>write, 1, msg) - # . . push args - 68/push "F - test-write-buffered-with-intermediate-flush: unflushed bytes"/imm32 - 68/push 1/imm32 - # . . push *_test-buffered-file->write - b8/copy-to-EAX _test-buffered-file/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) - # . . 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 - -# . . vim:nowrap:textwidth=0 diff --git a/subx/067error-byte.subx b/subx/067error-byte.subx deleted file mode 100644 index 5f9a4ada..00000000 --- a/subx/067error-byte.subx +++ /dev/null @@ -1,112 +0,0 @@ -# Print an error message followed by the text representation of a byte. Then exit. - -== code -# instruction effective address register 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 -#? # . var ed/EAX : exit-descriptor -#? 81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP -#? 89/copy 3/mod/direct 0/rm32/EAX . . . 4/r32/ESP . . # copy ESP to EAX -#? # . configure ed to really exit() -#? # . . ed->target = 0 -#? c7 0/subop/copy 0/mod/direct 0/rm32/EAX . . . . . 0/imm32 # copy to *EAX -#? # . error-byte(ed, Stdout, msg, 34) -#? 68/push 0x34/imm32 -#? 68/push "abc"/imm32 -#? 68/push Stderr/imm32 -#? 50/push-EAX -#? e8/call error-byte/disp32 - # automatic test - 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -# write(out, "Error: "+msg+": "+byte) then stop(ed, 1) -error-byte: # ed : (address exit-descriptor), out : (address buffered-file), msg : (address array byte), n : byte -> - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # write-buffered(out, "Error: ") - # . . push args - 68/push "Error: "/imm32 - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) - # . . call - e8/call write-buffered/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # write-buffered(out, msg) - # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x10/disp8 . # push *(EBP+16) - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) - # . . call - e8/call write-buffered/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # write-buffered(out, ": ") - # . . push args - 68/push ": "/imm32 - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) - # . . call - e8/call write-buffered/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # print-byte(out, byte) - # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x14/disp8 . # push *(EBP+20) - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) - # . . call - e8/call print-byte/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # write-buffered(out, Newline) - # . . push args - 68/push Newline/imm32 - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) - # . . call - e8/call write-buffered/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # . flush(out) - # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) - # . . call - e8/call flush/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # stop(ed, 1) - # . . push args - 68/push 1/imm32 - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) - # . . call - e8/call stop/disp32 - # should never get past this point -$error-byte:dead-end: - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -== data - -# The buffered file for standard error. -Stderr: - # file descriptor or (address stream) - 02 00 00 00 # 1 = standard error - # 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. But -# I don't want to type in 1024 bytes here. - -# . . vim:nowrap:textwidth=0 diff --git a/subx/067write-buffered.subx b/subx/067write-buffered.subx new file mode 100644 index 00000000..9ae08b21 --- /dev/null +++ b/subx/067write-buffered.subx @@ -0,0 +1,220 @@ +# write-buffered: like 'write', but for a buffered-file + +== code +# instruction effective address register 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: +#? e8/call test-write-buffered/disp32 +#? e8/call test-write-buffered-with-intermediate-flush/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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +write-buffered: # f : (address buffered-file), msg : (address array byte) -> + # pseudocode: + # in = msg->data + # inend = &msg->data[msg->length] + # while in < inend + # if f->write >= f->length + # flush(f) + # clear-stream(f) + # f->data[f->write] = *in + # ++f->write + # ++in + # + # registers: + # ESI : in + # ECX : inend + # EDI : f + # EDX : f->length + # EBX : f->write (cached copy, need to keep in sync) + # EAX : temp + # + # . 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 + 57/push-EDI + # EAX = msg + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . 0/r32/EAX 0xc/disp8 . # copy *(EBP+12) to EAX + # in/ESI = msg->data + 8d/copy-address 1/mod/*+disp8 0/rm32/EAX . . . 6/r32/ESI 4/disp8 . # copy EAX+4 to ESI + # inend/ECX = &msg->data[msg->length] + 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX + 8d/copy-address 0/mod/indirect 4/rm32/sib 6/base/ESI 1/index/ECX . 1/r32/ECX . . # copy ESI+ECX to ECX + # EDI = f + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . 7/r32/EDI 8/disp8 . # copy *(EBP+8) to EDI + # EDX = f->length + 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 2/r32/EDX 0xc/disp8 . # copy *(EDI+12) to EDX + # EBX = f->write + 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 3/r32/EBX 4/disp8 . # copy *(EDI+4) to EBX +$write-buffered:loop: + # if (in >= inend) break + 39/compare 3/mod/direct 6/rm32/ESI . . . 1/r32/ECX . . # compare ESI with ECX + 7d/jump-if-greater-or-equal $write-buffered:loop-end/disp8 + # if (f->write >= f->length) flush and clear f's stream + 39/compare 3/mod/direct 3/rm32/EBX . . . 2/r32/EDX . . # compare EBX with EDX + 7c/jump-if-lesser $write-buffered:to-stream/disp8 + # . persist f->write + 89/copy 1/mod/*+disp8 7/rm32/EDI . . . 3/r32/EBX 4/disp8 . # copy EBX to *(EDI+4) + # . flush(f) + # . . push args + 57/push-EDI + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # . clear-stream(stream = f+4) + # . . push args + 8d/copy-address 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 4/disp8 . # copy EDI+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 + # . f->write must now be 0; update its cache at EBX + 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX +$write-buffered:to-stream: + # write to stream + # f->data[f->write] = *in + # . AL = *in + 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX + 8a/copy-byte 0/mod/indirect 6/rm32/ESI . . . 0/r32/AL . . # copy byte at *ESI to AL + # . f->data[f->write] = AL + 88/copy-byte 1/mod/*+disp8 4/rm32/sib 7/base/EDI 3/index/EBX . 0/r32/AL 0x10/disp8 . # copy AL to *(EDI+EBX+16) + # ++f->write + 43/increment-EBX + # ++in + 46/increment-ESI + eb/jump $write-buffered:loop/disp8 +$write-buffered:loop-end: + # persist necessary variables from registers + 89/copy 1/mod/*+disp8 7/rm32/EDI . . . 3/r32/EBX 4/disp8 . # copy EBX to *(EDI+4) +$write-buffered:end: + # . restore registers + 5f/pop-to-EDI + 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 + +test-write-buffered: + # - check that write-buffered writes to the file + # setup + # . 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-buffered(_test-buffered-file, "Abc") + # . . push args + 68/push "Abc"/imm32 + 68/push _test-buffered-file/imm32 + # . . call + e8/call write-buffered/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # flush(_test-buffered-file) + # . . push args + 68/push _test-buffered-file/imm32 + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(*_test-stream->data, "Abc", msg) + # . . push args + 68/push "F - test-write-buffered-single"/imm32 + 68/push 0x636241/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . 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-buffered-with-intermediate-flush: + # - check that write-buffered flushes in the middle if its buffer fills up + # setup + # . 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 + # _test-stream can hold 8 bytes, but _test-buffered-file can hold only 6. + # Try to write 7 bytes. + # . write-buffered(_test-buffered-file, "Abcdefg") + # . . push args + 68/push "Abcdefg"/imm32 + 68/push _test-buffered-file/imm32 + # . . call + e8/call write-buffered/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # don't flush + # 6 bytes should still have gotten to _test-stream + # . check-ints-equal(*_test-stream->write, 6, msg) + # . . push args + 68/push "F - test-write-buffered-with-intermediate-flush: flushed data"/imm32 + 68/push 6/imm32 + # . . push *_test-stream->write + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 0/mod/indirect 0/rm32/EAX . . . . . . # 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 + # and 1 byte should still be in _test-buffered-file + # . check-ints-equal(*_test-buffered-file>write, 1, msg) + # . . push args + 68/push "F - test-write-buffered-with-intermediate-flush: unflushed bytes"/imm32 + 68/push 1/imm32 + # . . push *_test-buffered-file->write + b8/copy-to-EAX _test-buffered-file/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) + # . . 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 + +# . . vim:nowrap:textwidth=0 diff --git a/subx/068allocate.subx b/subx/068allocate.subx deleted file mode 100644 index 37b01d21..00000000 --- a/subx/068allocate.subx +++ /dev/null @@ -1,207 +0,0 @@ -# Helper to dynamically allocate memory on the heap. -# -# We'd like to be able to write tests for functions that allocate memory, -# making assertions on the precise addresses used. To achieve this we'll pass -# in an *allocation descriptor* to allocate from. -# -# Allocation descriptors are also useful outside of tests. Assembly and machine -# code are of necessity unsafe languages, and one of the most insidious kinds -# of bugs unsafe languages expose us to are dangling pointers to memory that -# has been freed and potentially even reused for something totally different. -# To reduce the odds of such "use after free" errors, SubX programs tend to not -# reclaim and reuse dynamically allocated memory. (Running out of memory is far -# easier to debug.) Long-running programs that want to reuse memory are mostly -# on their own to be careful. However, they do get one bit of help: they can -# carve out chunks of memory and then allocate from them manually using this -# very same 'allocate' helper. They just need a new allocation descriptor for -# their book-keeping. - -== data - -# The 'global' allocation descriptor. Pass this into 'allocate' to claim a -# hitherto unused bit of memory. -Heap: - Start-of-heap/imm32 # curr - 00 00 00 0b # limit = 0x0b000000; keep sync'd with DATA_SEGMENT + SEGMENT_ALIGNMENT - -== code -# instruction effective address register 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: - 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -# Claim the next 'n' bytes of memory starting at ad->curr and update ad->curr. -# If there isn't enough memory before ad->limit, return 0 and leave 'ad' unmodified. -allocate: # ad : (address allocation-descriptor), n : int -> address-or-null/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 - 52/push-EDX - # ECX = ad - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 8/disp8 . # copy *(EBP+8) to ECX - # save ad->curr - 8b/copy 0/mod/indirect 1/rm32/ECX . . . 0/r32/EAX . . # copy *ECX to EAX - # check if there's enough space - # . EDX = ad->curr + n - 89/copy 3/mod/direct 2/rm32/EDX . . . 0/r32/EAX . . # copy EAX to EDX - 03/add 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0xc/disp8 . # add *(EBP+12) to EDX - 3b/compare 1/mod/*+disp8 1/rm32/ECX . . . 2/r32/EDX 4/disp8 . # compare EDX with *(ECX+4) - 7c/jump-if-lesser $allocate:commit/disp8 - # return null if not - b8/copy-to-EAX 0/imm32 - eb/jump $allocate:end/disp8 -$allocate:commit: - # update ad->curr - 89/copy 0/mod/indirect 1/rm32/ECX . . . 2/r32/EDX . . # copy EDX to *ECX -$allocate:end: - # . restore registers - 5a/pop-to-EDX - 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 - -test-allocate-success: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var ad/ECX : (address allocation-descriptor) = {11, 15} - 68/push 0xf/imm32/limit - 68/push 0xb/imm32/curr - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = allocate(ad, 3) - # . . push args - 68/push 3/imm32 - 51/push-ECX - # . . call - e8/call allocate/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 11, msg) - # . . push args - 68/push "F - test-allocate-success: returns current pointer of allocation descriptor"/imm32 - 68/push 0xb/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 - # check-ints-equal(ad->curr, 14, msg) - # . . push args - 68/push "F - test-allocate-success: updates allocation descriptor"/imm32 - 68/push 0xe/imm32 - ff 6/subop/push 0/mod/indirect 1/rm32/ECX . . . . . . # push *ECX - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-allocate-failure: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var ad/ECX : (address allocation-descriptor) = {11, 15} - 68/push 0xf/imm32/limit - 68/push 0xb/imm32/curr - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = allocate(ad, 6) - # . . push args - 68/push 6/imm32 - 51/push-ECX - # . . call - e8/call allocate/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-allocate-failure: returns null"/imm32 - 68/push 0/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 - # no change to ad->curr - # . check-ints-equal(ad->curr, 11) - # . . push args - 68/push "F - test-allocate-failure: updates allocation descriptor"/imm32 - 68/push 0xb/imm32 - ff 6/subop/push 0/mod/indirect 1/rm32/ECX . . . . . . # push *ECX - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -# helper: create a nested allocation descriptor (useful for tests) -allocate-region: # ad : (address allocation-descriptor), n : int -> new-ad : (address allocation-descriptor) - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # . save registers - 51/push-ECX - # EAX = allocate(ad, n) - # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) - # . . call - e8/call allocate/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # if EAX == 0 abort - 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0/imm32 # compare EAX - 74/jump-if-equal $allocate-region:abort/disp8 - # earmark 8 bytes at the start for a new allocation descriptor - # . *EAX = EAX + 8 - 89/copy 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # copy EAX to ECX - 81 0/subop/add 3/mod/direct 1/rm32/ECX . . . . . 8/imm32 # add to ECX - 89/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy ECX to *EAX - # . *(EAX+4) = EAX + n - 89/copy 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # copy EAX to ECX - 03/add 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 0xc/disp8 . # add *(EBP+12) to ECX - 89/copy 1/mod/*+disp8 0/rm32/EAX . . . 1/r32/ECX 4/disp8 . # copy ECX to *(EAX+4) - # . restore registers - 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 - -# We could create a more general '$abort' jump target, but then we'd need to do -# a conditional jump followed by loading the error message and an unconditional -# jump. Or we'd need to unconditionally load the error message before a -# conditional jump, even if it's unused the vast majority of the time. This way -# we bloat a potentially cold segment in RAM so we can abort with a single -# instruction. -$allocate-region:abort: - # . _write(2/stderr, error) - # . . push args - 68/push "allocate-region: failed to allocate"/imm32 - 68/push 2/imm32/stderr - # . . call - e8/call _write/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # . syscall(exit, 1) - bb/copy-to-EBX 1/imm32 - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - # never gets here - -# . . vim:nowrap:textwidth=0 diff --git a/subx/068error-byte.subx b/subx/068error-byte.subx new file mode 100644 index 00000000..5f9a4ada --- /dev/null +++ b/subx/068error-byte.subx @@ -0,0 +1,112 @@ +# Print an error message followed by the text representation of a byte. Then exit. + +== code +# instruction effective address register 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 +#? # . var ed/EAX : exit-descriptor +#? 81 5/subop/subtract 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # subtract from ESP +#? 89/copy 3/mod/direct 0/rm32/EAX . . . 4/r32/ESP . . # copy ESP to EAX +#? # . configure ed to really exit() +#? # . . ed->target = 0 +#? c7 0/subop/copy 0/mod/direct 0/rm32/EAX . . . . . 0/imm32 # copy to *EAX +#? # . error-byte(ed, Stdout, msg, 34) +#? 68/push 0x34/imm32 +#? 68/push "abc"/imm32 +#? 68/push Stderr/imm32 +#? 50/push-EAX +#? e8/call error-byte/disp32 + # automatic test + 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +# write(out, "Error: "+msg+": "+byte) then stop(ed, 1) +error-byte: # ed : (address exit-descriptor), out : (address buffered-file), msg : (address array byte), n : byte -> + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # write-buffered(out, "Error: ") + # . . push args + 68/push "Error: "/imm32 + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + # . . call + e8/call write-buffered/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # write-buffered(out, msg) + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x10/disp8 . # push *(EBP+16) + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + # . . call + e8/call write-buffered/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # write-buffered(out, ": ") + # . . push args + 68/push ": "/imm32 + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + # . . call + e8/call write-buffered/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # print-byte(out, byte) + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x14/disp8 . # push *(EBP+20) + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + # . . call + e8/call print-byte/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # write-buffered(out, Newline) + # . . push args + 68/push Newline/imm32 + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + # . . call + e8/call write-buffered/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # . flush(out) + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # stop(ed, 1) + # . . push args + 68/push 1/imm32 + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call stop/disp32 + # should never get past this point +$error-byte:dead-end: + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +== data + +# The buffered file for standard error. +Stderr: + # file descriptor or (address stream) + 02 00 00 00 # 1 = standard error + # 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. But +# I don't want to type in 1024 bytes here. + +# . . vim:nowrap:textwidth=0 diff --git a/subx/069allocate.subx b/subx/069allocate.subx new file mode 100644 index 00000000..37b01d21 --- /dev/null +++ b/subx/069allocate.subx @@ -0,0 +1,207 @@ +# Helper to dynamically allocate memory on the heap. +# +# We'd like to be able to write tests for functions that allocate memory, +# making assertions on the precise addresses used. To achieve this we'll pass +# in an *allocation descriptor* to allocate from. +# +# Allocation descriptors are also useful outside of tests. Assembly and machine +# code are of necessity unsafe languages, and one of the most insidious kinds +# of bugs unsafe languages expose us to are dangling pointers to memory that +# has been freed and potentially even reused for something totally different. +# To reduce the odds of such "use after free" errors, SubX programs tend to not +# reclaim and reuse dynamically allocated memory. (Running out of memory is far +# easier to debug.) Long-running programs that want to reuse memory are mostly +# on their own to be careful. However, they do get one bit of help: they can +# carve out chunks of memory and then allocate from them manually using this +# very same 'allocate' helper. They just need a new allocation descriptor for +# their book-keeping. + +== data + +# The 'global' allocation descriptor. Pass this into 'allocate' to claim a +# hitherto unused bit of memory. +Heap: + Start-of-heap/imm32 # curr + 00 00 00 0b # limit = 0x0b000000; keep sync'd with DATA_SEGMENT + SEGMENT_ALIGNMENT + +== code +# instruction effective address register 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: + 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +# Claim the next 'n' bytes of memory starting at ad->curr and update ad->curr. +# If there isn't enough memory before ad->limit, return 0 and leave 'ad' unmodified. +allocate: # ad : (address allocation-descriptor), n : int -> address-or-null/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 + 52/push-EDX + # ECX = ad + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 8/disp8 . # copy *(EBP+8) to ECX + # save ad->curr + 8b/copy 0/mod/indirect 1/rm32/ECX . . . 0/r32/EAX . . # copy *ECX to EAX + # check if there's enough space + # . EDX = ad->curr + n + 89/copy 3/mod/direct 2/rm32/EDX . . . 0/r32/EAX . . # copy EAX to EDX + 03/add 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0xc/disp8 . # add *(EBP+12) to EDX + 3b/compare 1/mod/*+disp8 1/rm32/ECX . . . 2/r32/EDX 4/disp8 . # compare EDX with *(ECX+4) + 7c/jump-if-lesser $allocate:commit/disp8 + # return null if not + b8/copy-to-EAX 0/imm32 + eb/jump $allocate:end/disp8 +$allocate:commit: + # update ad->curr + 89/copy 0/mod/indirect 1/rm32/ECX . . . 2/r32/EDX . . # copy EDX to *ECX +$allocate:end: + # . restore registers + 5a/pop-to-EDX + 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 + +test-allocate-success: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var ad/ECX : (address allocation-descriptor) = {11, 15} + 68/push 0xf/imm32/limit + 68/push 0xb/imm32/curr + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = allocate(ad, 3) + # . . push args + 68/push 3/imm32 + 51/push-ECX + # . . call + e8/call allocate/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, 11, msg) + # . . push args + 68/push "F - test-allocate-success: returns current pointer of allocation descriptor"/imm32 + 68/push 0xb/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 + # check-ints-equal(ad->curr, 14, msg) + # . . push args + 68/push "F - test-allocate-success: updates allocation descriptor"/imm32 + 68/push 0xe/imm32 + ff 6/subop/push 0/mod/indirect 1/rm32/ECX . . . . . . # push *ECX + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-allocate-failure: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var ad/ECX : (address allocation-descriptor) = {11, 15} + 68/push 0xf/imm32/limit + 68/push 0xb/imm32/curr + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = allocate(ad, 6) + # . . push args + 68/push 6/imm32 + 51/push-ECX + # . . call + e8/call allocate/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-allocate-failure: returns null"/imm32 + 68/push 0/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 + # no change to ad->curr + # . check-ints-equal(ad->curr, 11) + # . . push args + 68/push "F - test-allocate-failure: updates allocation descriptor"/imm32 + 68/push 0xb/imm32 + ff 6/subop/push 0/mod/indirect 1/rm32/ECX . . . . . . # push *ECX + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +# helper: create a nested allocation descriptor (useful for tests) +allocate-region: # ad : (address allocation-descriptor), n : int -> new-ad : (address allocation-descriptor) + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # . save registers + 51/push-ECX + # EAX = allocate(ad, n) + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call allocate/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # if EAX == 0 abort + 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0/imm32 # compare EAX + 74/jump-if-equal $allocate-region:abort/disp8 + # earmark 8 bytes at the start for a new allocation descriptor + # . *EAX = EAX + 8 + 89/copy 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # copy EAX to ECX + 81 0/subop/add 3/mod/direct 1/rm32/ECX . . . . . 8/imm32 # add to ECX + 89/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy ECX to *EAX + # . *(EAX+4) = EAX + n + 89/copy 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # copy EAX to ECX + 03/add 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 0xc/disp8 . # add *(EBP+12) to ECX + 89/copy 1/mod/*+disp8 0/rm32/EAX . . . 1/r32/ECX 4/disp8 . # copy ECX to *(EAX+4) + # . restore registers + 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 + +# We could create a more general '$abort' jump target, but then we'd need to do +# a conditional jump followed by loading the error message and an unconditional +# jump. Or we'd need to unconditionally load the error message before a +# conditional jump, even if it's unused the vast majority of the time. This way +# we bloat a potentially cold segment in RAM so we can abort with a single +# instruction. +$allocate-region:abort: + # . _write(2/stderr, error) + # . . push args + 68/push "allocate-region: failed to allocate"/imm32 + 68/push 2/imm32/stderr + # . . call + e8/call _write/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # . syscall(exit, 1) + bb/copy-to-EBX 1/imm32 + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + # never gets here + +# . . vim:nowrap:textwidth=0 diff --git a/subx/069new-stream.subx b/subx/069new-stream.subx deleted file mode 100644 index e6f2e0cb..00000000 --- a/subx/069new-stream.subx +++ /dev/null @@ -1,124 +0,0 @@ -# Helper to allocate a stream on the heap. - -== code -# instruction effective address register 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: - 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -new-stream: # ad : (address allocation-descriptor), length : int, elemsize : int -> address/EAX - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # . save registers - 52/push-EDX - # n = elemsize * length + 12 (for read, write and length) - # . EAX = elemsize - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 0x10/disp8 . # copy *(EBP+16) to EAX - # . EAX *= length - 31/xor 3/mod/direct 2/rm32/EDX . . . 2/r32/EDX . . # clear EDX - f7 4/subop/multiply 1/mod/*+disp8 5/rm32/EBP . . 0xc/disp8 . # multiply *(EBP+12) into EAX - # . if overflow abort - 81 7/subop/compare 3/mod/direct 2/rm32/EDX . . . . . 0/imm32 # compare EDX - 75/jump-if-not-equal $new-stream:abort/disp8 - # . EDX = elemsize*length - 89/copy 3/mod/direct 2/rm32/EDX . . . 0/r32/EAX . . # copy EAX to EDX - # . EAX += 12 - 05/add-to-EAX 0xc/imm32 - # allocate(ad, n) - # . . push args - 50/push-EAX - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) - # . . call - e8/call allocate/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # EAX->length = elemsize*length - 89/copy 1/mod/*+disp8 0/rm32/EAX . . . 2/r32/EDX 8/disp8 . # copy EDX to *(EAX+8) - # clear-stream(EAX) - # . . push args - 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 -$new-stream:end: - # . restore registers - 5a/pop-to-EDX - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -$new-stream:abort: - # . _write(2/stderr, error) - # . . push args - 68/push "new-stream: size too large"/imm32 - 68/push 2/imm32/stderr - # . . call - e8/call _write/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # . syscall(exit, 1) - bb/copy-to-EBX 1/imm32 - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - # never gets here - -test-new-stream: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var ad/ECX : (address allocation-descriptor) = allocate-region(Heap, 512) - # . EAX = allocate-region(Heap, 512) - # . . push args - 68/push 0x200/imm32 - 68/push Heap/imm32 - # . . call - e8/call allocate-region/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # . ECX = EAX - 89/copy 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # copy EAX to ECX - # var start/EDX = ad->curr - 8b/copy 0/mod/indirect 1/rm32/ECX . . . 2/r32/EDX . . # copy *ECX to EDX - # EAX = new-stream(ad, 3, 2) - # . . push args - 68/push 2/imm32 - 68/push 3/imm32 - 51/push-ECX - # . . call - e8/call new-stream/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # check-ints-equal(EAX, EDX, msg) - # . . push args - 68/push "F - test-new-stream: returns current pointer of allocation descriptor"/imm32 - 52/push-EDX - 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 - # check-ints-equal(EAX->length, 6, msg) - # . . push args - 68/push "F - test-new-stream: sets length correctly"/imm32 - 68/push 6/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 - # the rest is delegated to clear-stream() so we won't bother checking it - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -# . . vim:nowrap:textwidth=0 diff --git a/subx/070new-stream.subx b/subx/070new-stream.subx new file mode 100644 index 00000000..e6f2e0cb --- /dev/null +++ b/subx/070new-stream.subx @@ -0,0 +1,124 @@ +# Helper to allocate a stream on the heap. + +== code +# instruction effective address register 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: + 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +new-stream: # ad : (address allocation-descriptor), length : int, elemsize : int -> address/EAX + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # . save registers + 52/push-EDX + # n = elemsize * length + 12 (for read, write and length) + # . EAX = elemsize + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 0x10/disp8 . # copy *(EBP+16) to EAX + # . EAX *= length + 31/xor 3/mod/direct 2/rm32/EDX . . . 2/r32/EDX . . # clear EDX + f7 4/subop/multiply 1/mod/*+disp8 5/rm32/EBP . . 0xc/disp8 . # multiply *(EBP+12) into EAX + # . if overflow abort + 81 7/subop/compare 3/mod/direct 2/rm32/EDX . . . . . 0/imm32 # compare EDX + 75/jump-if-not-equal $new-stream:abort/disp8 + # . EDX = elemsize*length + 89/copy 3/mod/direct 2/rm32/EDX . . . 0/r32/EAX . . # copy EAX to EDX + # . EAX += 12 + 05/add-to-EAX 0xc/imm32 + # allocate(ad, n) + # . . push args + 50/push-EAX + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call allocate/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # EAX->length = elemsize*length + 89/copy 1/mod/*+disp8 0/rm32/EAX . . . 2/r32/EDX 8/disp8 . # copy EDX to *(EAX+8) + # clear-stream(EAX) + # . . push args + 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 +$new-stream:end: + # . restore registers + 5a/pop-to-EDX + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +$new-stream:abort: + # . _write(2/stderr, error) + # . . push args + 68/push "new-stream: size too large"/imm32 + 68/push 2/imm32/stderr + # . . call + e8/call _write/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # . syscall(exit, 1) + bb/copy-to-EBX 1/imm32 + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + # never gets here + +test-new-stream: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var ad/ECX : (address allocation-descriptor) = allocate-region(Heap, 512) + # . EAX = allocate-region(Heap, 512) + # . . push args + 68/push 0x200/imm32 + 68/push Heap/imm32 + # . . call + e8/call allocate-region/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # . ECX = EAX + 89/copy 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # copy EAX to ECX + # var start/EDX = ad->curr + 8b/copy 0/mod/indirect 1/rm32/ECX . . . 2/r32/EDX . . # copy *ECX to EDX + # EAX = new-stream(ad, 3, 2) + # . . push args + 68/push 2/imm32 + 68/push 3/imm32 + 51/push-ECX + # . . call + e8/call new-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # check-ints-equal(EAX, EDX, msg) + # . . push args + 68/push "F - test-new-stream: returns current pointer of allocation descriptor"/imm32 + 52/push-EDX + 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 + # check-ints-equal(EAX->length, 6, msg) + # . . push args + 68/push "F - test-new-stream: sets length correctly"/imm32 + 68/push 6/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 + # the rest is delegated to clear-stream() so we won't bother checking it + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +# . . vim:nowrap:textwidth=0 diff --git a/subx/070read-line.subx b/subx/070read-line.subx deleted file mode 100644 index 961fc521..00000000 --- a/subx/070read-line.subx +++ /dev/null @@ -1,312 +0,0 @@ -== code -# instruction effective address register 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: - 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -# read bytes from 'f' until (and including) a newline and store them into 's' -# return true if no data found, false otherwise -# just abort if 's' is too small -read-line: # f : (address buffered-file), s : (address stream byte) -> eof?/EAX - # pseudocode: - # loop: - # if (s->write >= s->length) abort - # if (f->read >= f->write) populate stream from file - # if (f->write == 0) break - # AL = f->data[f->read] - # s->data[s->write] = AL - # ++f->read - # ++s->write - # if AL == '\n' break - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # . save registers - 51/push-ECX - 52/push-EDX - 56/push-ESI - 57/push-EDI - # ESI = f - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 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 - # EDI = s - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 7/r32/EDI 0xc/disp8 . # copy *(EBP+12) to EDI - # EDX = s->write - 8b/copy 0/mod/indirect 7/rm32/EDI . . . 2/r32/EDX . . # copy *EDI to EDX -$read-line:loop: - # if (s->write >= s->length) abort - 3b/compare 1/mod/*+disp8 7/rm32/EDI . . . 2/r32/EDX 8/disp8 . # compare EDX with *(EDI+8) - 7d/jump-if-greater-or-equal $read-line:abort/disp8 - # if (f->read >= f->write) populate stream from file - 3b/compare 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # compare ECX with *(ESI+4) - 7c/jump-if-lesser $read-line: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 - # . f->read must now be 0; update its cache at ECX - 31/xor 3/mod/direct 1/rm32/ECX . . . 1/r32/ECX . . # clear ECX - # . 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 f->write == 0 return true - # . if EAX == 0 return true - 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0/imm32 # compare EAX - 75/jump-if-not-equal $read-line:from-stream/disp8 - b8/copy-to-EAX 0xffffffff/imm32 - eb/jump $read-line:end/disp8 -$read-line: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 byte at *(ESI+ECX+16) to AL - # s->data[s->write] = AL - 88/copy-byte 1/mod/*+disp8 4/rm32/sib 7/base/EDI 2/index/EDX . 0/r32/AL 0xc/disp8 . # copy AL to *(EDI+EDX+12) - # ++f->read - 41/increment-ECX - # ++s->write - 42/increment-EDX - # if AL == '\n' return false - 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0xa/imm32 # compare EAX - 75/jump-if-not-equal $read-line:loop/disp8 - 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX -$read-line:end: - # save f->read - 89/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 8/disp8 . # copy ECX to *(ESI+8) - # save s->write - 89/copy 0/mod/indirect 7/rm32/EDI . . . 2/r32/EDX . . # copy EDX to *EDI - # . restore registers - 5f/pop-to-EDI - 5e/pop-to-ESI - 5a/pop-to-EDX - 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 - -$read-line:abort: - # . _write(2/stderr, error) - # . . push args - 68/push "read-line: line too long"/imm32 - 68/push 2/imm32/stderr - # . . call - e8/call _write/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # . syscall(exit, 1) - bb/copy-to-EBX 1/imm32 - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - # never gets here - -test-read-line: - # - check that read-line stops at a newline - # setup - # . 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 - # . clear-stream(_test-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - # . . 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\ncd") - # . 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 - # . write(_test-stream, "\n") - # . . push args - 68/push Newline/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 - # . write(_test-stream, "cd") - # . . push args - 68/push "cd"/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 a line from _test-stream (buffered by _test-buffered-file) into _test-stream-buffer - # . EAX = read-line(_test-buffered-file, _test-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - 68/push _test-buffered-file/imm32 - # . . call - e8/call read-line/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 0/not-at-eof, msg) - # . . push args - 68/push "F - test-read-line: return value"/imm32 - 68/push 0/imm32/not-at-eof - 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 - # check-ints-equal(*_test-stream-buffer->data, 61/a 62/b 0a/newline 00, msg) - # . . push args - 68/push "F - test-read-line"/imm32 - 68/push 0x000a6261/imm32 - # . . push *_test-stream->data - b8/copy-to-EAX _test-stream-buffer/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) - # . . 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-line-returns-true-on-eof: - # setup - # . 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 - # . clear-stream(_test-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - # . . call - e8/call clear-stream/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # write nothing - # EAX = read-line(_test-buffered-file, _test-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - 68/push _test-buffered-file/imm32 - # . . call - e8/call read-line/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, eof, msg) - # . . push args - 68/push "F - test-read-line-returns-true-on-eof"/imm32 - 68/push 0xffffffff/imm32/not-at-eof - 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-line-reads-final-line-until-eof: - # setup - # . 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 - # . clear-stream(_test-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - # . . 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, "cd") - # . . push args - 68/push "cd"/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 a line from _test-stream (buffered by _test-buffered-file) into _test-stream-buffer - # . EAX = read-line(_test-buffered-file, _test-stream-buffer) - # . . push args - 68/push _test-stream-buffer/imm32 - 68/push _test-buffered-file/imm32 - # . . call - e8/call read-line/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, eof, msg) - # . . push args - 68/push "F - test-read-line-reads-final-line-until-eof: return value"/imm32 - 68/push 0xffffffff/imm32/not-at-eof - 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 - # check-ints-equal(*_test-stream-buffer->data, 63/c 64/d 00 00, msg) - # . . push args - 68/push "F - test-read-line-reads-final-line-until-eof"/imm32 - 68/push 0x00006463/imm32 - # . . push *_test-stream->data - b8/copy-to-EAX _test-stream-buffer/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) - # . . 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 - -# . . vim:nowrap:textwidth=0 diff --git a/subx/071read-line.subx b/subx/071read-line.subx new file mode 100644 index 00000000..961fc521 --- /dev/null +++ b/subx/071read-line.subx @@ -0,0 +1,312 @@ +== code +# instruction effective address register 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: + 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +# read bytes from 'f' until (and including) a newline and store them into 's' +# return true if no data found, false otherwise +# just abort if 's' is too small +read-line: # f : (address buffered-file), s : (address stream byte) -> eof?/EAX + # pseudocode: + # loop: + # if (s->write >= s->length) abort + # if (f->read >= f->write) populate stream from file + # if (f->write == 0) break + # AL = f->data[f->read] + # s->data[s->write] = AL + # ++f->read + # ++s->write + # if AL == '\n' break + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # . save registers + 51/push-ECX + 52/push-EDX + 56/push-ESI + 57/push-EDI + # ESI = f + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 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 + # EDI = s + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 7/r32/EDI 0xc/disp8 . # copy *(EBP+12) to EDI + # EDX = s->write + 8b/copy 0/mod/indirect 7/rm32/EDI . . . 2/r32/EDX . . # copy *EDI to EDX +$read-line:loop: + # if (s->write >= s->length) abort + 3b/compare 1/mod/*+disp8 7/rm32/EDI . . . 2/r32/EDX 8/disp8 . # compare EDX with *(EDI+8) + 7d/jump-if-greater-or-equal $read-line:abort/disp8 + # if (f->read >= f->write) populate stream from file + 3b/compare 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # compare ECX with *(ESI+4) + 7c/jump-if-lesser $read-line: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 + # . f->read must now be 0; update its cache at ECX + 31/xor 3/mod/direct 1/rm32/ECX . . . 1/r32/ECX . . # clear ECX + # . 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 f->write == 0 return true + # . if EAX == 0 return true + 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0/imm32 # compare EAX + 75/jump-if-not-equal $read-line:from-stream/disp8 + b8/copy-to-EAX 0xffffffff/imm32 + eb/jump $read-line:end/disp8 +$read-line: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 byte at *(ESI+ECX+16) to AL + # s->data[s->write] = AL + 88/copy-byte 1/mod/*+disp8 4/rm32/sib 7/base/EDI 2/index/EDX . 0/r32/AL 0xc/disp8 . # copy AL to *(EDI+EDX+12) + # ++f->read + 41/increment-ECX + # ++s->write + 42/increment-EDX + # if AL == '\n' return false + 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0xa/imm32 # compare EAX + 75/jump-if-not-equal $read-line:loop/disp8 + 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX +$read-line:end: + # save f->read + 89/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 8/disp8 . # copy ECX to *(ESI+8) + # save s->write + 89/copy 0/mod/indirect 7/rm32/EDI . . . 2/r32/EDX . . # copy EDX to *EDI + # . restore registers + 5f/pop-to-EDI + 5e/pop-to-ESI + 5a/pop-to-EDX + 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 + +$read-line:abort: + # . _write(2/stderr, error) + # . . push args + 68/push "read-line: line too long"/imm32 + 68/push 2/imm32/stderr + # . . call + e8/call _write/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # . syscall(exit, 1) + bb/copy-to-EBX 1/imm32 + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + # never gets here + +test-read-line: + # - check that read-line stops at a newline + # setup + # . 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 + # . clear-stream(_test-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + # . . 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\ncd") + # . 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 + # . write(_test-stream, "\n") + # . . push args + 68/push Newline/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 + # . write(_test-stream, "cd") + # . . push args + 68/push "cd"/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 a line from _test-stream (buffered by _test-buffered-file) into _test-stream-buffer + # . EAX = read-line(_test-buffered-file, _test-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + 68/push _test-buffered-file/imm32 + # . . call + e8/call read-line/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, 0/not-at-eof, msg) + # . . push args + 68/push "F - test-read-line: return value"/imm32 + 68/push 0/imm32/not-at-eof + 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 + # check-ints-equal(*_test-stream-buffer->data, 61/a 62/b 0a/newline 00, msg) + # . . push args + 68/push "F - test-read-line"/imm32 + 68/push 0x000a6261/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream-buffer/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . 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-line-returns-true-on-eof: + # setup + # . 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 + # . clear-stream(_test-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + # . . call + e8/call clear-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # write nothing + # EAX = read-line(_test-buffered-file, _test-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + 68/push _test-buffered-file/imm32 + # . . call + e8/call read-line/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, eof, msg) + # . . push args + 68/push "F - test-read-line-returns-true-on-eof"/imm32 + 68/push 0xffffffff/imm32/not-at-eof + 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-line-reads-final-line-until-eof: + # setup + # . 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 + # . clear-stream(_test-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + # . . 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, "cd") + # . . push args + 68/push "cd"/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 a line from _test-stream (buffered by _test-buffered-file) into _test-stream-buffer + # . EAX = read-line(_test-buffered-file, _test-stream-buffer) + # . . push args + 68/push _test-stream-buffer/imm32 + 68/push _test-buffered-file/imm32 + # . . call + e8/call read-line/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, eof, msg) + # . . push args + 68/push "F - test-read-line-reads-final-line-until-eof: return value"/imm32 + 68/push 0xffffffff/imm32/not-at-eof + 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 + # check-ints-equal(*_test-stream-buffer->data, 63/c 64/d 00 00, msg) + # . . push args + 68/push "F - test-read-line-reads-final-line-until-eof"/imm32 + 68/push 0x00006463/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream-buffer/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . 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 + +# . . vim:nowrap:textwidth=0 diff --git a/subx/071slice.subx b/subx/071slice.subx deleted file mode 100644 index 82099bc5..00000000 --- a/subx/071slice.subx +++ /dev/null @@ -1,520 +0,0 @@ -# new data structure: a slice is an open interval of addresses [start, end) -# that includes 'start' but not 'end' - -== code -# instruction effective address register 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: - 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -slice-empty?: # s : (address slice) -> EAX : boolean - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # . save registers - 51/push-ECX - # ECX = s - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 8/disp8 . # copy *(EBP+8) to ECX - # if s->start == s->end return true - # . EAX = s->start - 8b/copy 0/mod/indirect 1/rm32/ECX . . . 0/r32/EAX . . # copy *ECX to EAX - # . compare EAX with s->end - 39/compare 1/mod/*+disp8 1/rm32/ECX . . . 0/r32/EAX 4/disp8 . # compare EAX and *(ECX+4) - b8/copy-to-EAX 1/imm32/true - 74/jump-if-equal $slice-empty?:end/disp8 - b8/copy-to-EAX 0/imm32/false -$slice-empty?:end: - # . restore registers - 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 - -test-slice-empty-true: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = {34, 34} - 68/push 34/imm32/end - 68/push 34/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # slice-empty?(slice) - # . . push args - 51/push-ECX - # . . call - e8/call slice-empty?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 1, msg) - # . . push args - 68/push "F - test-slice-empty-true"/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-slice-empty-false: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX = {34, 23} - 68/push 23/imm32/end - 68/push 34/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # slice-empty?(slice) - # . . push args - 51/push-ECX - # . . call - e8/call slice-empty?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-slice-empty-false"/imm32 - 68/push 0/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -slice-equal?: # s : (address slice), p : (address string) -> EAX : boolean - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # . save registers - 51/push-ECX - 52/push-EDX - 53/push-EBX - 56/push-ESI - # ESI = s - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI - # curr/EDX = s->start - 8b/copy 0/mod/indirect 6/rm32/ESI . . . 2/r32/EDX . . # copy *ESI to EDX - # max/ESI = s->end - 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 6/r32/ESI 4/disp8 . # copy *(ESI+4) to ESI - # EBX = p - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 3/r32/EBX 0xc/disp8 . # copy *(EBP+12) to EBX - # EAX = s->end - s->start - 89/copy 3/mod/direct 0/rm32/EAX . . . 6/r32/ESI . . # copy ESI to EAX - 29/subtract 3/mod/direct 0/rm32/EAX . . . 2/r32/EDX . . # subtract EDX from EAX - # if (EAX != p->length) return false; - 39/compare 0/mod/indirect 3/rm32/EBX . . . 0/r32/EAX . . # compare *EBX and EAX - 75/jump-if-not-equal $slice-equal?:false/disp8 - # skip p->length - 81 0/subop/add 3/mod/direct 3/rm32/EBX . . . . . 4/imm32 # add to EBX - # EAX = ECX = false - 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX - 31/xor 3/mod/direct 1/rm32/ECX . . . 1/r32/ECX . . # clear ECX -$slice-equal?:loop: - # if (curr >= max) return true - 39/compare 3/mod/direct 2/rm32/EDX . . . 6/r32/ESI . . # compare EDX and ESI - 7d/jump-if-greater-or-equal $slice-equal?:true/disp8 - # AL = *p - 8a/copy-byte 0/mod/indirect 3/rm32/EBX . . . 0/r32/AL . . # copy byte at *EBX to AL - # CL = *curr - 8a/copy-byte 0/mod/indirect 2/rm32/EDX . . . 1/r32/CL . . # copy byte at *EDX to CL - # if (EAX != ECX) return false - 39/compare 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # compare EAX and ECX - 75/jump-if-not-equal $slice-equal?:false/disp8 - # ++p - 43/increment-EBX - # ++curr - 42/increment-EDX - eb/jump $slice-equal?:loop/disp8 -$slice-equal?:false: - b8/copy-to-EAX 0/imm32 - eb/jump $slice-equal?:end/disp8 -$slice-equal?:true: - b8/copy-to-EAX 1/imm32 -$slice-equal?:end: - # . restore registers - 5e/pop-to-ESI - 5b/pop-to-EBX - 5a/pop-to-EDX - 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 - -test-slice-equal: - # - slice-equal?(slice("Abc"), "Abc") == 1 - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX - 68/push _test-slice-data-3/imm32/end - 68/push _test-slice-data-0/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = slice-equal?(ECX, "Abc") - # . . push args - 68/push "Abc"/imm32 - 51/push-ECX - # . . call - e8/call slice-equal?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 1, msg) - # . . push args - 68/push "F - test-slice-equal"/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-slice-equal-false: - # - slice-equal?(slice("bcd"), "Abc") == 0 - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX - 68/push _test-slice-data-4/imm32/end - 68/push _test-slice-data-1/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = slice-equal?(ECX, "Abc") - # . . push args - 68/push "Abc"/imm32 - 51/push-ECX - # . . call - e8/call slice-equal?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-slice-equal-false"/imm32 - 68/push 0/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-slice-equal-too-long: - # - slice-equal?(slice("Abcd"), "Abc") == 0 - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX - 68/push _test-slice-data-4/imm32/end - 68/push _test-slice-data-0/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = slice-equal?(ECX, "Abc") - # . . push args - 68/push "Abc"/imm32 - 51/push-ECX - # . . call - e8/call slice-equal?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-slice-equal-too-long"/imm32 - 68/push 0/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-slice-equal-too-short: - # - slice-equal?(slice("A"), "Abc") == 0 - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX - 68/push _test-slice-data-1/imm32/end - 68/push _test-slice-data-0/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = slice-equal?(ECX, "Abc") - # . . push args - 68/push "Abc"/imm32 - 51/push-ECX - # . . call - e8/call slice-equal?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-slice-equal-too-short"/imm32 - 68/push 0/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-slice-equal-empty: - # - slice-equal?(slice(""), "Abc") == 0 - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX - 68/push _test-slice-data-0/imm32/end - 68/push _test-slice-data-0/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = slice-equal?(ECX, "Abc") - # . . push args - 68/push "Abc"/imm32 - 51/push-ECX - # . . call - e8/call slice-equal?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-slice-equal-empty"/imm32 - 68/push 0/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-slice-equal-with-empty: - # - slice-equal?(slice("Ab"), "") == 0 - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX - 68/push _test-slice-data-2/imm32/end - 68/push _test-slice-data-0/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = slice-equal?(ECX, "") - # . . push args - 68/push ""/imm32 - 51/push-ECX - # . . call - e8/call slice-equal?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 0, msg) - # . . push args - 68/push "F - test-slice-equal-with-empty"/imm32 - 68/push 0/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-slice-equal-empty-with-empty: - # - slice-equal?(slice(""), "") == 1 - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var slice/ECX - 68/push _test-slice-data-0/imm32/end - 68/push _test-slice-data-0/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # EAX = slice-equal?(ECX, "") - # . . push args - 68/push ""/imm32 - 51/push-ECX - # . . call - e8/call slice-equal?/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 1, msg) - # . . push args - 68/push "F - test-slice-equal-empty-with-empty"/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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -write-slice: # out : (address buffered-file), s : (address slice) - # . 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 - 57/push-EDI - # ESI = s - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 0xc/disp8 . # copy *(EBP+12) to ESI - # curr/ECX = s->start - 8b/copy 0/mod/indirect 6/rm32/ESI . . . 1/r32/ECX . . # copy *ESI to ECX - # max/ESI = s->end - 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 6/r32/ESI 4/disp8 . # copy *(ESI+4) to ESI - # EDI = f - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . 7/r32/EDI 8/disp8 . # copy *(EBP+8) to EDI - # EDX = f->length - 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 2/r32/EDX 0xc/disp8 . # copy *(EDI+12) to EDX - # EBX = f->write - 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 3/r32/EBX 4/disp8 . # copy *(EDI+4) to EBX -$write-slice:loop: - # if (curr >= max) break - 39/compare 3/mod/direct 1/rm32/ECX . . . 6/r32/ESI . . # compare ECX with ESI - 7d/jump-if-greater-or-equal $write-slice:loop-end/disp8 - # if (f->write >= f->length) flush and clear f's stream - 39/compare 3/mod/direct 3/rm32/EBX . . . 2/r32/EDX . . # compare EBX with EDX - 7c/jump-if-lesser $write-slice:to-stream/disp8 - # . persist f->write - 89/copy 1/mod/*+disp8 7/rm32/EDI . . . 3/r32/EBX 4/disp8 . # copy EBX to *(EDI+4) - # . flush(f) - # . . push args - 57/push-EDI - # . . call - e8/call flush/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(stream = f+4) - # . . push args - 8d/copy-address 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 4/disp8 . # copy EDI+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 - # . f->write must now be 0; update its cache at EBX - 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX -$write-slice:to-stream: - # f->data[f->write] = *in - # . AL = *in - 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX - 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 0/r32/AL . . # copy byte at *ECX to AL - # . f->data[f->write] = AL - 88/copy-byte 1/mod/*+disp8 4/rm32/sib 7/base/EDI 3/index/EBX . 0/r32/AL 0x10/disp8 . # copy AL to *(EDI+EBX+16) - # ++f->write - 43/increment-EBX - # ++in - 41/increment-ECX - eb/jump $write-slice:loop/disp8 -$write-slice:loop-end: - # persist necessary variables from registers - 89/copy 1/mod/*+disp8 7/rm32/EDI . . . 3/r32/EBX 4/disp8 . # copy EBX to *(EDI+4) -$write-slice:end: - # . restore registers - 5f/pop-to-EDI - 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 - -test-write-slice: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # setup - # . 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 - # var slice/ECX = "Abc" - 68/push _test-slice-data-3/imm32/end - 68/push _test-slice-data-0/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # write-slice(_test-buffered-file, slice) - # . . push args - 51/push-ECX - 68/push _test-buffered-file/imm32 - # . . call - e8/call write-slice/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # flush(_test-buffered-file) - # . . push args - 68/push _test-buffered-file/imm32 - # . . call - e8/call flush/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # check-ints-equal(*_test-stream->data, "Abc", msg) - # . . push args - 68/push "F - test-write-slice"/imm32 - 68/push 0x636241/imm32 - # . . push *_test-stream->data - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -== data - -_test-slice-data-0: - 41/A -_test-slice-data-1: - 62/b -_test-slice-data-2: - 63/c -_test-slice-data-3: - 64/d -_test-slice-data-4: - -# . _. vim:nowrap:textwidth=0 diff --git a/subx/072next-token.subx b/subx/072next-token.subx deleted file mode 100644 index 41dd94f3..00000000 --- a/subx/072next-token.subx +++ /dev/null @@ -1,849 +0,0 @@ -== code -# instruction effective address register 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: -#? e8/call test-next-token-from-slice/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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX - b8/copy-to-EAX 1/imm32/exit - cd/syscall 0x80/imm8 - -# extract the next run of characters that are different from a given 'delimiter' (skipping multiple delimiters if necessary) -# on eof return an empty interval -next-token: # in : (address stream), delimiter : byte, out : (address slice) - # . 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 - 56/push-ESI - 57/push-EDI - # ESI = in - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI - # EDI = out - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 7/r32/EDI 0x10/disp8 . # copy *(EBP+16) to EDI - # skip-chars-matching(in, delimiter) - # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) - 56/push-ESI - # . . call - e8/call skip-chars-matching/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # out->start = &in->data[in->read] - 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # copy *(ESI+4) to ECX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 1/index/ECX . 0/r32/EAX 0xc/disp8 . # copy ESI+ECX+12 to EAX - 89/copy 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # copy EAX to *EDI - # skip-chars-not-matching(in, delimiter) - # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) - 56/push-ESI - # . . call - e8/call skip-chars-not-matching/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # out->end = &in->data[in->read] - 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # copy *(ESI+4) to ECX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 1/index/ECX . 0/r32/EAX 0xc/disp8 . # copy ESI+ECX+12 to EAX - 89/copy 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 4/disp8 . # copy EAX to *(EDI+4) - # . restore registers - 5f/pop-to-EDI - 5e/pop-to-ESI - 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-next-token: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # setup - # . 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 - # var slice/ECX = {0, 0} - 68/push 0/imm32/end - 68/push 0/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # 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 - # next-token(_test-stream, 0x20/space, slice) - # . . push args - 51/push-ECX - 68/push 0x20/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call next-token/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # check-ints-equal(slice->start - _test-stream->data, 2, msg) - # . check-ints-equal(slice->start - _test-stream, 14, msg) - # . . push args - 68/push "F - test-next-token: start"/imm32 - 68/push 0xe/imm32 - # . . push slice->start - _test-stream - 8b/copy 0/mod/indirect 1/rm32/ECX . . . 0/r32/EAX . . # copy *ECX to EAX - 81 5/subop/subtract 3/mod/direct 0/rm32/EAX . . . . . _test-stream/imm32 # subtract from EAX - 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 - # check-ints-equal(slice->end - _test-stream->data, 4, msg) - # . check-ints-equal(slice->end - _test-stream, 16, msg) - # . . push args - 68/push "F - test-next-token: end"/imm32 - 68/push 0x10/imm32 - # . . push slice->end - _test-stream - 8b/copy 1/mod/*+disp8 1/rm32/ECX . . . 0/r32/EAX 4/disp8 . # copy *(ECX+4) to EAX - 81 5/subop/subtract 3/mod/direct 0/rm32/EAX . . . . . _test-stream/imm32 # subtract from EAX - 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 - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-next-token-eof: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # setup - # . 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 - # var slice/ECX = {0, 0} - 68/push 0/imm32/end - 68/push 0/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # write nothing to _test-stream - # next-token(_test-stream, 0x20/space, slice) - # . . push args - 51/push-ECX - 68/push 0x20/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call next-token/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # check-ints-equal(slice->end, slice->start, msg) - # . . push args - 68/push "F - test-next-token-eof"/imm32 - ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4) - ff 6/subop/push 0/mod/indirect 1/rm32/ECX . . . . . . # push *ECX - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -# extract the next run of characters that are different from a given 'delimiter' (skipping multiple delimiters if necessary) -# on eof return an empty interval -next-token-from-slice: # start : (address byte), end : (address byte), delimiter : byte, out : (address slice) -> - # . 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 - 57/push-EDI - # ECX = end - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 0xc/disp8 . # copy *(EBP+12) to ECX - # EDX = delimiter - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0x10/disp8 . # copy *(EBP+16) to EDX - # EDI = out - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 7/r32/EDI 0x14/disp8 . # copy *(EBP+20) to EDI - # EAX = skip-chars-matching-in-slice(start, end, delimiter) - # . . push args - 52/push-EDX - 51/push-ECX - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) - # . . call - e8/call skip-chars-matching-in-slice/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # out->start = EAX - 89/copy 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # copy EAX to *EDI - # EAX = skip-chars-not-matching-in-slice(EAX, end, delimiter) - # . . push args - 52/push-EDX - 51/push-ECX - 50/push-EAX - # . . call - e8/call skip-chars-not-matching-in-slice/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # out->end = EAX - 89/copy 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 4/disp8 . # copy EAX to *(EDI+4) - # . restore registers - 5f/pop-to-EDI - 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 - -test-next-token-from-slice: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # (EAX..ECX) = " ab" - b8/copy-to-EAX " ab"/imm32 - 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX - 05/add-to-EAX 4/imm32 - # var out/EDI : (address slice) = {0, 0} - 68/push 0/imm32/end - 68/push 0/imm32/start - 89/copy 3/mod/direct 7/rm32/EDI . . . 4/r32/ESP . . # copy ESP to EDI - # next-token-from-slice(EAX, ECX, 0x20/space, out) - # . . push args - 57/push-EDI - 68/push 0x20/imm32 - 51/push-ECX - 50/push-EAX - # . . call - e8/call next-token-from-slice/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP - # out->start should be at the 'a' - # . check-ints-equal(out->start - in->start, 2, msg) - # . . push args - 68/push "F - test-next-token-from-slice: start"/imm32 - 68/push 2/imm32 - # . . push out->start - in->start - 8b/copy 0/mod/indirect 7/rm32/EDI . . . 1/r32/ECX . . # copy *EDI to ECX - 2b/subtract 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # subtract EAX from ECX - 51/push-ECX - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # out->end should be after the 'b' - # check-ints-equal(out->end - in->start, 4, msg) - # . . push args - 68/push "F - test-next-token-from-slice: end"/imm32 - 68/push 4/imm32 - # . . push out->end - in->start - 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 1/r32/ECX 4/disp8 . # copy *(EDI+4) to ECX - 2b/subtract 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # subtract EAX from ECX - 51/push-ECX - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-next-token-from-slice-eof: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # var out/EDI : (address slice) = {0, 0} - 68/push 0/imm32/end - 68/push 0/imm32/start - 89/copy 3/mod/direct 7/rm32/EDI . . . 4/r32/ESP . . # copy ESP to EDI - # next-token-from-slice(0, 0, 0x20/space, out) - # . . push args - 57/push-EDI - 68/push 0x20/imm32 - 68/push 0/imm32 - 68/push 0/imm32 - # . . call - e8/call next-token-from-slice/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP - # out should be empty - # . check-ints-equal(out->end - out->start, 0, msg) - # . . push args - 68/push "F - test-next-token-from-slice-eof"/imm32 - 68/push 0/imm32 - # . . push out->start - in->start - 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 1/r32/ECX 4/disp8 . # copy *(EDI+4) to ECX - 2b/subtract 0/mod/indirect 7/rm32/EDI . . . 1/r32/ECX . . # subtract *EDI from ECX - 51/push-ECX - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -test-next-token-from-slice-nothing: - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # (EAX..ECX) = " " - b8/copy-to-EAX " "/imm32 - 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX - 05/add-to-EAX 4/imm32 - # var out/EDI : (address slice) = {0, 0} - 68/push 0/imm32/end - 68/push 0/imm32/start - 89/copy 3/mod/direct 7/rm32/EDI . . . 4/r32/ESP . . # copy ESP to EDI - # next-token-from-slice(in, 0x20/space, out) - # . . push args - 57/push-EDI - 68/push 0x20/imm32 - 51/push-ECX - 50/push-EAX - # . . call - e8/call next-token-from-slice/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP - # out should be empty - # . check-ints-equal(out->end - out->start, 0, msg) - # . . push args - 68/push "F - test-next-token-from-slice-eof"/imm32 - 68/push 0/imm32 - # . . push out->start - in->start - 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 1/r32/ECX 4/disp8 . # copy *(EDI+4) to ECX - 2b/subtract 0/mod/indirect 7/rm32/EDI . . . 1/r32/ECX . . # subtract *EDI from ECX - 51/push-ECX - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -skip-chars-matching: # in : (address stream), delimiter : 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 - 56/push-ESI - # ESI = in - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI - # ECX = in->read - 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # copy *(ESI+4) to ECX - # EBX = in->write - 8b/copy 0/mod/indirect 6/rm32/ESI . . . 3/r32/EBX . . # copy *ESI to EBX - # EDX = delimiter - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0xc/disp8 . # copy *(EBP+12) to EDX -$skip-chars-matching:loop: - # if (in->read >= in->write) break - 39/compare 3/mod/direct 1/rm32/ECX . . . 3/r32/EBX . . # compare ECX with EBX - 7d/jump-if-greater-or-equal $skip-chars-matching:end/disp8 - # EAX = in->data[in->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 0xc/disp8 . # copy byte at *(ESI+ECX+12) to AL - # if (EAX != delimiter) break - 39/compare 3/mod/direct 0/rm32/EAX . . . 2/r32/EDX . . # compare EAX and EDX - 75/jump-if-not-equal $skip-chars-matching:end/disp8 - # ++in->read - 41/inc-ECX - eb/jump $skip-chars-matching:loop/disp8 -$skip-chars-matching:end: - # persist in->read - 89/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # copy ECX to *(ESI+4) - # . restore registers - 5e/pop-to-ESI - 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 - -test-skip-chars-matching: - # setup - # . 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 - # 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 - # skip-chars-matching(_test-stream, 0x20/space) - # . . push args - 68/push 0x20/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call skip-chars-matching/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(_test-stream->read, 2, msg) - # . . push args - 68/push "F - test-skip-chars-matching"/imm32 - 68/push 2/imm32 - # . . push *_test-stream->read - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) - # . . 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-skip-chars-matching-none: - # setup - # . 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 - # 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 - # skip-chars-matching(_test-stream, 0x20/space) - # . . push args - 68/push 0x20/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call skip-chars-matching/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(_test-stream->read, 0, msg) - # . . push args - 68/push "F - test-skip-chars-matching-none"/imm32 - 68/push 0/imm32 - # . . push *_test-stream->read - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) - # . . 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 - -# minor fork of 'skip-chars-matching' -skip-chars-not-matching: # in : (address stream), delimiter : 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 - 56/push-ESI - # ESI = in - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI - # ECX = in->read - 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # copy *(ESI+4) to ECX - # EBX = in->write - 8b/copy 0/mod/indirect 6/rm32/ESI . . . 3/r32/EBX . . # copy *ESI to EBX - # EDX = delimiter - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0xc/disp8 . # copy *(EBP+12) to EDX -$skip-chars-not-matching:loop: - # if (in->read >= in->write) break - 39/compare 3/mod/direct 1/rm32/ECX . . . 3/r32/EBX . . # compare ECX with EBX - 7d/jump-if-greater-or-equal $skip-chars-not-matching:end/disp8 - # EAX = in->data[in->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 0xc/disp8 . # copy byte at *(ESI+ECX+12) to AL - # if (EAX == delimiter) break - 39/compare 3/mod/direct 0/rm32/EAX . . . 2/r32/EDX . . # compare EAX and EDX - 74/jump-if-equal $skip-chars-not-matching:end/disp8 - # ++in->read - 41/inc-ECX - eb/jump $skip-chars-not-matching:loop/disp8 -$skip-chars-not-matching:end: - # persist in->read - 89/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # copy ECX to *(ESI+4) - # . restore registers - 5e/pop-to-ESI - 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 - -test-skip-chars-not-matching: - # setup - # . 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 - # 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 - # skip-chars-not-matching(_test-stream, 0x20/space) - # . . push args - 68/push 0x20/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call skip-chars-not-matching/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(_test-stream->read, 2, msg) - # . . push args - 68/push "F - test-skip-chars-not-matching"/imm32 - 68/push 2/imm32 - # . . push *_test-stream->read - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) - # . . 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-skip-chars-not-matching-none: - # setup - # . 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 - # 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 - # skip-chars-not-matching(_test-stream, 0x20/space) - # . . push args - 68/push 0x20/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call skip-chars-not-matching/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(_test-stream->read, 0, msg) - # . . push args - 68/push "F - test-skip-chars-not-matching-none"/imm32 - 68/push 0/imm32 - # . . push *_test-stream->read - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) - # . . 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-skip-chars-not-matching-all: - # setup - # . 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 - # 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 - # skip-chars-not-matching(_test-stream, 0x20/space) - # . . push args - 68/push 0x20/imm32 - 68/push _test-stream/imm32 - # . . call - e8/call skip-chars-not-matching/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(_test-stream->read, 2, msg) - # . . push args - 68/push "F - test-skip-chars-not-matching-all"/imm32 - 68/push 2/imm32 - # . . push *_test-stream->read - b8/copy-to-EAX _test-stream/imm32 - ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) - # . . 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 - -skip-chars-matching-in-slice: # curr : (address byte), end : (address byte), delimiter : byte -> curr/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 - 52/push-EDX - 53/push-EBX - # EAX = curr - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 8/disp8 . # copy *(EBP+8) to EAX - # ECX = end - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 0xc/disp8 . # copy *(EBP+12) to ECX - # EDX = delimiter - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0x10/disp8 . # copy *(EBP+16) to EDX - # EBX = 0 - 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX -$skip-chars-matching-in-slice:loop: - # if (curr >= end) break - 39/compare 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # compare EAX with ECX - 7d/jump-if-greater-or-equal $skip-chars-matching-in-slice:end/disp8 - # if (*curr != delimiter) break - 8a/copy-byte 0/mod/indirect 0/rm32/EAX . . . 3/r32/BL . . # copy byte at *EAX to BL - 39/compare 3/mod/direct 3/rm32/EBX . . . 2/r32/EDX . . # compare EBX and EDX - 75/jump-if-not-equal $skip-chars-matching-in-slice:end/disp8 - # ++in->read - 40/inc-EAX - eb/jump $skip-chars-matching-in-slice:loop/disp8 -$skip-chars-matching-in-slice:end: - # . restore registers - 5b/pop-to-EBX - 5a/pop-to-EDX - 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 - -test-skip-chars-matching-in-slice: - # (EAX..ECX) = " ab" - b8/copy-to-EAX " ab"/imm32 - 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX - 05/add-to-EAX 4/imm32 - # EAX = skip-chars-matching-in-slice(EAX, ECX, 0x20/space) - # . . push args - 68/push 0x20/imm32 - 51/push-ECX - 50/push-EAX - # . . call - e8/call skip-chars-matching-in-slice/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # check-ints-equal(ECX-EAX, 2, msg) - # . . push args - 68/push "F - test-skip-chars-matching-in-slice"/imm32 - 68/push 2/imm32 - # . . push ECX-EAX - 29/subtract 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # subtract EAX from ECX - 51/push-ECX - # . . 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-skip-chars-matching-in-slice-none: - # (EAX..ECX) = "ab" - b8/copy-to-EAX "ab"/imm32 - 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX - 05/add-to-EAX 4/imm32 - # EAX = skip-chars-matching-in-slice(EAX, ECX, 0x20/space) - # . . push args - 68/push 0x20/imm32 - 51/push-ECX - 50/push-EAX - # . . call - e8/call skip-chars-matching-in-slice/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # check-ints-equal(ECX-EAX, 2, msg) - # . . push args - 68/push "F - test-skip-chars-matching-in-slice-none"/imm32 - 68/push 2/imm32 - # . . push ECX-EAX - 29/subtract 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # subtract EAX from ECX - 51/push-ECX - # . . 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 - -# minor fork of 'skip-chars-matching-in-slice' -skip-chars-not-matching-in-slice: # curr : (address byte), end : (address byte), delimiter : byte -> curr/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 - 52/push-EDX - 53/push-EBX - # EAX = curr - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 8/disp8 . # copy *(EBP+8) to EAX - # ECX = end - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 0xc/disp8 . # copy *(EBP+12) to ECX - # EDX = delimiter - 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0x10/disp8 . # copy *(EBP+16) to EDX - # EBX = 0 - 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX -$skip-chars-not-matching-in-slice:loop: - # if (curr >= end) break - 39/compare 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # compare EAX with ECX - 7d/jump-if-greater-or-equal $skip-chars-not-matching-in-slice:end/disp8 - # if (*curr == delimiter) break - 8a/copy-byte 0/mod/indirect 0/rm32/EAX . . . 3/r32/BL . . # copy byte at *EAX to BL - 39/compare 3/mod/direct 3/rm32/EBX . . . 2/r32/EDX . . # compare EBX and EDX - 74/jump-if-equal $skip-chars-not-matching-in-slice:end/disp8 - # ++in->read - 40/inc-EAX - eb/jump $skip-chars-not-matching-in-slice:loop/disp8 -$skip-chars-not-matching-in-slice:end: - # . restore registers - 5b/pop-to-EBX - 5a/pop-to-EDX - 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 - -test-skip-chars-not-matching-in-slice: - # (EAX..ECX) = "ab " - b8/copy-to-EAX "ab "/imm32 - 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX - 05/add-to-EAX 4/imm32 - # EAX = skip-chars-not-matching-in-slice(EAX, ECX, 0x20/space) - # . . push args - 68/push 0x20/imm32 - 51/push-ECX - 50/push-EAX - # . . call - e8/call skip-chars-not-matching-in-slice/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # check-ints-equal(ECX-EAX, 1, msg) - # . . push args - 68/push "F - test-skip-chars-not-matching-in-slice"/imm32 - 68/push 1/imm32 - # . . push ECX-EAX - 29/subtract 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # subtract EAX from ECX - 51/push-ECX - # . . 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-skip-chars-not-matching-in-slice-none: - # (EAX..ECX) = " ab" - b8/copy-to-EAX " ab"/imm32 - 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX - 05/add-to-EAX 4/imm32 - # EAX = skip-chars-not-matching-in-slice(EAX, ECX, 0x20/space) - # . . push args - 68/push 0x20/imm32 - 51/push-ECX - 50/push-EAX - # . . call - e8/call skip-chars-not-matching-in-slice/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # check-ints-equal(ECX-EAX, 3, msg) - # . . push args - 68/push "F - test-skip-chars-not-matching-in-slice-none"/imm32 - 68/push 3/imm32 - # . . push ECX-EAX - 29/subtract 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # subtract EAX from ECX - 51/push-ECX - # . . 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-skip-chars-not-matching-in-slice-all: - # (EAX..ECX) = "ab" - b8/copy-to-EAX "ab"/imm32 - 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX - 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX - 05/add-to-EAX 4/imm32 - # EAX = skip-chars-not-matching-in-slice(EAX, ECX, 0x20/space) - # . . push args - 68/push 0x20/imm32 - 51/push-ECX - 50/push-EAX - # . . call - e8/call skip-chars-not-matching-in-slice/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # check-ints-equal(ECX-EAX, 0, msg) - # . . push args - 68/push "F - test-skip-chars-not-matching-in-slice-all"/imm32 - 68/push 0/imm32 - # . . push ECX-EAX - 29/subtract 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # subtract EAX from ECX - 51/push-ECX - # . . 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 - -# . . vim:nowrap:textwidth=0 diff --git a/subx/072slice.subx b/subx/072slice.subx new file mode 100644 index 00000000..82099bc5 --- /dev/null +++ b/subx/072slice.subx @@ -0,0 +1,520 @@ +# new data structure: a slice is an open interval of addresses [start, end) +# that includes 'start' but not 'end' + +== code +# instruction effective address register 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: + 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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +slice-empty?: # s : (address slice) -> EAX : boolean + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # . save registers + 51/push-ECX + # ECX = s + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 8/disp8 . # copy *(EBP+8) to ECX + # if s->start == s->end return true + # . EAX = s->start + 8b/copy 0/mod/indirect 1/rm32/ECX . . . 0/r32/EAX . . # copy *ECX to EAX + # . compare EAX with s->end + 39/compare 1/mod/*+disp8 1/rm32/ECX . . . 0/r32/EAX 4/disp8 . # compare EAX and *(ECX+4) + b8/copy-to-EAX 1/imm32/true + 74/jump-if-equal $slice-empty?:end/disp8 + b8/copy-to-EAX 0/imm32/false +$slice-empty?:end: + # . restore registers + 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 + +test-slice-empty-true: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = {34, 34} + 68/push 34/imm32/end + 68/push 34/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # slice-empty?(slice) + # . . push args + 51/push-ECX + # . . call + e8/call slice-empty?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 1, msg) + # . . push args + 68/push "F - test-slice-empty-true"/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-slice-empty-false: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX = {34, 23} + 68/push 23/imm32/end + 68/push 34/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # slice-empty?(slice) + # . . push args + 51/push-ECX + # . . call + e8/call slice-empty?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-slice-empty-false"/imm32 + 68/push 0/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +slice-equal?: # s : (address slice), p : (address string) -> EAX : boolean + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # . save registers + 51/push-ECX + 52/push-EDX + 53/push-EBX + 56/push-ESI + # ESI = s + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI + # curr/EDX = s->start + 8b/copy 0/mod/indirect 6/rm32/ESI . . . 2/r32/EDX . . # copy *ESI to EDX + # max/ESI = s->end + 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 6/r32/ESI 4/disp8 . # copy *(ESI+4) to ESI + # EBX = p + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 3/r32/EBX 0xc/disp8 . # copy *(EBP+12) to EBX + # EAX = s->end - s->start + 89/copy 3/mod/direct 0/rm32/EAX . . . 6/r32/ESI . . # copy ESI to EAX + 29/subtract 3/mod/direct 0/rm32/EAX . . . 2/r32/EDX . . # subtract EDX from EAX + # if (EAX != p->length) return false; + 39/compare 0/mod/indirect 3/rm32/EBX . . . 0/r32/EAX . . # compare *EBX and EAX + 75/jump-if-not-equal $slice-equal?:false/disp8 + # skip p->length + 81 0/subop/add 3/mod/direct 3/rm32/EBX . . . . . 4/imm32 # add to EBX + # EAX = ECX = false + 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX + 31/xor 3/mod/direct 1/rm32/ECX . . . 1/r32/ECX . . # clear ECX +$slice-equal?:loop: + # if (curr >= max) return true + 39/compare 3/mod/direct 2/rm32/EDX . . . 6/r32/ESI . . # compare EDX and ESI + 7d/jump-if-greater-or-equal $slice-equal?:true/disp8 + # AL = *p + 8a/copy-byte 0/mod/indirect 3/rm32/EBX . . . 0/r32/AL . . # copy byte at *EBX to AL + # CL = *curr + 8a/copy-byte 0/mod/indirect 2/rm32/EDX . . . 1/r32/CL . . # copy byte at *EDX to CL + # if (EAX != ECX) return false + 39/compare 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # compare EAX and ECX + 75/jump-if-not-equal $slice-equal?:false/disp8 + # ++p + 43/increment-EBX + # ++curr + 42/increment-EDX + eb/jump $slice-equal?:loop/disp8 +$slice-equal?:false: + b8/copy-to-EAX 0/imm32 + eb/jump $slice-equal?:end/disp8 +$slice-equal?:true: + b8/copy-to-EAX 1/imm32 +$slice-equal?:end: + # . restore registers + 5e/pop-to-ESI + 5b/pop-to-EBX + 5a/pop-to-EDX + 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 + +test-slice-equal: + # - slice-equal?(slice("Abc"), "Abc") == 1 + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX + 68/push _test-slice-data-3/imm32/end + 68/push _test-slice-data-0/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = slice-equal?(ECX, "Abc") + # . . push args + 68/push "Abc"/imm32 + 51/push-ECX + # . . call + e8/call slice-equal?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, 1, msg) + # . . push args + 68/push "F - test-slice-equal"/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-slice-equal-false: + # - slice-equal?(slice("bcd"), "Abc") == 0 + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX + 68/push _test-slice-data-4/imm32/end + 68/push _test-slice-data-1/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = slice-equal?(ECX, "Abc") + # . . push args + 68/push "Abc"/imm32 + 51/push-ECX + # . . call + e8/call slice-equal?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-slice-equal-false"/imm32 + 68/push 0/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-slice-equal-too-long: + # - slice-equal?(slice("Abcd"), "Abc") == 0 + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX + 68/push _test-slice-data-4/imm32/end + 68/push _test-slice-data-0/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = slice-equal?(ECX, "Abc") + # . . push args + 68/push "Abc"/imm32 + 51/push-ECX + # . . call + e8/call slice-equal?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-slice-equal-too-long"/imm32 + 68/push 0/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-slice-equal-too-short: + # - slice-equal?(slice("A"), "Abc") == 0 + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX + 68/push _test-slice-data-1/imm32/end + 68/push _test-slice-data-0/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = slice-equal?(ECX, "Abc") + # . . push args + 68/push "Abc"/imm32 + 51/push-ECX + # . . call + e8/call slice-equal?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-slice-equal-too-short"/imm32 + 68/push 0/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-slice-equal-empty: + # - slice-equal?(slice(""), "Abc") == 0 + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX + 68/push _test-slice-data-0/imm32/end + 68/push _test-slice-data-0/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = slice-equal?(ECX, "Abc") + # . . push args + 68/push "Abc"/imm32 + 51/push-ECX + # . . call + e8/call slice-equal?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-slice-equal-empty"/imm32 + 68/push 0/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-slice-equal-with-empty: + # - slice-equal?(slice("Ab"), "") == 0 + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX + 68/push _test-slice-data-2/imm32/end + 68/push _test-slice-data-0/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = slice-equal?(ECX, "") + # . . push args + 68/push ""/imm32 + 51/push-ECX + # . . call + e8/call slice-equal?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, 0, msg) + # . . push args + 68/push "F - test-slice-equal-with-empty"/imm32 + 68/push 0/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-slice-equal-empty-with-empty: + # - slice-equal?(slice(""), "") == 1 + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var slice/ECX + 68/push _test-slice-data-0/imm32/end + 68/push _test-slice-data-0/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # EAX = slice-equal?(ECX, "") + # . . push args + 68/push ""/imm32 + 51/push-ECX + # . . call + e8/call slice-equal?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(EAX, 1, msg) + # . . push args + 68/push "F - test-slice-equal-empty-with-empty"/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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +write-slice: # out : (address buffered-file), s : (address slice) + # . 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 + 57/push-EDI + # ESI = s + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 0xc/disp8 . # copy *(EBP+12) to ESI + # curr/ECX = s->start + 8b/copy 0/mod/indirect 6/rm32/ESI . . . 1/r32/ECX . . # copy *ESI to ECX + # max/ESI = s->end + 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 6/r32/ESI 4/disp8 . # copy *(ESI+4) to ESI + # EDI = f + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . 7/r32/EDI 8/disp8 . # copy *(EBP+8) to EDI + # EDX = f->length + 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 2/r32/EDX 0xc/disp8 . # copy *(EDI+12) to EDX + # EBX = f->write + 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 3/r32/EBX 4/disp8 . # copy *(EDI+4) to EBX +$write-slice:loop: + # if (curr >= max) break + 39/compare 3/mod/direct 1/rm32/ECX . . . 6/r32/ESI . . # compare ECX with ESI + 7d/jump-if-greater-or-equal $write-slice:loop-end/disp8 + # if (f->write >= f->length) flush and clear f's stream + 39/compare 3/mod/direct 3/rm32/EBX . . . 2/r32/EDX . . # compare EBX with EDX + 7c/jump-if-lesser $write-slice:to-stream/disp8 + # . persist f->write + 89/copy 1/mod/*+disp8 7/rm32/EDI . . . 3/r32/EBX 4/disp8 . # copy EBX to *(EDI+4) + # . flush(f) + # . . push args + 57/push-EDI + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # . clear-stream(stream = f+4) + # . . push args + 8d/copy-address 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 4/disp8 . # copy EDI+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 + # . f->write must now be 0; update its cache at EBX + 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX +$write-slice:to-stream: + # f->data[f->write] = *in + # . AL = *in + 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX + 8a/copy-byte 0/mod/indirect 1/rm32/ECX . . . 0/r32/AL . . # copy byte at *ECX to AL + # . f->data[f->write] = AL + 88/copy-byte 1/mod/*+disp8 4/rm32/sib 7/base/EDI 3/index/EBX . 0/r32/AL 0x10/disp8 . # copy AL to *(EDI+EBX+16) + # ++f->write + 43/increment-EBX + # ++in + 41/increment-ECX + eb/jump $write-slice:loop/disp8 +$write-slice:loop-end: + # persist necessary variables from registers + 89/copy 1/mod/*+disp8 7/rm32/EDI . . . 3/r32/EBX 4/disp8 . # copy EBX to *(EDI+4) +$write-slice:end: + # . restore registers + 5f/pop-to-EDI + 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 + +test-write-slice: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # setup + # . 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 + # var slice/ECX = "Abc" + 68/push _test-slice-data-3/imm32/end + 68/push _test-slice-data-0/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # write-slice(_test-buffered-file, slice) + # . . push args + 51/push-ECX + 68/push _test-buffered-file/imm32 + # . . call + e8/call write-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # flush(_test-buffered-file) + # . . push args + 68/push _test-buffered-file/imm32 + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check-ints-equal(*_test-stream->data, "Abc", msg) + # . . push args + 68/push "F - test-write-slice"/imm32 + 68/push 0x636241/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +== data + +_test-slice-data-0: + 41/A +_test-slice-data-1: + 62/b +_test-slice-data-2: + 63/c +_test-slice-data-3: + 64/d +_test-slice-data-4: + +# . _. vim:nowrap:textwidth=0 diff --git a/subx/073next-token.subx b/subx/073next-token.subx new file mode 100644 index 00000000..41dd94f3 --- /dev/null +++ b/subx/073next-token.subx @@ -0,0 +1,849 @@ +== code +# instruction effective address register 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: +#? e8/call test-next-token-from-slice/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 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX + b8/copy-to-EAX 1/imm32/exit + cd/syscall 0x80/imm8 + +# extract the next run of characters that are different from a given 'delimiter' (skipping multiple delimiters if necessary) +# on eof return an empty interval +next-token: # in : (address stream), delimiter : byte, out : (address slice) + # . 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 + 56/push-ESI + 57/push-EDI + # ESI = in + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI + # EDI = out + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 7/r32/EDI 0x10/disp8 . # copy *(EBP+16) to EDI + # skip-chars-matching(in, delimiter) + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + 56/push-ESI + # . . call + e8/call skip-chars-matching/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # out->start = &in->data[in->read] + 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # copy *(ESI+4) to ECX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 1/index/ECX . 0/r32/EAX 0xc/disp8 . # copy ESI+ECX+12 to EAX + 89/copy 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # copy EAX to *EDI + # skip-chars-not-matching(in, delimiter) + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + 56/push-ESI + # . . call + e8/call skip-chars-not-matching/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # out->end = &in->data[in->read] + 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # copy *(ESI+4) to ECX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 1/index/ECX . 0/r32/EAX 0xc/disp8 . # copy ESI+ECX+12 to EAX + 89/copy 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 4/disp8 . # copy EAX to *(EDI+4) + # . restore registers + 5f/pop-to-EDI + 5e/pop-to-ESI + 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-next-token: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # setup + # . 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 + # var slice/ECX = {0, 0} + 68/push 0/imm32/end + 68/push 0/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # 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 + # next-token(_test-stream, 0x20/space, slice) + # . . push args + 51/push-ECX + 68/push 0x20/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call next-token/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # check-ints-equal(slice->start - _test-stream->data, 2, msg) + # . check-ints-equal(slice->start - _test-stream, 14, msg) + # . . push args + 68/push "F - test-next-token: start"/imm32 + 68/push 0xe/imm32 + # . . push slice->start - _test-stream + 8b/copy 0/mod/indirect 1/rm32/ECX . . . 0/r32/EAX . . # copy *ECX to EAX + 81 5/subop/subtract 3/mod/direct 0/rm32/EAX . . . . . _test-stream/imm32 # subtract from EAX + 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 + # check-ints-equal(slice->end - _test-stream->data, 4, msg) + # . check-ints-equal(slice->end - _test-stream, 16, msg) + # . . push args + 68/push "F - test-next-token: end"/imm32 + 68/push 0x10/imm32 + # . . push slice->end - _test-stream + 8b/copy 1/mod/*+disp8 1/rm32/ECX . . . 0/r32/EAX 4/disp8 . # copy *(ECX+4) to EAX + 81 5/subop/subtract 3/mod/direct 0/rm32/EAX . . . . . _test-stream/imm32 # subtract from EAX + 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 + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-next-token-eof: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # setup + # . 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 + # var slice/ECX = {0, 0} + 68/push 0/imm32/end + 68/push 0/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # write nothing to _test-stream + # next-token(_test-stream, 0x20/space, slice) + # . . push args + 51/push-ECX + 68/push 0x20/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call next-token/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # check-ints-equal(slice->end, slice->start, msg) + # . . push args + 68/push "F - test-next-token-eof"/imm32 + ff 6/subop/push 1/mod/*+disp8 1/rm32/ECX . . . . 4/disp8 . # push *(ECX+4) + ff 6/subop/push 0/mod/indirect 1/rm32/ECX . . . . . . # push *ECX + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +# extract the next run of characters that are different from a given 'delimiter' (skipping multiple delimiters if necessary) +# on eof return an empty interval +next-token-from-slice: # start : (address byte), end : (address byte), delimiter : byte, out : (address slice) -> + # . 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 + 57/push-EDI + # ECX = end + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 0xc/disp8 . # copy *(EBP+12) to ECX + # EDX = delimiter + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0x10/disp8 . # copy *(EBP+16) to EDX + # EDI = out + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 7/r32/EDI 0x14/disp8 . # copy *(EBP+20) to EDI + # EAX = skip-chars-matching-in-slice(start, end, delimiter) + # . . push args + 52/push-EDX + 51/push-ECX + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call skip-chars-matching-in-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # out->start = EAX + 89/copy 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # copy EAX to *EDI + # EAX = skip-chars-not-matching-in-slice(EAX, end, delimiter) + # . . push args + 52/push-EDX + 51/push-ECX + 50/push-EAX + # . . call + e8/call skip-chars-not-matching-in-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # out->end = EAX + 89/copy 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 4/disp8 . # copy EAX to *(EDI+4) + # . restore registers + 5f/pop-to-EDI + 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 + +test-next-token-from-slice: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # (EAX..ECX) = " ab" + b8/copy-to-EAX " ab"/imm32 + 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX + 05/add-to-EAX 4/imm32 + # var out/EDI : (address slice) = {0, 0} + 68/push 0/imm32/end + 68/push 0/imm32/start + 89/copy 3/mod/direct 7/rm32/EDI . . . 4/r32/ESP . . # copy ESP to EDI + # next-token-from-slice(EAX, ECX, 0x20/space, out) + # . . push args + 57/push-EDI + 68/push 0x20/imm32 + 51/push-ECX + 50/push-EAX + # . . call + e8/call next-token-from-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP + # out->start should be at the 'a' + # . check-ints-equal(out->start - in->start, 2, msg) + # . . push args + 68/push "F - test-next-token-from-slice: start"/imm32 + 68/push 2/imm32 + # . . push out->start - in->start + 8b/copy 0/mod/indirect 7/rm32/EDI . . . 1/r32/ECX . . # copy *EDI to ECX + 2b/subtract 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # subtract EAX from ECX + 51/push-ECX + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # out->end should be after the 'b' + # check-ints-equal(out->end - in->start, 4, msg) + # . . push args + 68/push "F - test-next-token-from-slice: end"/imm32 + 68/push 4/imm32 + # . . push out->end - in->start + 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 1/r32/ECX 4/disp8 . # copy *(EDI+4) to ECX + 2b/subtract 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # subtract EAX from ECX + 51/push-ECX + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-next-token-from-slice-eof: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # var out/EDI : (address slice) = {0, 0} + 68/push 0/imm32/end + 68/push 0/imm32/start + 89/copy 3/mod/direct 7/rm32/EDI . . . 4/r32/ESP . . # copy ESP to EDI + # next-token-from-slice(0, 0, 0x20/space, out) + # . . push args + 57/push-EDI + 68/push 0x20/imm32 + 68/push 0/imm32 + 68/push 0/imm32 + # . . call + e8/call next-token-from-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP + # out should be empty + # . check-ints-equal(out->end - out->start, 0, msg) + # . . push args + 68/push "F - test-next-token-from-slice-eof"/imm32 + 68/push 0/imm32 + # . . push out->start - in->start + 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 1/r32/ECX 4/disp8 . # copy *(EDI+4) to ECX + 2b/subtract 0/mod/indirect 7/rm32/EDI . . . 1/r32/ECX . . # subtract *EDI from ECX + 51/push-ECX + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-next-token-from-slice-nothing: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # (EAX..ECX) = " " + b8/copy-to-EAX " "/imm32 + 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX + 05/add-to-EAX 4/imm32 + # var out/EDI : (address slice) = {0, 0} + 68/push 0/imm32/end + 68/push 0/imm32/start + 89/copy 3/mod/direct 7/rm32/EDI . . . 4/r32/ESP . . # copy ESP to EDI + # next-token-from-slice(in, 0x20/space, out) + # . . push args + 57/push-EDI + 68/push 0x20/imm32 + 51/push-ECX + 50/push-EAX + # . . call + e8/call next-token-from-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP + # out should be empty + # . check-ints-equal(out->end - out->start, 0, msg) + # . . push args + 68/push "F - test-next-token-from-slice-eof"/imm32 + 68/push 0/imm32 + # . . push out->start - in->start + 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 1/r32/ECX 4/disp8 . # copy *(EDI+4) to ECX + 2b/subtract 0/mod/indirect 7/rm32/EDI . . . 1/r32/ECX . . # subtract *EDI from ECX + 51/push-ECX + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +skip-chars-matching: # in : (address stream), delimiter : 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 + 56/push-ESI + # ESI = in + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI + # ECX = in->read + 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # copy *(ESI+4) to ECX + # EBX = in->write + 8b/copy 0/mod/indirect 6/rm32/ESI . . . 3/r32/EBX . . # copy *ESI to EBX + # EDX = delimiter + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0xc/disp8 . # copy *(EBP+12) to EDX +$skip-chars-matching:loop: + # if (in->read >= in->write) break + 39/compare 3/mod/direct 1/rm32/ECX . . . 3/r32/EBX . . # compare ECX with EBX + 7d/jump-if-greater-or-equal $skip-chars-matching:end/disp8 + # EAX = in->data[in->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 0xc/disp8 . # copy byte at *(ESI+ECX+12) to AL + # if (EAX != delimiter) break + 39/compare 3/mod/direct 0/rm32/EAX . . . 2/r32/EDX . . # compare EAX and EDX + 75/jump-if-not-equal $skip-chars-matching:end/disp8 + # ++in->read + 41/inc-ECX + eb/jump $skip-chars-matching:loop/disp8 +$skip-chars-matching:end: + # persist in->read + 89/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # copy ECX to *(ESI+4) + # . restore registers + 5e/pop-to-ESI + 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 + +test-skip-chars-matching: + # setup + # . 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 + # 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 + # skip-chars-matching(_test-stream, 0x20/space) + # . . push args + 68/push 0x20/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call skip-chars-matching/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(_test-stream->read, 2, msg) + # . . push args + 68/push "F - test-skip-chars-matching"/imm32 + 68/push 2/imm32 + # . . push *_test-stream->read + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) + # . . 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-skip-chars-matching-none: + # setup + # . 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 + # 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 + # skip-chars-matching(_test-stream, 0x20/space) + # . . push args + 68/push 0x20/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call skip-chars-matching/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(_test-stream->read, 0, msg) + # . . push args + 68/push "F - test-skip-chars-matching-none"/imm32 + 68/push 0/imm32 + # . . push *_test-stream->read + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) + # . . 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 + +# minor fork of 'skip-chars-matching' +skip-chars-not-matching: # in : (address stream), delimiter : 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 + 56/push-ESI + # ESI = in + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI + # ECX = in->read + 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # copy *(ESI+4) to ECX + # EBX = in->write + 8b/copy 0/mod/indirect 6/rm32/ESI . . . 3/r32/EBX . . # copy *ESI to EBX + # EDX = delimiter + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0xc/disp8 . # copy *(EBP+12) to EDX +$skip-chars-not-matching:loop: + # if (in->read >= in->write) break + 39/compare 3/mod/direct 1/rm32/ECX . . . 3/r32/EBX . . # compare ECX with EBX + 7d/jump-if-greater-or-equal $skip-chars-not-matching:end/disp8 + # EAX = in->data[in->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 0xc/disp8 . # copy byte at *(ESI+ECX+12) to AL + # if (EAX == delimiter) break + 39/compare 3/mod/direct 0/rm32/EAX . . . 2/r32/EDX . . # compare EAX and EDX + 74/jump-if-equal $skip-chars-not-matching:end/disp8 + # ++in->read + 41/inc-ECX + eb/jump $skip-chars-not-matching:loop/disp8 +$skip-chars-not-matching:end: + # persist in->read + 89/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 4/disp8 . # copy ECX to *(ESI+4) + # . restore registers + 5e/pop-to-ESI + 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 + +test-skip-chars-not-matching: + # setup + # . 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 + # 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 + # skip-chars-not-matching(_test-stream, 0x20/space) + # . . push args + 68/push 0x20/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call skip-chars-not-matching/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(_test-stream->read, 2, msg) + # . . push args + 68/push "F - test-skip-chars-not-matching"/imm32 + 68/push 2/imm32 + # . . push *_test-stream->read + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) + # . . 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-skip-chars-not-matching-none: + # setup + # . 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 + # 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 + # skip-chars-not-matching(_test-stream, 0x20/space) + # . . push args + 68/push 0x20/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call skip-chars-not-matching/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(_test-stream->read, 0, msg) + # . . push args + 68/push "F - test-skip-chars-not-matching-none"/imm32 + 68/push 0/imm32 + # . . push *_test-stream->read + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) + # . . 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-skip-chars-not-matching-all: + # setup + # . 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 + # 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 + # skip-chars-not-matching(_test-stream, 0x20/space) + # . . push args + 68/push 0x20/imm32 + 68/push _test-stream/imm32 + # . . call + e8/call skip-chars-not-matching/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # check-ints-equal(_test-stream->read, 2, msg) + # . . push args + 68/push "F - test-skip-chars-not-matching-all"/imm32 + 68/push 2/imm32 + # . . push *_test-stream->read + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 4/disp8 . # push *(EAX+4) + # . . 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 + +skip-chars-matching-in-slice: # curr : (address byte), end : (address byte), delimiter : byte -> curr/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 + 52/push-EDX + 53/push-EBX + # EAX = curr + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 8/disp8 . # copy *(EBP+8) to EAX + # ECX = end + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 0xc/disp8 . # copy *(EBP+12) to ECX + # EDX = delimiter + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0x10/disp8 . # copy *(EBP+16) to EDX + # EBX = 0 + 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX +$skip-chars-matching-in-slice:loop: + # if (curr >= end) break + 39/compare 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # compare EAX with ECX + 7d/jump-if-greater-or-equal $skip-chars-matching-in-slice:end/disp8 + # if (*curr != delimiter) break + 8a/copy-byte 0/mod/indirect 0/rm32/EAX . . . 3/r32/BL . . # copy byte at *EAX to BL + 39/compare 3/mod/direct 3/rm32/EBX . . . 2/r32/EDX . . # compare EBX and EDX + 75/jump-if-not-equal $skip-chars-matching-in-slice:end/disp8 + # ++in->read + 40/inc-EAX + eb/jump $skip-chars-matching-in-slice:loop/disp8 +$skip-chars-matching-in-slice:end: + # . restore registers + 5b/pop-to-EBX + 5a/pop-to-EDX + 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 + +test-skip-chars-matching-in-slice: + # (EAX..ECX) = " ab" + b8/copy-to-EAX " ab"/imm32 + 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX + 05/add-to-EAX 4/imm32 + # EAX = skip-chars-matching-in-slice(EAX, ECX, 0x20/space) + # . . push args + 68/push 0x20/imm32 + 51/push-ECX + 50/push-EAX + # . . call + e8/call skip-chars-matching-in-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # check-ints-equal(ECX-EAX, 2, msg) + # . . push args + 68/push "F - test-skip-chars-matching-in-slice"/imm32 + 68/push 2/imm32 + # . . push ECX-EAX + 29/subtract 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # subtract EAX from ECX + 51/push-ECX + # . . 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-skip-chars-matching-in-slice-none: + # (EAX..ECX) = "ab" + b8/copy-to-EAX "ab"/imm32 + 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX + 05/add-to-EAX 4/imm32 + # EAX = skip-chars-matching-in-slice(EAX, ECX, 0x20/space) + # . . push args + 68/push 0x20/imm32 + 51/push-ECX + 50/push-EAX + # . . call + e8/call skip-chars-matching-in-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # check-ints-equal(ECX-EAX, 2, msg) + # . . push args + 68/push "F - test-skip-chars-matching-in-slice-none"/imm32 + 68/push 2/imm32 + # . . push ECX-EAX + 29/subtract 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # subtract EAX from ECX + 51/push-ECX + # . . 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 + +# minor fork of 'skip-chars-matching-in-slice' +skip-chars-not-matching-in-slice: # curr : (address byte), end : (address byte), delimiter : byte -> curr/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 + 52/push-EDX + 53/push-EBX + # EAX = curr + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 0/r32/EAX 8/disp8 . # copy *(EBP+8) to EAX + # ECX = end + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 1/r32/ECX 0xc/disp8 . # copy *(EBP+12) to ECX + # EDX = delimiter + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 2/r32/EDX 0x10/disp8 . # copy *(EBP+16) to EDX + # EBX = 0 + 31/xor 3/mod/direct 3/rm32/EBX . . . 3/r32/EBX . . # clear EBX +$skip-chars-not-matching-in-slice:loop: + # if (curr >= end) break + 39/compare 3/mod/direct 0/rm32/EAX . . . 1/r32/ECX . . # compare EAX with ECX + 7d/jump-if-greater-or-equal $skip-chars-not-matching-in-slice:end/disp8 + # if (*curr == delimiter) break + 8a/copy-byte 0/mod/indirect 0/rm32/EAX . . . 3/r32/BL . . # copy byte at *EAX to BL + 39/compare 3/mod/direct 3/rm32/EBX . . . 2/r32/EDX . . # compare EBX and EDX + 74/jump-if-equal $skip-chars-not-matching-in-slice:end/disp8 + # ++in->read + 40/inc-EAX + eb/jump $skip-chars-not-matching-in-slice:loop/disp8 +$skip-chars-not-matching-in-slice:end: + # . restore registers + 5b/pop-to-EBX + 5a/pop-to-EDX + 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 + +test-skip-chars-not-matching-in-slice: + # (EAX..ECX) = "ab " + b8/copy-to-EAX "ab "/imm32 + 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX + 05/add-to-EAX 4/imm32 + # EAX = skip-chars-not-matching-in-slice(EAX, ECX, 0x20/space) + # . . push args + 68/push 0x20/imm32 + 51/push-ECX + 50/push-EAX + # . . call + e8/call skip-chars-not-matching-in-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # check-ints-equal(ECX-EAX, 1, msg) + # . . push args + 68/push "F - test-skip-chars-not-matching-in-slice"/imm32 + 68/push 1/imm32 + # . . push ECX-EAX + 29/subtract 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # subtract EAX from ECX + 51/push-ECX + # . . 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-skip-chars-not-matching-in-slice-none: + # (EAX..ECX) = " ab" + b8/copy-to-EAX " ab"/imm32 + 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX + 05/add-to-EAX 4/imm32 + # EAX = skip-chars-not-matching-in-slice(EAX, ECX, 0x20/space) + # . . push args + 68/push 0x20/imm32 + 51/push-ECX + 50/push-EAX + # . . call + e8/call skip-chars-not-matching-in-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # check-ints-equal(ECX-EAX, 3, msg) + # . . push args + 68/push "F - test-skip-chars-not-matching-in-slice-none"/imm32 + 68/push 3/imm32 + # . . push ECX-EAX + 29/subtract 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # subtract EAX from ECX + 51/push-ECX + # . . 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-skip-chars-not-matching-in-slice-all: + # (EAX..ECX) = "ab" + b8/copy-to-EAX "ab"/imm32 + 8b/copy 0/mod/indirect 0/rm32/EAX . . . 1/r32/ECX . . # copy *EAX to ECX + 8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/EAX 1/index/ECX . 1/r32/ECX 4/disp8 . # copy EAX+ECX+4 to ECX + 05/add-to-EAX 4/imm32 + # EAX = skip-chars-not-matching-in-slice(EAX, ECX, 0x20/space) + # . . push args + 68/push 0x20/imm32 + 51/push-ECX + 50/push-EAX + # . . call + e8/call skip-chars-not-matching-in-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # check-ints-equal(ECX-EAX, 0, msg) + # . . push args + 68/push "F - test-skip-chars-not-matching-in-slice-all"/imm32 + 68/push 0/imm32 + # . . push ECX-EAX + 29/subtract 3/mod/direct 1/rm32/ECX . . . 0/r32/EAX . . # subtract EAX from ECX + 51/push-ECX + # . . 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 + +# . . vim:nowrap:textwidth=0 -- cgit 1.4.1-2-gfad0