; This is a forth interpreter for x86_64 (elf64) format elf64 executable entry main ;;; ======================================== ;;; 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 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: mov rsp,DS_TOP mov rbp,RS_TOP ;; Initialize STREAM STDIN push 0 push 10000 DOFORTH p_stream pop qword [p_stdin_DFA] ;; 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: