snapshot before branching
[rrq/jonasforth.git] / src / main.asm
index 73fbc0da59dfce86ab222b4a3efa27974d089631..3914b11c72bf20fd41fa07d182203fc707b75ba7 100644 (file)
@@ -1,5 +1,6 @@
-;; The UEFI module defines the following functions. Each of these functions
-;; preserve the value of RSI and RSP. They may use other registers as they like.
+;; The UEFI module defines the following functions. Each of these
+;; functions preserve the value of RSI and RSP. They may use other
+;; registers as they like.
 ;;
 ;; os_initialize
 ;;   Called at initialization.
@@ -14,7 +15,8 @@
 ;;
 ;; os_terminate
 ;;   Shut down the system, returning the error code given in RAX.
-include 'os/uefi.asm'
+
+include 'src/uefi.asm'
 
 ;; The code in this macro is placed at the end of each Forth word. When we are
 ;; executing a definition, this code is what causes execution to resume at the
@@ -29,7 +31,7 @@ macro next {
 }
 
 ;; pushr and popr work on the return stack, whose location is stored in the
-;; register RBP.
+;; register RBP. Always allocates an extra 8 bytes as "local frame"
 macro pushr x {
   sub rbp, 8
   mov qword [rbp], x
@@ -66,7 +68,8 @@ label:
 initial_latest_entry = label#_entry
 }
 
-;; Define a Forth word that is implemented in assembly. See 'header' for details.
+;; Define a Forth word that is implemented in assembly. See 'header'
+;; for details.
 macro forth_asm label, name, immediate {
   header label, name, immediate
   dq .start
@@ -79,7 +82,7 @@ include "impl.asm"      ; Misc. subroutines
 include "bootstrap.asm" ; Forth words encoded in Assembly
 
 main:
-  cld                        ; Clear direction flag so LODSQ does the right thing.
+  cld                     ; Clear direction flag so LODSQ does the right thing.
   mov rbp, return_stack_top  ; Initialize return stack
 
   call os_initialize
@@ -89,12 +92,15 @@ main:
 
 program: dq MAIN
 
-;; The codeword is the code that will be executed at the beginning of a forth
-;; word. It needs to save the old RSI and update it to point to the next word to
-;; execute.
+;; The codeword is the code that will be executed at the beginning of
+;; a forth word. It needs to save the old RSI and update it to point
+;; to the next word to execute.
 header DOCOL, 'DOCOL'
-  pushr rsi            ; Save old value of RSI on return stack; we will continue execution there after we are done executing this word
-  lea rsi, [rax + 8]   ; RAX currently points to the address of the codeword, so we want to continue at RAX+8
+  pushr rsi            ; Save old value of RSI on return stack; we
+                      ; will continue execution there after we are
+                      ; done executing this word
+  lea rsi, [rax + 8]   ; RAX currently points to the address of the
+                      ; codeword, so we want to continue at RAX+8
   next                 ; Execute word pointed to by RSI
 
 ;; This word is called at the end of a Forth definition. It just needs to
@@ -103,16 +109,16 @@ forth_asm EXIT, 'EXIT'
   popr rsi
   next
 
-;; LIT is a special word that reads the next "word pointer" and causes it to be
-;; placed on the stack rather than executed.
+;; LIT is a special word that reads the next "word pointer" and causes
+;; it to be placed on the stack rather than executed.
 forth_asm LIT, 'LIT'
   lodsq
   push rax
   next
 
-;; When LITSTRING is encountered while executing a word, it instead reads a
-;; string from the definition of that word, and places that string on the stack
-;; as (buffer, length).
+;; When LITSTRING is encountered while executing a word, it instead
+;; reads a string from the definition of that word, and places that
+;; string on the stack as (buffer, length).
 forth_asm LITSTRING, 'LITSTRING'
   lodsb
   push rsi ; Buffer
@@ -121,8 +127,9 @@ forth_asm LITSTRING, 'LITSTRING'
   add rsi, rax ; Skip over string before resuming execution
   next
 
-;; Given a string (a pointer following by a size), return the location of the
-;; dictionary entry for that word. If no such word exists, return 0.
+;; Given a string (a pointer following by a size), return the location
+;; of the dictionary entry for that word. If no such word exists,
+;; return 0.
 forth_asm FIND, 'FIND'
   mov [.rsi], rsi
 
@@ -139,8 +146,8 @@ forth_asm FIND, 'FIND'
   mov rsi, [.rsi]
   next
 
-;; Given an entry in the dictionary, return a pointer to the codeword of that
-;; entry.
+;; Given an entry in the dictionary, return a pointer to the codeword
+;; of that entry.
 forth_asm TCFA, '>CFA'
   pop rax
   add rax, 8 + 1                ; [rax] = length of name
@@ -150,22 +157,43 @@ forth_asm TCFA, '>CFA'
   push rax
   next
 
-;; BRANCH is the fundamental mechanism for branching. BRANCH reads the next word
-;; as a signed integer literal and jumps by that offset.
+;; BRANCH is the fundamental mechanism for branching. BRANCH reads the
+;; next word as a signed integer literal and jumps by that offset.
+
 forth_asm BRANCH, 'BRANCH'
-  add rsi, [rsi] ; [RSI], which is the next word, contains the offset; we add this to the instruction pointer.
+  add rsi, [rsi] ; [RSI], which is the next word, contains the offset
+                ; we add this to the instruction pointer.
   next           ; Then, we can just continue execution as normal
 
-;; 0BRANCH is like BRANCH, but it jumps only if the top of the stack is zero.
+;; 0BRANCH is like BRANCH, but it jumps only if the top of the stack
+;; is zero.
 forth_asm ZBRANCH, '0BRANCH'
-  ;; Compare top of stack to see if we should branch
   pop rax
-  cmp rax, 0
+  cmp rax, 0    ; Compare top of stack to see if we should branch
   jnz .dont_branch
 .do_branch:
-  jmp BRANCH.start
+  add rsi,[rsi]
+  next
 .dont_branch:
-  add rsi, 8     ; We need to skip over the next word, which contains the offset.
+  add rsi, 8     ; We need to skip over the next word, which contains
+                ; the offset.
+  next
+
+;; Push the return stack pointer. "grows" negatively
+forth_asm RSPGET, 'R='
+  push rbp
+  next
+
+;; The return stack "grows" negatively, and rbp is the address of the top
+;; Move rbp by n (from stack) bytes
+forth_asm RSPADD, 'R+'
+  pop rax
+  sub rbp, rax
+  next
+
+;; Push top of the stack.
+forth_asm TOP_, 'TOP'
+  push rsp
   next
 
 ;; Duplicate the top of the stack.
@@ -178,6 +206,13 @@ forth_asm EXEC, 'EXEC'
   pop rax
   jmp qword [rax]
 
+;; This word skips a word without exectuing, but pushes its address
+forth_asm SKIP_, 'SKIP'
+  push rsi
+  add rsi, 8     ; We need to skip over the next word, which contains
+                ; the offset.
+  next
+
 ;; Expects a character on the stack and prints it to standard output.
 forth_asm EMIT, 'EMIT'
   pushr rsi
@@ -192,10 +227,10 @@ forth_asm EMIT, 'EMIT'
   popr rsi
   next
 
-;; Read a single character from the current input stream. Usually, this will wait
-;; for the user to press a key, and then return the corresponding character. When
-;; reading from a special buffer, it will instead return the next characater from
-;; that buffer.
+;; Read a single character from the current input stream. Usually,
+;; this will wait for the user to press a key, and then return the
+;; corresponding character. When reading from a special buffer, it
+;; will instead return the next characater from that buffer.
 ;;
 ;; The ASCII character code is placed on the stack.
 forth_asm KEY, 'KEY'
@@ -222,16 +257,18 @@ forth_asm KEY, 'KEY'
   dec [input_buffer_length]
   ret
 
-;; Read a word and push it onto the stack as a pointer and a size. The pointer
-;; is valid until the next call to READ_WORD.
+;; Read a word and push it onto the stack as a pointer and a size. The
+;; pointer is valid until the next call to READ_WORD.
 forth_asm READ_WORD, 'READ-WORD'
   push rsi
 .skip_whitespace:
   ;; Read characters until one of them is not whitespace.
   call KEY.impl
-  ;; We consider newlines and spaces to be whitespace.
+  ;; We consider newlines, tabs and spaces to be whitespace.
   cmp al, ' '
   je .skip_whitespace
+  cmp al, $9
+  je .skip_whitespace
   cmp al, $A
   je .skip_whitespace
 
@@ -249,6 +286,8 @@ forth_asm READ_WORD, 'READ-WORD'
 
   cmp al, ' '
   je .end
+  cmp al, 9
+  je .end
   cmp al, $A
   jne .read_alpha
 
@@ -260,8 +299,8 @@ forth_asm READ_WORD, 'READ-WORD'
 
   next
 
-;; Takes a string on the stack and replaces it with the decimal number that the
-;; string represents.
+;; Takes a string on the stack and replaces it with the decimal number
+;; that the string represents.
 forth_asm PARSE_NUMBER, 'PARSE-NUMBER'
   pop rcx     ; Length
   pop rdi     ; String pointer
@@ -315,6 +354,14 @@ forth_asm DROP, 'DROP'
   add rsp, 8
   next
 
+;; Takes a value and an address, and stores the value at the given address.
+forth_asm AND_, '&'
+  pop rbx                       ; a
+  pop rax                       ; b
+  and rax, rbx
+  push rax
+  next
+
 forth_asm NOT_, 'NOT'
   pop rax
   cmp rax, 0
@@ -419,8 +466,8 @@ forth_asm PLUS, '+'
   push rax
   next
 
-;; Calculate difference between two integers on the stack. The second number is
-;; subtracted from the first.
+;; Calculate difference between two integers on the stack. The second
+;; number is subtracted from the first.
 forth_asm MINUS, '-'
   pop rax
   pop rbx
@@ -428,8 +475,16 @@ forth_asm MINUS, '-'
   push rbx
   next
 
-;; Given two integers a and b on the stack, pushes the quotient and remainder of
-;; division of a by b.
+;; Multiply two integers on the stack ignoring overflow
+forth_asm MULT, '*'
+  pop rax
+  pop rbx
+  mul rbx
+  push rax
+  next
+
+;; Given two integers a and b on the stack, pushes the quotient and
+;; remainder of division of a by b.
 forth_asm TIMESMOD, '/MOD'
   pop rbx                       ; b
   pop rax                       ; a
@@ -439,9 +494,10 @@ forth_asm TIMESMOD, '/MOD'
   push rdx                      ; a % b
   next
 
-;; Read input until next " character is found. Push a string containing the
-;; input on the stack as (buffer length). Note that the buffer is only valid
-;; until the next call to S" and that no more than 255 characters can be read.
+;; Read input until next " character is found. Push a string
+;; containing the input on the stack as (buffer length). Note that the
+;; buffer is only valid until the next call to S" and that no more
+;; than 255 characters can be read.
 forth_asm READ_STRING, 'S"'
   ;; If the input buffer is set, we should read from there instead.
   cmp [input_buffer], 0
@@ -504,11 +560,12 @@ read_string_buffer:
 
   next
 
-;; CREATE inserts a new header in the dictionary, and updates LATEST so that it
-;; points to the header. To compile a word, the user can then call ',' to
-;; continue to append data after the header.
+;; CREATE inserts a new header in the dictionary, and updates LATEST
+;; so that it points to the header. To compile a word, the user can
+;; then call ',' to continue to append data after the header.
 ;;
-;; It takes the name of the word as a string (address length) on the stack.
+;; It takes the name of the word as a string (address length) on the
+;; stack.
 forth_asm CREATE, 'CREATE'
   pop rcx                       ; Word string length
   pop rdx                       ; Word string pointer
@@ -546,9 +603,9 @@ forth_asm ROT, 'ROT'
   pop rax
   pop rbx
   pop rdx
+  push rbx
   push rax
   push rdx
-  push rbx
   next
 
 forth_asm PICK, 'PICK'
@@ -558,6 +615,10 @@ forth_asm PICK, 'PICK'
   push rax
   next
 
+forth_asm OVER, 'OVER'
+  push qword [rsp + 8]
+  next
+
 forth_asm EQL, '='
   pop rax
   pop rbx
@@ -570,6 +631,30 @@ forth_asm EQL, '='
   push 1
   next
 
+forth_asm LT_, '<'
+  pop rax
+  pop rbx
+  cmp rax, rbx
+  jle .le
+.notle:
+  push 1
+  next
+.le:
+  push 0
+  next
+
+forth_asm GT_, '>'
+  pop rax
+  pop rbx
+  cmp rax, rbx
+  jge .ge
+.notge:
+  push 1
+  next
+.ge:
+  push 0
+  next
+
 forth MAIN, 'MAIN'
   dq SYSCODE
   dq INTERPRET_STRING
@@ -583,15 +668,21 @@ forth EFI_SYSTEM_TABLE_CONSTANT, 'SystemTable'
   dq LIT, system_table, GET
   dq EXIT
 
+forth_asm EFICALL1, 'EFICALL1'
+  pop rax ; function pointer
+  pop rcx ; 1st argument
+  sub rsp, 32
+  call rax
+  add rsp, 32
+  next
+
 forth_asm EFICALL2, 'EFICALL2'
   pop rax ; function pointer
   pop rdx ; 2nd argument
   pop rcx ; 1st argument
-
   sub rsp, 32
   call rax
   add rsp, 32
-
   next
 
 forth_asm EFICALL3, 'EFICALL3'
@@ -599,45 +690,61 @@ forth_asm EFICALL3, 'EFICALL3'
   pop r8  ; 3rd argument
   pop rdx ; 2nd argument
   pop rcx ; 1st argument
-
   sub rsp, 32
   call rax
   add rsp, 32
+  push rax
+  next
 
+forth_asm EFICALL4, 'EFICALL4'
+  pop rax ; function pointer
+  pop r9  ; 4th argument
+  pop r8  ; 3rd argument
+  pop rdx ; 2nd argument
+  pop rcx ; 1st argument
+  sub rsp, 32
+  call rax
+  add rsp, 32
   push rax
+  next
 
+forth_asm EFICALL5, 'EFICALL5'
+  pop rax ; function pointer
+  pop r10 ; 5th argument
+  pop r9  ; 4th argument
+  pop r8  ; 3rd argument
+  pop rdx ; 2nd argument
+  pop rcx ; 1st argument
+  push r10 ; restore as stack argument
+  sub rsp, 32
+  call rax
+  add rsp, 32 + 8
+  push rax
   next
 
 forth_asm EFICALL10, 'EFICALL10'
   pop rax ; function pointer
-
-  mov rcx, [rsp + 8 * 9]
-  mov rdx, [rsp + 8 * 8]
+  mov rcx, [rsp + 8 * 9]       ; 1st
+  mov rdx, [rsp + 8 * 8]       ; 2nd
   mov r8, [rsp + 8 * 7]
   mov r9, [rsp + 8 * 6]
-
   ;; Reverse order of stack arguments
   mov r10, [rsp + 8 * 5]
   mov r11, [rsp + 8 * 0]
   mov [rsp + 8 * 5], r11
   mov [rsp + 8 * 0], r10
-
   mov r10, [rsp + 8 * 4]
   mov r11, [rsp + 8 * 1]
   mov [rsp + 8 * 4], r11
   mov [rsp + 8 * 1], r10
-
   mov r10, [rsp + 8 * 3]
   mov r11, [rsp + 8 * 2]
   mov [rsp + 8 * 3], r11
   mov [rsp + 8 * 2], r10
-
   sub rsp, 32
   call rax
   add rsp, 32 + 8 * 10
-
   push rax
-
   next
 
 ;; Built-in variables:
@@ -669,18 +776,18 @@ forth INPUT_LENGTH, 'INPUT-LENGTH'
 
 section '.data' readable writable
 
-;; The LATEST variable holds a pointer to the word that was last added to the
-;; dictionary. This pointer is updated as new words are added, and its value is
-;; used by FIND to look up words.
+;; The LATEST variable holds a pointer to the word that was last added
+;; to the dictionary. This pointer is updated as new words are added,
+;; and its value is used by FIND to look up words.
 latest_entry dq initial_latest_entry
 
-;; The STATE variable is 0 when the interpreter is executing, and non-zero when
-;; it is compiling.
+;; The STATE variable is 0 when the interpreter is executing, and
+;; non-zero when it is compiling.
 var_STATE dq 0
 
-;; The interpreter can read either from standard input or from a buffer. When
-;; input-buffer is set (non-null), words like READ-WORD and S" will use this
-;; buffer instead of reading user input.
+;; The interpreter can read either from standard input or from a
+;; buffer. When input-buffer is set (non-null), words like READ-WORD
+;; and S" will use this buffer instead of reading user input.
 input_buffer dq 0
 input_buffer_length dq 0
 
@@ -693,8 +800,8 @@ READ_STRING.char_buffer db ?
 READ_STRING.buffer rb $FF
 READ_STRING.length dq ?
 
-DOTU.chars db '0123456789ABCDEF'
-DOTU.buffer rq 16               ; 64-bit number has no more than 16 digits in hex
+DOTU.chars db '0123456789abcdef'
+DOTU.buffer rq 16     ; 64-bit number has no more than 16 digits in hex
 DOTU.rbuffer rq 16
 DOTU.length dq ?
 DOTU.printed_length dq ?
@@ -712,11 +819,12 @@ here_top rq $4000
 rq $2000
 return_stack_top:
 
-;; We store some Forth code in sys.f that defined common words that the user
-;; would expect to have available at startup. To execute these words, we just
-;; include the file directly in the binary, and then interpret it at startup.
+;; We store some Forth code in sys.f that defined common words that
+;; the user would expect to have available at startup. To execute
+;; these words, we just include the file directly in the binary, and
+;; then interpret it at startup.
 sysf:
 file '../init/sys.f'
 file '../init/uefi.f'
+file '../init/blurb.f'
 sysf.len = $ - sysf
-