From d6efaa23e200d7a061715f8a036221a03d9d8055 Mon Sep 17 00:00:00 2001 From: Jonas Hvid Date: Thu, 14 Nov 2019 18:03:42 +0100 Subject: [PATCH] Implement "next" macro and add related notes --- main.asm | 25 ++++++++++--------------- notes | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/main.asm b/main.asm index acb119a..537796d 100644 --- a/main.asm +++ b/main.asm @@ -1,25 +1,20 @@ format ELF64 executable -struc with_length string& { - . db string - .length = $ - . -} - -macro write_stdout string_label { - mov rax, 1 - mov rdi, 1 - mov rsi, string_label - mov rdx, string_label#.length - syscall +;; 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 +;; next word in that definition. +macro next { + ;; RSI points to the address of the definition of the next word to execute. + lodsq ; Load value at RSI into RAX and increment RSI + ;; Now RAX contains the location of the next word to execute. The first 8 + ;; bytes of this word is the address of the codeword, which is what we want + ;; to execute. + jmp qword [rax] ; Jump to the codeword of the current word } segment readable executable start: - write_stdout message - jmp $ segment readable - -message with_length 'Hello, world!',$A diff --git a/notes b/notes index c23bee7..d10de80 100644 --- a/notes +++ b/notes @@ -8,6 +8,9 @@ JONESFORTH: # Notes on implementation +This is my summary of the most important parts of +https://raw.githubusercontent.com/nornagon/jonesforth/master/jonesforth.S. + ## Dictionary In Forth, words are stored in a dictionary. The dictionary is a linked list whose entries look like this: @@ -33,3 +36,26 @@ For example, DOUBLE and QUADRUPLE may be stored like this: LATEST The Forth variable LATEST contains a pointer to the most recently defined word. +## Threaded code + +In a typical Forth interpreter, code is stored in a peculiar way. (This way of +storing code is primarily motivated by space contraints on early systems.) + +The definition of a word is stored as a sequence of memory adresses of each of +the words making up that definition. (At the end of a compiled definition, there +is also some extra code that causes execution to continue in the correct way.) + +We use a register (ESI) to store a reference to the next index of the +word (inside a definition) that we are executing. Then, in order to execute a +word, we just jump to whatever address is pointed to by ESI. The code for +updating ESI and continuing execution is stored at the end of each subroutine. + +Of course, this approach only works if each of the words that we are executing +is defined in assembly, but we also want to be able to execute Forth words! + +We get around this problem by adding a "codeword" to the beginning of any +compiled subroutine. This codeword is a pointer to the intrepreter to run the +given function. In order to run such functions, we actually need two jumps when +executing: In order to execute a word, we jump to the address at the location +pointed to by the address in ESI. + -- 2.39.2