ENGINE step 4 Phase 2 (plan docs/v4.0.0/plans/2026-11-13-engine-step4-phase2-word-accounts.md). Rulings, Captain Bob 2026-11-13: "ID field in the header"; "Per heartbeat tick"; the ACL "All of it in Phase 4". - The word ID, at xt-4 before the flags (ENGINE.md 3b). The system's words are numbered as they are assembled, by a new directive, #id, (and #ids, the count), in the C assembler (text.c) and in the machine ROM's (capsules/v4/rom), which counts in the system's own (NEXT-ID). (HEADER) numbers later words; FORGET gives back the IDs it frees, always the newest; COLD resets to (BOOT-ID); (SEAL) records it. All 313 system headers carry the cell. - The kernel's records (v4/mesh.h): (TELL-DEFINED) and (TELL-FORGOTTEN), requests -19/-20 on port 0, which the kernel puts in (WORD-DEFINED)/(WORD-FORGOTTEN) when it seals a node, and reads the system's own words once. A bit per address marks a word's xt (calls into the ROM or headerless code are not words); at call, the bit test and a count. v4_mesh_charge hands the owner the calls since. - The Stadium: stadium_words_charge -- N calls as N of stadium_word_dispatch's dispatches, shared by calls when the reservoir is short; sk_v4.c charges every node's words at the tick beside its opcodes, and stadium_word_forget before an ID is used again. - Tests first: test_text.c, test_host_dict.c, test_host_quit.c (numbering, FORGET, COLD), test_mesh_boot.c (44 checks), kernel/tests/test_stadium_words.c; qemu_session.py: Hera's words admitted after POST. - amd64: Stadium words: promotions=141 after POST, CONSERVED; POST 539/539; word_count=377; dict_hash 0xa854ba15b437d288 (the new cell); the boot 142 s against Phase 1's 153 s. - v4 test, test-32: all pass; v4-checks: 80 programs under the sanitizers, 0 failures; the hosted builds alike. docs/reference/v4-dictionary.tsv: the two new words; FABRIC-3 XLVIII.24. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
247 lines
12 KiB
Plaintext
247 lines
12 KiB
Plaintext
\ dict.f18 -- the dictionary: its space, its entries, and looking a name up.
|
|
\
|
|
\ DECOMPOSITION.md 5.12 and 5.13: HERE ALIGN ALLOT , C, 2, PAD LATEST, FIND ' ,
|
|
\ and the entry-field words >LINK LFA LINK> >NAME NFA NAME> CFA PFA >BODY
|
|
\ TRAVERSE SMUDGE HIDDEN. Part of the compiler capsule: it runs on the host
|
|
\ node. Rests on core.f18 and input.f18.
|
|
\
|
|
\ AN ENTRY. A word's execution address (xt) is the address of its code, and
|
|
\ everything else is found from it:
|
|
\
|
|
\ name the count byte, then the characters, four bytes to a cell,
|
|
\ padded with zeros to a whole cell
|
|
\ xt - 4 the word ID: what the kernel's record of the word is kept by
|
|
\ (ENGINE.md 3b; #189 Phase 2, Captain Bob 2026-11-13: "ID field in
|
|
\ the header"). The next is in (NEXT-ID); FORGET gives back the
|
|
\ IDs of what it removes, which are always the newest
|
|
\ xt - 3 flags: 1 immediate, 2 hidden, 4 data
|
|
\ xt - 2 the word address of the name
|
|
\ xt - 1 the link: the xt of the entry before this one, 0 for the first
|
|
\ xt the code
|
|
\
|
|
\ A name holds at most 31 characters; longer ones are cut to 31 both when
|
|
\ defined and when looked up, so 31 characters are significant (FORTH-79).
|
|
\ A "data" word is one whose code is a single call to its run-time routine,
|
|
\ with its parameter field in the cell after it: xt + 1. For any other word
|
|
\ the parameter field is the code itself.
|
|
\
|
|
\ THE SPACE. DP is a byte address, so that C, can pack characters; HERE is
|
|
\ the next whole cell. An address unit is a cell (D-1): ALLOT counts cells.
|
|
\
|
|
\ Constants the loader supplies:
|
|
\ DP word address of the variable: the next free byte
|
|
\ (LATEST) word address of the variable: the xt of the newest entry of the
|
|
\ FORTH vocabulary, or 0
|
|
\ CONTEXT CURRENT word addresses of the variables: each holds the address
|
|
\ of a vocabulary's head cell -- the cell that holds the xt of
|
|
\ that vocabulary's newest entry. FORTH's head cell is (LATEST).
|
|
\
|
|
\ VOCABULARIES (FORTH-79). A name is looked up in the CONTEXT vocabulary and
|
|
\ then, if that is not FORTH, in FORTH. A new entry goes into the CURRENT
|
|
\ vocabulary: it is linked to that vocabulary's newest entry and becomes it.
|
|
\ DBASE DLIMIT byte addresses of the start and end of dictionary space
|
|
\ PAD byte address of the scratch area for strings, 84 bytes or more
|
|
\ (D) word address of five cells of scratch for this file
|
|
\ (WORD-DEFINED) (WORD-FORGOTTEN) word addresses of two variables: see
|
|
\ "telling the kernel" below
|
|
\ PAD is in line.
|
|
|
|
macro 4* 2* 2* endmacro
|
|
macro 4/ 2/ 2/ endmacro
|
|
macro OR over inv and xor endmacro
|
|
|
|
\ ---- the space (5.12) ------------------------------------------------------
|
|
\ These reach DP through B and leave A alone, so that , and C, can keep what
|
|
\ they are storing in A while the space is taken: nothing of theirs is then
|
|
\ on the data stack under (DP+), which itself needs three cells (D-2).
|
|
|
|
: (DERR) ( -- 0 ) NODE-ERROR b! 5 !b 0 ; \ dictionary full
|
|
|
|
\ ( n -- flag ) move DP by n bytes if it stays inside the dictionary space
|
|
\ and say -1; otherwise leave it, set NODE-ERROR and say 0. The two
|
|
\ subtractions are x + ~y + 1, which unlike the in-line - needs no return
|
|
\ entry: these words are called at the bottom of long chains of calls.
|
|
: (DP+)
|
|
DP b! @b +
|
|
dup DBASE inv + 1 + -if LOW drop drop jump (DERR) \ new - DBASE
|
|
LOW: drop dup inv DLIMIT + 1 + -if HIGH drop drop jump (DERR) \ DLIMIT - new
|
|
HIGH: drop DP b! !b -1 ;
|
|
|
|
set HDR-NFA here ", HERE #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: HERE ( -- addr ) DP b! @b 3 + 4/ ;
|
|
set HDR-NFA here ", ALIGN #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: ALIGN ( -- ) DP b! @b 3 + -4 and !b ;
|
|
set HDR-NFA here ", ALLOT #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: ALLOT ( n -- ) ALIGN 4* (DP+) drop ;
|
|
|
|
\ , calls nothing: the code generator calls it from as deep as anything goes.
|
|
set HDR-NFA here ", , #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: , ( x -- )
|
|
a!
|
|
DP b! @b 3 + -4 and \ the next whole cell, as a byte address
|
|
dup inv DLIMIT + -3 + -if ROOM \ DLIMIT - (it + 4)
|
|
drop drop NODE-ERROR b! 5 !b ;
|
|
ROOM: drop dup 4 + !b \ DP is past it
|
|
4/ b! a !b ;
|
|
set HDR-NFA here ", C, #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: C, ( c -- )
|
|
a! 1 (DP+) if FULL
|
|
drop a DP b! @b -1 + jump C!
|
|
FULL: drop ;
|
|
set HDR-NFA here ", 2, #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: 2, ( lo hi -- ) SWAP , , ; \ the low cell first, as v3
|
|
|
|
set HDR-NFA here ", LATEST #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: LATEST ( -- xt ) CURRENT a! @ a! @ ; \ the newest entry of the CURRENT vocabulary
|
|
|
|
\ ---- the fields of an entry (5.13) -----------------------------------------
|
|
|
|
: (FLAGS) ( xt -- addr ) -3 + ;
|
|
set HDR-NFA here ", >LINK #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: >LINK ( xt -- addr ) -1 + ;
|
|
set HDR-NFA here ", LFA #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: LFA ( xt -- addr ) jump >LINK
|
|
set HDR-NFA here ", LINK> #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: LINK> ( addr -- xt ) a! @ ;
|
|
set HDR-NFA here ", >NAME #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: >NAME ( xt -- baddr ) -2 + a! @ 4* ;
|
|
set HDR-NFA here ", NFA #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: NFA ( xt -- baddr ) jump >NAME
|
|
set HDR-NFA here ", NAME> #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: NAME> ( baddr -- xt ) dup C@ 31 and 4 + 4/ push 4/ pop + 4 + ;
|
|
set HDR-NFA here ", CFA #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: CFA ( xt -- xt ) ;
|
|
set HDR-NFA here ", PFA #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: PFA ( xt -- addr ) dup (FLAGS) a! @ 4 and if CODE drop 1 + ; CODE: drop ;
|
|
set HDR-NFA here ", >BODY #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: >BODY ( xt -- addr ) jump PFA
|
|
|
|
\ ( baddr n -- baddr' ) as v3: for n > 0, from a name's count byte to the
|
|
\ byte after its last character; otherwise the address unchanged.
|
|
set HDR-NFA here ", TRAVERSE #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: TRAVERSE
|
|
-if NN drop ;
|
|
NN: if ZERO drop dup C@ 31 and + 1 + ;
|
|
ZERO: drop ;
|
|
|
|
set HDR-NFA here ", SMUDGE #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: SMUDGE ( -- ) LATEST if NONE (FLAGS) a! @ 2 xor ! ; NONE: drop ;
|
|
set HDR-NFA here ", HIDDEN #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: HIDDEN ( -- ) LATEST if NONE (FLAGS) a! @ 2 OR ! ; NONE: drop ;
|
|
|
|
\ ---- telling the kernel (docs/v4.0.0/ENGINE.md 3b) ------------------------------
|
|
\ A word's code is here on the node; its accounts are a record its kernel
|
|
\ keeps. So the kernel has to be told of every entry that is made and of
|
|
\ every entry that goes. An entry is made in one place, (HEADER); entries go
|
|
\ in two, FORGET and COLD (system.f18). Each tells through a variable that
|
|
\ holds the xt of a word to run, or 0 for a node whose host keeps no records:
|
|
\ (WORD-DEFINED) ( xt -- ) an entry has been made; its code will be at xt
|
|
\ (WORD-FORGOTTEN) ( w -- ) every entry at word address w or above is gone
|
|
\ On the bare-metal system the kernel puts words of its own there.
|
|
|
|
\ ( -- ) tell of the entry just made, whose code starts at HERE
|
|
: (DEFINED)
|
|
(WORD-DEFINED) b! @b if NONE HERE SWAP push ;
|
|
NONE: drop ;
|
|
|
|
\ ( w -- ) tell that the entries from word address w up have gone
|
|
: (FORGOTTEN)
|
|
(WORD-FORGOTTEN) b! @b if NONE push ;
|
|
NONE: drop drop ;
|
|
|
|
\ ( xt -- ) and ( w -- ) the words a kernel that keeps records puts in (WORD-DEFINED) and
|
|
\ (WORD-FORGOTTEN): requests -19 and -20 to the kernel on the node's port 0, which takes the xt or
|
|
\ the word address off the stack (v4/mesh.h; #189, Phase 2). A host that keeps none leaves both 0.
|
|
set HDR-NFA here ", (TELL-DEFINED) #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: (TELL-DEFINED) -19 (PORT) b! !b ;
|
|
set HDR-NFA here ", (TELL-FORGOTTEN) #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: (TELL-FORGOTTEN) -20 (PORT) b! !b ;
|
|
|
|
\ ---- making an entry -------------------------------------------------------
|
|
\ (D)+0 where the name starts (D)+1 the name being looked up
|
|
\ (D)+2 the name being copied, or the entry being looked at
|
|
\ (D)+3 a count (D)+4 whether a lookup has gone on to FORTH
|
|
|
|
\ ( -- bytes ) what an entry for a name of (D)+3 characters takes: its name
|
|
\ cells and four more
|
|
: (NEED) (D)+3 a! @ 4 + -4 and 16 + ;
|
|
|
|
\ ( baddr -- ) an entry for the counted string at baddr; its code starts at
|
|
\ HERE afterwards. An empty name, or no room, makes nothing and sets
|
|
\ NODE-ERROR.
|
|
: (HEADER)
|
|
dup (D)+2 a! ! \ (CLIP), in line
|
|
C@ -32 + -if LONG drop jump CLIPPED
|
|
LONG: drop 31 (D)+2 a! @ C!
|
|
CLIPPED:
|
|
(D)+2 a! @ C@ if EMPTY \ len
|
|
(D)+3 a! !
|
|
ALIGN
|
|
(NEED) (DP+) if FULL drop \ there is room;
|
|
(NEED) NEGATE (DP+) drop \ DP back where it was
|
|
HERE (D) a! ! \ where the name starts
|
|
(D)+3 a! @ C,
|
|
L: (D)+3 a! @ if DONE -1 + !
|
|
(D)+2 a! @ 1 + dup ! C@ C,
|
|
jump L
|
|
DONE: drop
|
|
P: DP b! @b 3 and if ALIGNED drop 0 C, jump P
|
|
ALIGNED: drop
|
|
(NEXT-ID) a! @ dup 1 + ! , \ its word ID, and the next
|
|
0 , (D) a! @ , LATEST ,
|
|
HERE CURRENT a! @ a! !
|
|
jump (DEFINED)
|
|
FULL: drop ; \ (DP+) has set NODE-ERROR
|
|
EMPTY: drop NODE-ERROR b! 6 !b ; \ name missing
|
|
|
|
\ ---- looking a name up -----------------------------------------------------
|
|
|
|
\ ( baddr -- xt | 0 ) the newest visible entry named by the counted string.
|
|
\ Written out in one piece -- the string cut to 31, each entry's flags and
|
|
\ name fetched, the two names compared byte by byte -- so that the only call
|
|
\ it makes is C@: it is called from the interpreter for every word of every
|
|
\ line, with the user's return stack beneath it.
|
|
: (LOOKUP)
|
|
dup (D)+1 a! ! \ cut the string to 31 characters
|
|
C@ -32 + -if LONG drop jump CUT
|
|
LONG: drop 31 (D)+1 a! @ C!
|
|
CUT:
|
|
0 (D)+4 a! ! \ not yet on to FORTH
|
|
CONTEXT a! @ a! @
|
|
L: if END
|
|
(D)+2 a! !
|
|
(D)+2 a! @ -3 + a! @ 2 and if VISIBLE drop jump OLDER
|
|
VISIBLE: drop
|
|
(D)+2 a! @ -2 + a! @ 4* (D) a! ! \ its name
|
|
(D)+1 a! @ C@ 1 + (D)+3 a! ! \ the count byte and the characters
|
|
C: (D)+3 a! @ if SAME -1 + dup ! \ k
|
|
dup (D) a! @ + C@ \ k c1
|
|
SWAP (D)+1 a! @ + C@ \ c1 c2
|
|
xor if EQ drop jump OLDER
|
|
EQ: drop jump C
|
|
SAME: drop (D)+2 a! @ ;
|
|
OLDER: (D)+2 a! @ -1 + a! @ jump L
|
|
END: (D)+4 a! @ if FIRST drop ; \ FORTH has been searched: not found
|
|
FIRST: drop -1 (D)+4 a! !
|
|
CONTEXT a! @ (LATEST) xor if ISFORTH \ CONTEXT is FORTH: that was it
|
|
drop drop (LATEST) a! @ jump L
|
|
ISFORTH: drop ;
|
|
|
|
\ ( -- xt | 0 ) FORTH-79: the compilation address of the next word in the
|
|
\ input stream, or 0 if it is not in the dictionary.
|
|
set HDR-NFA here ", FIND #id, #, 0 #, HDR-NFA #, HDR-LINK set HDR-LINK here
|
|
: FIND 32 WORD jump (LOOKUP)
|
|
|
|
\ ( -- addr ) FORTH-79: the parameter field address of the next word in the
|
|
\ input stream. Not found is an error: 0, and NODE-ERROR is set. This is '
|
|
\ when interpreting; compile.f18 adds what ' does when compiling.
|
|
\ ( -- 0 ) say that the word WORD has just left is not known, as v3 does:
|
|
\ UNKNOWN WORD: 'xxx'
|
|
: (UNKNOWN)
|
|
$4E4B4E55 (EMIT4) $204E574F (EMIT4) $44524F57 (EMIT4) $27203A (EMIT4)
|
|
WBUF COUNT TYPE 39 EMIT CR
|
|
NODE-ERROR b! -1 !b 0 ;
|
|
|
|
: (') 32 WORD (LOOKUP) if MISSING jump PFA
|
|
MISSING: drop jump (UNKNOWN)
|