-; This is a forth interpreter
+; This is a forth interpreter for x86_64 (elf64)
format elf64 executable
- entry main_code
+ 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 <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
+
+;;; ============================================================
-;;; ############################################################
-;;; The FORTH words
segment readable writable executable
-include 'machine.fasm'
-
- ;; PROGRAM_VERSION is the program version string
- WORD program_version, 'PROGRAM_VERSION', marker
- db length
-program_version_string:
- include 'version'
- length = $ - program_version_string
+ WORD return_stack,'RS',dovariable
+ ;; The return stack
+ rb 1048576 ; 1 Mb return stack
+RS_TOP: ; The initial rbp
- ;; MAIN is the program entry point
- ;; ( -- )
- WORD_assembler main, "MAIN"
- mov rsi,program_version_string ; address of string
- mov edx,length ; length of string (cheating)
- mov edi,1 ; stdout
- mov eax,1 ; sys_write
- syscall
- jmp terminate0_code
+ WORD data_stack,'DS',dovariable
+ ;; The data stack
+ rb 1048576 ; 1 Mb data stack
+DS_TOP: ; The initial rsp
- ;; TERMINATE0 terminates the program with code 0
+ WORD inline_code,'[ASM]',fasm
;; ( -- )
- WORD_assembler terminate0, 'TERMINATE0'
- xor edi,edi
- mov eax,60
- syscall
+ ;; 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
- ;; EXIT ends a forth code defintion, returning to caller
- ;; ( -- )
- WORD_assembler exit, 'EXIT'
+;;; 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
-
- ;; MARKER is a word that pushes the address after itself, then exits
- ;; ( -- p )
- WORD_assembler marker, 'MARKER'
- push qword rsi
- jmp exit_code
-
- ;; MARKER@ is a word that pushes a the value after itself, then exits
- ;; ( -- v )
- WORD_assembler marker_get, 'MARKER@'
- push qword [rsi]
- jmp exit_code
-
- ;; DOFORTH begins a FORTH defintion
- WORD_assembler doforth, 'DOFORTH'
- pushr rsi
- lea rsi, [rax + 8]
+
+ ;; 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
- ;; LIT is a word that pushes a the value after itself, then continues
- ;; ( -- v )
- WORD_assembler lit, 'LIT'
- push qword [rsi]
- add rsi, 8
+ ;; 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
- ;; HERE is a variable pointing to the free heap
- WORD here, 'HERE', marker
- dq heap_start ; initialise to first "free" data
+include 'wordlists.fasm'
+include 'syscalls.fasm'
+include 'memory.fasm'
+include 'stack.fasm'
+include 'math.fasm'
+include 'stdio.fasm'
- ;; WORDS is the list of words
- WORD words, 'WORDS', marker
- dq forth_tfa ; initialise to last forth word
+ 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
- ;; FORTH is the last word of the VOCABULARY
- WORD forth, 'FORTH', marker_get
- dq forth_tfa
+ WORD p_stdin,'STDIN',dovalue
+ ;; Initialised to hold a STREAM for fd 0
+ dq 0
-heap_start:
+ 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: