summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--amd64.e66
-rw-r--r--hex.e241
-rw-r--r--labels.e2
-rw-r--r--output.e2
4 files changed, 304 insertions, 7 deletions
diff --git a/amd64.e b/amd64.e
index 4b4a2bd..2060a3d 100644
--- a/amd64.e
+++ b/amd64.e
@@ -762,7 +762,9 @@ s" :cc-greater" keyword
   3roll rex-w 0x03 pack8 3unroll
   reg64 swap addressing-indirect-reg64 ;
 
-~ (output point, source register, target register -- output point)
+~ TODO needs description of argument order considerations
+~
+~ (output point, immediate value, target register -- output point)
 : add-reg64-imm8
   3roll rex-w 0x83 pack8 swap 0 swap addressing-reg64
   swap pack8 ;
@@ -777,7 +779,10 @@ s" :cc-greater" keyword
   3roll rex-w 0x2B pack8 3unroll
   swap reg64 swap addressing-indirect-reg64 ;
 
-~ (output point, source register, target register -- output point)
+~ TODO this convention is unlike other arithmetic operations. See the note
+~ on cmp-reg64-imm8, pick one to go with, and document it.
+~
+~ (output point, immediate value, target register -- output point)
 : sub-reg64-imm8
   3roll rex-w 0x83 pack8 swap 5 swap addressing-reg64
   swap pack8 ;
@@ -853,6 +858,30 @@ s" :cc-greater" keyword
   swap rex-w 0xF7 pack8
   swap 2 swap addressing-reg64 ;
 
+~ (output point, bit count, target register -- output point)
+: rol-reg64-imm8
+  3roll rex-w 0xC1 pack8 swap
+  0 swap addressing-reg64
+  swap pack8 ;
+
+~ (output point, bit count, target register -- output point)
+: rol-reg8-imm8
+  3roll 0xC0 pack8 swap
+  0 swap addressing-reg8
+  swap pack8 ;
+
+~ (output point, bit count, target register -- output point)
+: ror-reg64-imm8
+  3roll rex-w 0xC1 swap
+  1 swap addressing-reg64
+  swap pack8 ;
+
+~ (output point, bit count, target register -- output point)
+: ror-reg8-imm8
+  3roll 0xC0 pack8 swap
+  1 swap addressing-reg64
+  swap pack8 ;
+
 
 ~ Control flow instructions
 ~ ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -865,13 +894,22 @@ s" :cc-greater" keyword
   3roll rex-w 0x3B pack8 3unroll
   reg64 swap addressing-reg64 ;
 
-~ (output point, left register, right value -- output point)
+~   The parameter order here is weird, so take note. In a comparison between
+~ a register and an immediate value, it makes sense to think of the register
+~ as the target... but instead we opt to be consistent with the other cmp
+~ instructions, which in turn are consistent with the sub instructions, in
+~ which we put the left operand first because subtraction is not commutative.
+~ TODO wrong, backwards, other choice
+~
+~ TODO the immediate arithmetic instructions are all documented wrong
+~
+~ (output point, immediate value, target register -- output point)
 : cmp-reg64-imm8
   3roll rex-w 0x83 pack8
-  ~ (left register, right value, output point)
-  3roll 7 swap addressing-reg64
-  ~ ( output point, right value)
-  pack8 ;
+  ~ (immediate value, register, output point)
+  swap 7 swap addressing-reg64
+  ~ ( output point, immediate value)
+  swap pack8 ;
 
 ~   Pretend to bitwise-and left with right, and set the flags the same way as
 ~ if we actually had.
@@ -917,3 +955,17 @@ s" :cc-greater" keyword
   swap 0xE9 pack8
   swap pack32 ;
 
+~ TODO This is technically a "near" call; the name doesn't capture that.
+~ Should it?
+~
+~ (output point, address offset value -- output point)
+: call-rel-imm32
+  swap 0xE8 pack8
+  swap pack32 ;
+
+~ TODO This is a "near" return.
+~
+~ (output point)
+: ret
+  0xC3 pack8 ;
+
diff --git a/hex.e b/hex.e
new file mode 100644
index 0000000..51e7703
--- /dev/null
+++ b/hex.e
@@ -0,0 +1,241 @@
+~ cat labels.e elf.e hex.e | ./evoke > hex && chmod 755 hex && ./hex
+
+~ (buffer start, output point, label offset
+~  -- buffer start, output point)
+: jmp-rel-imm8-from-here
+  over 3 pick - 2 + - jmp-rel-imm8 ;
+
+~ (buffer start, output point, label offset, condition code
+~  -- buffer start, output point)
+: jmp-cc-rel-imm8-from-here
+  swap 2 pick 4 pick - 2 + - swap jmp-cc-rel-imm8 ;
+
+~ (buffer start, output point, label offset -- buffer start, output point)
+: call-rel-imm32-from-here
+  over 3 pick - 5 + - call-rel-imm32 ;
+
+
+~ (buffer start, output point -- buffer start, output point)
+: output-start-routine
+  current-offset L!' cold-start
+  ~   The basic registers preserved across syscalls are rbx, rsp, rbp.
+  ~ To avoid redundant moves, we store the buffer pointer in rbx just once,
+  ~ and keep it there. We've made sure our load origin fits in 32 bits, so we
+  ~ can use imm32 for that. We're going to want to do an indirect load from
+  ~ it, so we can't use rbp for this.
+  L@' buffer L@' origin + :rbx mov-reg64-imm32
+
+  current-offset L!' input-loop-start
+  L@' read-byte call-rel-imm32-from-here
+
+  ~   If the length is 0, we got EOF. If it's less than zero, we got a read
+  ~ error. Either way, we exit. This is a signed comparison, as it needs to
+  ~ be.
+  0 :rax cmp-reg64-imm8
+  L@' exit :cc-equal jmp-cc-rel-imm8-from-here
+  L@' read-error :cc-less jmp-cc-rel-imm8-from-here
+
+  ~ Now that the length is handled, retrieve the input byte.
+  :rbx :rax mov-reg64-indirect-reg64
+
+  ~ If it's space or linefeed, skip it (go back to the loop start).
+  0x20 :rax cmp-reg64-imm8                  ~ ASCII space
+  L@' input-loop-start :cc-equal jmp-cc-rel-imm8-from-here
+  0x0a :rax cmp-reg64-imm8                  ~ ASCII linefeed
+  L@' input-loop-start :cc-equal jmp-cc-rel-imm8-from-here
+  ~ If it's a comment, skip the whole thing.
+  0x7e :rax cmp-reg64-imm8                  ~ ASCII tilde
+  L@' skip-comment :cc-equal jmp-cc-rel-imm8-from-here
+
+  ~ Decode the value, or exit with an error.
+  L@' decode-nibble call-rel-imm32-from-here
+
+  ~ We use rbp as a place to stash the high nibble.
+  :rax :rbp mov-reg64-reg64
+  4 :rbp rol-reg64-imm8
+
+  ~ Now we read another byte.
+  L@' read-byte call-rel-imm32-from-here
+
+  ~ Handle the length. A second hex digit is required here.
+  0 :rax cmp-reg64-imm8
+  L@' unexpected-eof :cc-equal jmp-cc-rel-imm8-from-here
+  L@' read-error :cc-less jmp-cc-rel-imm8-from-here
+
+  ~ Now that the length is handled, retrieve the input byte.
+  :rbx :rax mov-reg64-indirect-reg64
+
+  ~ Decode the value, or exit with an error.
+  L@' decode-nibble call-rel-imm32-from-here
+
+  ~ We OR in the low nibble.
+  :rax :rbp or-reg64-reg64
+
+  ~ Output the byte. We reuse the buffer as a place to store it.
+  :rbp :rbx mov-indirect-reg64-reg64
+  :rbx :rsi mov-reg64-reg64                 ~ buffer pointer
+  1 :rdx mov-reg64-imm32                    ~ buffer length
+  1 :rax mov-reg64-imm32                    ~ syscall number for sys-write
+  1 :rdi mov-reg64-imm32                    ~ file descriptor 1 is stdout
+  syscall
+
+  ~ Back to the start of the loop.
+  L@' input-loop-start jmp-rel-imm8-from-here
+
+  current-offset L!' skip-comment
+
+  ~ Read a byte for the comment.
+  L@' read-byte call-rel-imm32-from-here
+
+  ~ Handle the length. We're allowed to end in a comment.
+  0 :rax cmp-reg64-imm8
+  L@' exit :cc-equal jmp-cc-rel-imm8-from-here
+  L@' read-error :cc-less jmp-cc-rel-imm8-from-here
+
+  ~ Now that the length is handled, retrieve the input byte.
+  :rbx :rax mov-reg64-indirect-reg64
+
+  ~ If it's linefeed, the comment is over.
+  0x0a :rax cmp-reg64-imm8                  ~ ASCII linefeed
+  L@' input-loop-start :cc-equal jmp-cc-rel-imm8-from-here
+
+  ~ We're still in the comment, keep handling it.
+  L@' skip-comment jmp-rel-imm8-from-here ;
+
+
+~   This routine has no expectations; it reads a byte into L' buffer, keeps
+~ the return value of the syscall in :rax, and returns to its caller. The
+~ caller is responsible for doing something with the return value.
+~
+~ (output memory start, current output point
+~  -- output memory start, current output point)
+: output-read-byte
+  current-offset L!' read-byte
+  ~ We use self-xor as a concise way to set registers to zero.
+  :rax :rax xor-reg64-reg64                 ~ syscall number for sys-read
+  :rdi :rdi xor-reg64-reg64                 ~ file descriptor 0 is stdin
+  :rbx :rsi mov-reg64-reg64                 ~ buffer pointer
+  ~ We read one byte at a time, because it makes the loop structure simple.
+  1 :rdx mov-reg64-imm32                    ~ buffer length
+  syscall
+  ret ;
+
+
+~   This routine expects :rax to hold an ASCII byte, which must be a valid
+~ hexadecimal digit. When it returns, :rax holds a decoded nibble. If the
+~ input is invalid, it jumps to L' invalid-byte instead, thereby ending
+~ execution.
+~
+~ (output memory start, current output point
+~  -- output memory start, current output point)
+: output-decode-nibble
+  current-offset L!' decode-nibble
+
+  0x30 :rax sub-reg64-imm8                  ~ ASCII zero
+  ~ If it's negative, jump to the error path.
+  L@' invalid-byte :cc-less jmp-cc-rel-imm8-from-here
+  10 :rax cmp-reg64-imm8
+  ~ This is an unsigned comparison.
+  L@' got-nibble :cc-below jmp-cc-rel-imm8-from-here
+  0x41 0x30 - :rax sub-reg64-imm8           ~ ASCII capital A
+  ~ If it's negative, jump to the error path.
+  L@' invalid-byte :cc-less jmp-cc-rel-imm8-from-here
+  ~ To simplify the range adjustment, we do it unconditionally, before
+  ~ checking the upper bound.
+  10 :rax add-reg64-imm8
+  16 :rax cmp-reg64-imm8
+  ~ This is an unsigned comparison.
+  L@' got-nibble :cc-below jmp-cc-rel-imm8-from-here
+  0x61 0x41 - 10 + :rax sub-reg64-imm8      ~ ASCII lowercase a
+  ~ If it's negative, jump to the error path.
+  L@' invalid-byte :cc-less jmp-cc-rel-imm8-from-here
+  ~ Again, we adjust the range unconditionally, then check the upper bound.
+  10 :rax add-reg64-imm8
+  16 :rax cmp-reg64-imm8
+  ~ This is an unsigned comparison.
+  L@' got-nibble :cc-below jmp-cc-rel-imm8-from-here
+  ~ It's not hex, so jump to the error path.
+  L@' invalid-byte jmp-rel-imm8-from-here
+
+  current-offset L!' got-nibble
+  ret ;
+
+
+~ (output memory start, current output point
+~  -- output memory start, current output point)
+: output-exit
+  current-offset L!' exit
+  60 :rax mov-reg64-imm32                   ~ syscall number for sys-exit
+  0 :rdi mov-reg64-imm32                    ~ exit code
+  syscall ;
+
+
+~   Printing an error message makes sure we don't produce a valid-looking
+~ binary that inadvertently gets used. So, it's worth it, despite coming at
+~ a cost to code size.
+~
+~ (output memory start, current output point
+~  -- output memory start, current output point)
+: output-error-handlers
+  current-offset L!' invalid-byte
+  L@' origin L@' invalid-byte-message + :rsi mov-reg64-imm64
+  L@' invalid-byte-message-size :rdx mov-reg64-imm64
+  L@' exit-with-error jmp-rel-imm8-from-here
+
+  current-offset L!' unexpected-eof
+  L@' origin L@' unexpected-eof-message + :rsi mov-reg64-imm64
+  L@' unexpected-eof-message-size :rdx mov-reg64-imm64
+  L@' exit-with-error jmp-rel-imm8-from-here
+
+  current-offset L!' read-error
+  L@' origin L@' read-error-message + :rsi mov-reg64-imm64
+  L@' read-error-message-size :rdx mov-reg64-imm64
+  ~ Fall through.
+
+  current-offset L!' exit-with-error
+  1 :rax mov-reg64-imm32                    ~ syscall number for sys-write
+  2 :rdi mov-reg64-imm32                    ~ file descriptor 2 is stderr
+  syscall
+
+  60 :rax mov-reg64-imm32                   ~ syscall number for sys-exit
+  1 :rdi mov-reg64-imm32                    ~ exit code
+  syscall
+  ;
+
+
+~ (output memory start, current output point
+~  -- output memory start, current output point)
+: output-messages
+  current-offset dup L!' invalid-byte-message 3unroll
+  s" Invalid byte." packstring
+  current-offset 4 roll - L!' invalid-byte-message-size
+
+  current-offset dup L!' read-error-message 3unroll
+  s" Read error." packstring
+  current-offset 4 roll - L!' read-error-message-size
+
+  current-offset dup L!' unexpected-eof-message 3unroll
+  s" Unexpected EOF." packstring
+  current-offset 4 roll - L!' unexpected-eof-message-size ;
+
+~ (output memory start, current output point
+~  -- output memory start, current output point)
+~
+~ Everything directly called by all-contents has this same interface.
+~
+: all-contents
+  0x08000000 L!' origin
+  elf-file-header
+  elf-program-header-writable
+  output-start-routine
+  output-read-byte
+  output-exit
+  output-decode-nibble
+  output-error-handlers
+  output-messages
+  current-offset L!' buffer 0 pack64
+  current-offset L!' total-size ;
+
+' all-contents entry-to-execution-token label-loop
+swap sys-write bye
+
diff --git a/labels.e b/labels.e
index d3d95ac..6175128 100644
--- a/labels.e
+++ b/labels.e
@@ -300,5 +300,7 @@
       exit } if
     drop dup
     reset-labels } while
+  ~ TODO run some analysis on the labels to detect labels that are never
+  ~ defined, but are referenced. List them.
   drop drop drop ." Failed after " . ."  iterations." newline ;
 
diff --git a/output.e b/output.e
index 5c4b31d..9ab3aa1 100644
--- a/output.e
+++ b/output.e
@@ -105,6 +105,8 @@
 ~ recursion, and lets us stick entirely with the trivial control-flow
 ~ constructs we already have.
 ~
+~ TODO add a variant that writes to a string
+~
 ~ (integer to print, base to print in, width to zero-pad to --)
 : .base-unsigned
   ~ (input, base, width)