refactor(v4.0.0): the capsule's inner words keep their state in memory

WORD, the dictionary words and the code generator run underneath
whatever the user has on the stacks, which are ten and nine deep (D-2).
Measured, they used five to seven data cells of their own, leaving a
line about three.  Each now keeps what it works on in its file's
scratch cells and has at most three cells on the data stack; , calls
nothing; and the longest chains of calls are shorter.

The public words of core.v4, input.v4 and dict.v4 get dictionary
headers.  NUMBER is split so the interpreter can have a flag instead of
NODE-ERROR.  The host-node tests share one memory map, host_map.h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-04 10:41:50 -04:00
co-authored by Claude Opus 5.5
parent 44ffd261bb
commit bbd1b4047f
10 changed files with 360 additions and 248 deletions
+49 -37
View File
@@ -19,14 +19,16 @@
\ bits whose lowest is bit 27 - 5k, and a branch there has 27 - 5k address bits.
\
\ Rests on core.v4 and dict.v4. Constants the loader supplies:
\ (CG) word address of nine cells: the word being built, the next
\ free slot, the number of literals it owes, and up to six of them
\ (CG) word address of thirteen cells of scratch for this file
\ CG-BSLOTS how many slots, counted from slot 0, may hold a branch
macro (CUR) (CG) endmacro
macro (SLOT) (CG) 1 + endmacro
macro (NLIT) (CG) 2 + endmacro
macro (LITS) (CG) 3 + endmacro
\ Like WORD, these run under whatever the user has on the stacks, so what they
\ work on is in (CG) and they keep at most three cells of their own on the
\ data stack (D-2):
\ (CG)+0 the word being built (CG)+1 its next free slot
\ (CG)+2 how many literals it owes, and (CG)+3 .. (CG)+8 the literals
\ (CG)+9 the opcode (OP,) was given (CG)+10 the ref (BRANCH>) is making
\ (CG)+11 the target a branch is to reach (CG)+12 which literal (FLUSH) is at
\ ( x n -- x' ) x shifted left n places, n >= 0
: (LSHIFT) if Z -1 + push L: 2* next L ; Z: drop ;
@@ -38,67 +40,77 @@ macro (LITS) (CG) 3 + endmacro
: (MASK) (LOWBIT) 1 SWAP (LSHIFT) -1 + ;
\ ( -- ) forget the word being built
: (CG-RESET) 0 (CUR) a! ! 0 (SLOT) a! ! 0 (NLIT) a! ! ;
: (CG-RESET) 0 (CG) a! ! 0 (CG)+1 a! ! 0 (CG)+2 a! ! ;
\ ( op -- ) into the next free slot. The slot must be free.
\ ( op -- ) into the next free slot. The slot must be free. The shift is
\ (LSHIFT) in line; the slot's lowest bit is never bit 0, so it shifts at
\ least once.
: (PUT)
(SLOT) a! @ dup 1 + ! \ op k
(LOWBIT) (LSHIFT)
(CUR) a! @ + ! ;
(CG)+1 a! @ dup 1 + ! \ op k
(LOWBIT) -1 + push L: 2* next L
(CG) a! @ + ! ;
\ ( -- ) append the word being built, its unused slots nop, then the
\ literals it owes; nothing if no slot is in use
: (FLUSH)
(SLOT) a! @ if EMPTY drop
P: (SLOT) a! @ -6 + if FULL drop 28 (PUT) jump P
(CG)+1 a! @ if EMPTY drop
P: (CG)+1 a! @ -6 + if FULL drop 28 (PUT) jump P
FULL: drop
(CUR) a! @ ,
0 L: dup (NLIT) a! @ xor if DONE
drop dup (LITS) + a! @ , 1 + jump L
DONE: drop drop
(CG) a! @ ,
0 (CG)+12 a! !
L: (CG)+12 a! @ (CG)+2 a! @ xor if DONE
drop (CG)+12 a! @ dup 1 + ! (CG)+3 + a! @ ,
jump L
DONE: drop
jump (CG-RESET)
EMPTY: drop ;
\ ( op -- ) any opcode that is neither a branch nor @p
: (OP,)
dup push (PUT) pop
if ENDS -1 + if ENDS \ ; and ex end the word
drop (SLOT) a! @ -6 + if ENDS drop ; \ and so does its last slot
dup (CG)+9 a! ! (PUT)
(CG)+9 a! @ if ENDS -1 + if ENDS \ ; and ex end the word
drop (CG)+1 a! @ -6 + if ENDS drop ; \ and so does its last slot
ENDS: drop jump (FLUSH)
\ ( x -- ) @p and its value
: (LIT,)
(NLIT) a! @ dup 1 + ! \ x n
(LITS) + a! !
(CG)+2 a! @ dup 1 + ! \ x n
(CG)+3 + a! !
8 (PUT)
(SLOT) a! @ -6 + if FULL drop ;
(CG)+1 a! @ -6 + if FULL drop ;
FULL: drop jump (FLUSH)
\ ( -- addr ) where the next word will go: a branch target
: (LABEL) (FLUSH) HERE ;
\ ( op -- ref ) a branch (jump 2, call 3, next 5, if 6, -if 7) whose address
\ is filled in later by (RESOLVE). ref is the branch's word address times 8,
\ plus its slot.
: (BRANCH>)
(SLOT) a! @ CG-BSLOTS - -if MOVE drop jump PLACED
\ ( op -- ) a branch (jump 2, call 3, next 5, if 6, -if 7) whose address is
\ not filled in yet. Its ref -- the branch's word address times 8, plus its
\ slot -- is left in (CG)+10. It ends by jumping to (FLUSH), so that the
\ chain of calls under it is one shorter.
: (BRANCH0)
(CG)+1 a! @ CG-BSLOTS - -if MOVE drop jump PLACED
MOVE: drop (FLUSH)
PLACED:
\ the word will be appended at HERE; its literals, if any, come after it
HERE 2* 2* 2* (SLOT) a! @ + push
HERE 2* 2* 2* (CG)+1 a! @ + (CG)+10 a! !
(PUT)
6 (SLOT) a! ! (FLUSH)
pop ;
6 (CG)+1 a! ! jump (FLUSH)
\ ( target ref -- ) put the address into the branch at ref
: (RESOLVE)
dup 7 and (MASK) push \ target ref R: mask
2/ 2/ 2/ a! \ target A: the branch's word
pop dup push and \ the address bits
\ ( ref -- ) put the address in (CG)+11 into the branch at ref
: (RESOLVE1)
dup 7 and (MASK) push \ ref R: mask
2/ 2/ 2/ a! \ A: the branch's word
(CG)+11 b! @b pop dup push and \ the address bits
@ pop inv and + ! ;
\ ( target ref -- ) put the address into the branch at ref
: (RESOLVE) SWAP (CG)+11 a! ! jump (RESOLVE1)
\ ( op -- ref ) the same, leaving the ref, for (RESOLVE) to fill in later
: (BRANCH>) (BRANCH0) (CG)+10 a! @ ;
\ ( target op -- ) a branch to a known address
: (BRANCH,) (BRANCH>) jump (RESOLVE)
: (BRANCH,) SWAP (CG)+11 a! ! (BRANCH0) (CG)+10 a! @ jump (RESOLVE1)
: (JUMP,) ( addr -- ) 2 jump (BRANCH,)
: (CALL,) ( addr -- ) 3 jump (BRANCH,)