Implement "next" macro and add related notes
authorJonas Hvid <mail@johv.dk>
Thu, 14 Nov 2019 17:03:42 +0000 (18:03 +0100)
committerJonas Hvid <mail@johv.dk>
Thu, 14 Nov 2019 17:06:38 +0000 (18:06 +0100)
main.asm
notes

index acb119a986410f4183b4e999382d6335f36fa9a4..537796de252a2c12db2f3597825afb3a1614f4ea 100644 (file)
--- 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 c23bee740dcee51e2d91f95f1c6691baa0c5d68d..d10de8057314223c1ebfea7266e1323716cd59d2 100644 (file)
--- 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.
+