-; This is a program
+; This is a forth interpreter for x86_64 (elf64)
format elf64 executable
entry main
- segment readable executable
- msg db 'hello world.'
- db 10
- length = $ - msg
+;;; ========================================
+;;; The pushr macro pushes x onto the return stack
+;;; The popr macro pops x from the return stack
+macro pushr x {
+ sub rbp, 8
+ mov [rbp], x
+}
+macro popr x {
+ mov x, [rbp]
+ add rbp, 8
+}
+
+;;; ========================================
+;;; The next macro "moves" execution to the next FORTH instruction,
+;;; using rsi as instruction pointer. It points to the doer field of a
+;;; word, which points to the assembly code that implements the
+;;; execution effect of the word. That doer code is entered with rsi
+;;; referring to the subsequent address in the colling word, and rax
+;;; referring to the doer field of the called word.
+
+macro next {
+ lodsq ; mov rax, [rsi] + add rsi,8
+ jmp qword [rax] ; goto code of that FORTH word (64 bit jump)
+}
+
+;;; ========================================
+;;; The FORTH macro transitions to inline FORTH execution.
+macro FORTH {
+ local forthcode
+ mov rsi,forthcode
+ next
+ ;; align 8
+forthcode:
+}
+
+;;; ========================================
+;;; The ENDFORTH macro transitions back to inline assembler after FORTH
+;;; ========================================
+macro ENDFORTH {
+ dq inline_code
+}
+
+;;; ========================================
+;;; The DOFORTH lays out a single FORTH call
+;;; ========================================
+macro DOFORTH label {
+ FORTH
+ dq label
+ ENDFORTH
+}
+
+;;; Macro WORD starts a FORTH word definition in this code
+;;;
+ previous_word = 0 ; Used for chaining the words
+
+ IMMEDIATE = 1 ; optional flag
+
+macro WORD label, name, doer, flags {
+ ;; align 8
+label#_TFA:
+ ;; TFA
+ dq previous_word
+ previous_word = label#_TFA
+ ;; PFA
+label#_PFA:
+ db flags + 0
+ db label - $ - 2
+ db name
+ db 0
+ ;; align 8
+
+label#_OFF:
+ dq 0 ; The DOES offset. Defaults to 0.
+ ;; also CFA = pointer to "doer"
+label:
+ if doer eq
+ dq doforth
+ else
+ if doer in <fasm>
+ dq label#_DFA
+ else
+ dq doer
+ end if
+ end if
+ ;; DFA
+label#_DFA:
+}
+
+;;; ============================================================
+;;; FORTH machine model
+;;; rsp = data stack pointer
+;;; rbp = return stack pointer
+;;; rsi = instruction pointer
+
+;;; ============================================================
+
+ segment readable writable executable
+
+ WORD return_stack,'RS',dovariable
+ ;; The return stack
+ rb 1048576 ; 1 Mb return stack
+RS_TOP: ; The initial rbp
+
+ WORD data_stack,'DS',dovariable
+ ;; The data stack
+ rb 1048576 ; 1 Mb data stack
+DS_TOP: ; The initial rsp
+
+ WORD inline_code,'[ASM]',fasm
+ ;; ( -- )
+ ;; This transitions execution into inline assembler in the
+ ;; calling word defintion. Note that it stops advancing rsi;
+ ;; code should use FORTH macro to reenter forth execution, or
+ ;; exit to the calling definition via "jmp exit".
+ jmp qword rsi
+
+;;; Execution semantics for FORTH defition word
+;;; At entry, rsi points into the calling definition, at the cell
+;;; following the cell indicating this word, rax points to the CFA of
+;;; this word.
+doforth:
+ pushr rsi
+ lea rsi, [rax+8] ; rsi = the DFA of the rax word
+ next
+
+ WORD p_exit, 'EXIT',fasm
+ ;; ( -- ) ( R: addr -- )
+ ;; Returns execution to the calling definition as per the
+ ;; return stack.
+exit:
+ popr rsi
+ next
+
+ ;; Execution semantics for a variable ( -- addr )
+ ;; rax points to doer field
+dovariable:
+ push rax+16
+ next
+
+ ;; Execution semantics for a constant ( -- v )
+ ;; rax points to doer field
+dovalue:
+ push qword [rax+16]
+ next
+
+ ;; Execution semantics for a string constant ( -- addr n )
+ ;; rax points to doer field
+dostring:
+ add rax,16
+ mov bl,[rax]
+ mov byte [rsp],bl
+ push rax+1
+ next
+
+include 'wordlists.fasm'
+include 'syscalls.fasm'
+include 'memory.fasm'
+include 'stack.fasm'
+include 'math.fasm'
+include 'stdio.fasm'
+
+ WORD p_program_version,'PROGRAM_VERSION',dostring
+ db length
+program_version_string:
+ db 'RRQ Forth version 0.1 - 2021-05-13',10
+ length = $ - program_version_string
+
+ WORD p_stdin,'STDIN',dovalue
+ ;; Initialised to hold a STREAM for fd 0
+ dq 0
+
+ WORD p_quit,'QUIT',fasm
+ ;; QUIT is the program entry point ********************
main:
- lea rsi,[msg] ; address of message
- mov edx,length ; length od trdting
- mov edi,1 ; stdout
- mov eax,1 ; sys_write
- syscall
+ mov rsp,DS_TOP
+ mov rbp,RS_TOP
+ ;; Initialize STREAM STDIN
+ push 0
+ push 10000
+ DOFORTH p_stream
+ pop qword [p_stdin_DFA]
-fini:
- xor edi,edi
+ ;; read a word
+ push qword 1 ; ( fd ) =stdout
+ push qword [p_stdin_DFA]
+ FORTH
+ dq p_read_word ; ( fd s n )
+ dq sys_write
+ ENDFORTH
+
+ push qword 1 ; stdout
+ push qword program_version_string ; address of string
+ push qword length ; length of string (cheating)
+ DOFORTH sys_write ; printout
+ pop rax ; ignore errors
+
+ push 0
+ DOFORTH sys_exit
+
+ ;; TERMINATE0 terminates the program with code 0
+ ;; ( v -- )
+ WORD terminate, 'TERMINATE',fasm
+ pop rdx
+terminate_special:
mov eax,60
syscall
+
+last_word:
+ ;; FORTH is the last word of VOCABULARY FORTH
+ WORD forth,'FORTH',dovalue
+ dq forth_TFA
+ dq 0
+
+heap_start: