The LATEST Word (I'm missing something)

Created: 2022-07-22 Updated: 2022-07-22

Back to nasmjf main page

Mystery Solved!

I asked on the Forth subreddit here and got an answer right away along with an excellent explanation that steered me straight:

Incredibly, I did not fully grok that a FORTH variable leaves its address on the stack, not its value. Which makes complete sense because to change a variable’s value, we need its address for storing (with !).

By the way, this demonstrates that I’ve spent all this time writing a FORTH interpreter, but I still barely know how FORTH works as a language.

Amazing.

Anyway, on to my misunderstanding…​

The Mystery

I’m missing something and I can’t find it.

When I was working on the assembly portion (now complete) of "nasmjf" my JonesFORTH port, I ran into an issue while defining the word : (COLON): I can’t figure out where or how the LATEST word is defined.

For reference, here are my copies of the original JonesFORTH source files mentioned below:

jonesforth.S (assembly)

jonesforth.f (FORTH)

The LATEST variable

Here’s a comment in jonesforth.S describing the variable LATEST:

LATEST      Points to the latest (most recently defined) word in the dictionary.

And here’s how it’s defined:

defvar "LATEST",6,,LATEST,name_SYSCALL0

Clearly LATEST contains the address of the word SYSCALL0, right?

The LATEST word

Ah, but a comment above the built-in variables section says:

LATEST is also a FORTH word which pushes the address of
LATEST (the variable) on to the stack, so you can read
or write it using @ and ! operators.  For example, to print
the current value of LATEST (and this can apply to any FORTH
variable) you would do:

    LATEST @ . CR

And that is how the word LATEST is used in the COLON (:) definition:

defword ":",1,,COLON
...
.int LATEST, FETCH, HIDDEN // Make the word hidden

and SEMICOLON (;):

defword ";",1,F_IMMED,SEMICOLON
...
.int LATEST, FETCH, HIDDEN // Toggle hidden flag

(Note that FETCH is the internal name for @.)

What’s the problem?

The only problem is that I don’t see where the LATEST word is being defined. Here’s every mention of LATEST in the assembly. There is no defword or defcode to be found:

dave@phobos~/proj/nasmjf$ ag LATEST jonesforth/jonesforth.S
191:	A FORTH variable called LATEST contains a pointer to the most recently defined word, in
210:	  LATEST
213:	the dictionary (just walk along the dictionary entries starting at LATEST and matching
215:	and add a word to the dictionary (create a new definition, set its LINK to LATEST, and set
216:	LATEST to point to the new word).  We'll see precisely these functions implemented in
1100:	have discussed so far was LATEST, which points to the last (most recently defined) word in the
1101:	FORTH dictionary.  LATEST is also a FORTH word which pushes the address of LATEST (the variable)
1103:	the current value of LATEST (and this can apply to any FORTH variable) you would do:
1105:	LATEST @ . CR
1125:	LATEST		Points to the latest (most recently defined) word in the dictionary.
1133:	defvar "LATEST",6,,LATEST,name_SYSCALL0 // SYSCALL0 must be last in built-in dictionary
1520:	mov var_LATEST,%edx	// LATEST points to name header of the latest word in the dictionary
1645:	  LATEST points here				points to codeword of DUP
1672:    pointer to previous word (from LATEST)			+-- Afterwards, HERE points here, where
1680:	(3) Set LATEST to point to the newly defined word, ...
1786:	movl var_LATEST,%eax	// Get link pointer
1799:	// Update LATEST and HERE.
1801:	movl %eax,var_LATEST
1875:	.int LATEST, FETCH, HIDDEN // Make the word hidden (see below for definition).
1885:	.int LATEST, FETCH, HIDDEN // Toggle hidden flag -- unhide the word (see below for definition).
1918:	movl var_LATEST,%edi	// LATEST word.
1927:		LATEST @ HIDDEN
2198:	initialise the LATEST variable to point to it.  This means that if you want to extend the assembler
2199:	part, you must put new words before SYSCALL0, or else change how LATEST is initialised.

And I missed it while porting, too, because to get my NASM port to work, I simply removed the FETCH after LATEST in the COLON and SEMICOLON definitions. It worked just fine!

DEFWORD ":",1,,COLON
...
dd LATEST, HIDDEN ; Make the word hidden while it's being compiled.

and

DEFWORD ";",1,F_IMMED,SEMICOLON
...
dd LATEST, HIDDEN ; Unhide word now that it's been compiled.

(By the way, I only discovered the problem while stepping through the execution of my port with GDB and trying to understand why my words weren’t being set to hidden while being compiled!)

It seemed extremely unlikely that there was a bug in JonesFORTH, which has had so many eyeballs on it (and obviously works!), but I eventually just shrugged and continued on.

But now I’m at the phase where I’m slowly stepping through every word definition in the FORTH half of JonesFORTH and I ran into the problem again while trying to understand why RECURSE wasn’t working. Sure enough, it was the LATEST @ problem again. RECURSE was fetching the address of the previous word (because the current word starts with a pointer to the previous in a linked list fashion).

Here’s the whole definition:

: RECURSE IMMEDIATE
    LATEST @	\ LATEST points to the word being compiled at the moment
    >CFA		\ get the codeword
    ,		\ compile it
;

I’m sure removing the @ will "fix" it, but I don’t want to do that. I’m okay with making changes to how the assembly portion runs (I already have), but I want to keep the original FORTH source unaltered.

And LATEST is always used like the word definition described above in the FORTH source. Here’s every mention. It always has the @ fetch:

dave@phobos~/proj/nasmjf$ ag LATEST jonesforth/jonesforth.f
110:\ access to the word which we are currently compiling through the LATEST pointer so we
113:	LATEST @	\ LATEST points to the word being compiled at the moment
739:	For example: LATEST @ ID. would print the name of the last word that was defined.
777:	The implementation simply iterates backwards from LATEST using the link pointers.
780:	LATEST @	( start at LATEST dictionary entry )
804:	and definitions will overwrite memory starting at the word.  We also need to set LATEST to
815:	DUP @ LATEST !	( set LATEST to point to the previous word )
829:		LATEST @ 128 DUMP
991:	LATEST @	( start at LATEST dictionary entry )
1028:	LATEST @	( word last curr )
1653:	LATEST @ DUP

So clearly I’ve somehow missed where LATEST becomes both a FORTH variable and a FORTH word in the assembly source.