Files
LithosAnanke/capsules/v4/rom/sym.f18
T
Claude e8e7ec8b9f feat(v4): word accounts at call -- the word ID in the header, the kernel's records, the Stadium charged per tick (#189)
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
2026-11-13 19:12:44 +00:00

159 lines
5.5 KiB
Plaintext

\ sym.f18 -- the ROM's symbol table.
\
\ A name the ROM assembles is given a kind and a value: 1 a word (its
\ address), 2 a constant, 3 a macro, 4 a set variable. Kind 0 is a name
\ only branched to so far: a placeholder that the forward branches to it
\ wait on (word.f18), which (DEFINE) fills in. The rules are
\ v4/src/text.c's define: a name may not be an opcode, a reserved word, a
\ label (NAME:), a number or start with &; nothing is defined twice, except
\ that a set variable may be set again and a placeholder be defined.
\
\ The table is used only while the ROM assembles, and lives in dictionary
\ space: 256 hash heads at (SYMH); records of four cells -- link, name,
\ kind, value -- growing up from SYMREC; names and macro bodies, growing
\ down from POOLTOP. When the two would meet, "too many names". (RECN) and
\ (POOLN) count the cells used, so an empty table is all zeroes. Names are
\ counted strings, the form the token is held in, compared a cell at a time.
\ (REC) (CNT) (PTR) (I) (WI) are working cells. Every routine uses A; some B.
\ ( p q -- f ) 1 if the counted strings at p and q are the same
: (SAME2?)
(I) a! ! (PTR) a! !
(PTR) a! @ a! @ 255 and 4 + 2/ 2/
L: (PTR) a! @ b! @b (I) a! @ a! @ xor if EQ drop drop 0 ;
EQ: drop (PTR) a! @ 1 + ! (I) a! @ 1 + !
-1 + if DONE jump L
DONE: drop 1 ;
\ ( addr -- f ) 1 if the counted string at addr is the token
: (SAME?) (TOK) jump (SAME2?)
\ ( table -- i | -1 ) which counted string of a table the token is; the
\ table ends with a zero cell
: (WALK)
0 (WI) a! !
L: dup a! @ if END
drop dup (SAME?) if NO
drop drop (WI) a! @ ;
NO: drop dup a! @ 255 and 4 + 2/ 2/ +
(WI) a! @ 1 + ! jump L
END: drop drop -1 ;
\ the opcodes' names, in the order of their numbers
: (MNEMONICS)
", ; ", ex ", jump ", call ", unext ", next ", if ", -if
", @p ", @+ ", @b ", @ ", !p ", !+ ", !b ", !
", +* ", 2* ", 2/ ", inv ", + ", and ", xor ", drop
", dup ", pop ", over ", a ", nop ", push ", b! ", a!
#, 0
\ the ROM's reserved words: its notation and its directives
: (RESERVED)
", : ", macro ", endmacro ", FOR ", NEXT ", UNEXT
", constant ", set ", here ", #, ", ", ", store
", start ", #id, ", #ids
#, 0
\ ( -- op | -1 ) which opcode the token names
: (OPCODE) &(MNEMONICS) jump (WALK)
\ ( -- f ) 1 if the token is a reserved word
: (RESERVED?) &(RESERVED) (WALK) -if Y drop 0 ;
Y: drop 1 ;
\ ( p -- h ) the hash, 0 .. 255, of the counted string at p, from its
\ first cell, and its second if the name runs into it (4 characters or more:
\ the cell after a shorter name is not its own)
: (HASH-AT)
a! @+ dup 255 and -4 + -if TWO drop jump MIX
TWO: drop @ xor
MIX: dup 2/ 2/ 2/ 2/ 2/ 2/ 2/ 2/ +
dup 2/ 2/ 2/ 2/ 2/ 2/ 2/ 2/ 2/ 2/ 2/ 2/ 2/ 2/ 2/ 2/ +
255 and ;
\ ( p -- rec | 0 ) the newest record whose name is the counted string at p,
\ placeholders included
: (FIND-AT)
dup (T3) a! ! (HASH-AT) (SYMH) + a! @
L: if NONE
dup (REC) a! ! 1 + a! @ (T3) a! @ (SAME2?) if NO
drop (REC) a! @ ;
NO: drop (REC) a! @ a! @ jump L
NONE: ;
\ ( -- rec | 0 ) the token's record, placeholders included
: (FIND0) (TOK) jump (FIND-AT)
\ ( -- rec | 0 ) the token's record, if it has been defined
: (FIND)
(FIND0) if NONE
dup 2 + a! @ if PH drop ;
PH: drop drop 0 ;
NONE: ;
\ ( n -- addr | 0 ) n cells of the pool, below what it holds; 0 if they
\ would meet the records ("too many names")
: (ALLOC)
dup (CNT) a! !
POOLTOP (POOLN) a! @ inv 1 + + (CNT) a! @ inv 1 + +
dup SYMREC (RECN) a! @ + 4 + inv 1 + +
-if ROOM drop drop drop ERR-NAMES (FAIL) 0 ;
ROOM: drop push drop
(POOLN) a! @ (CNT) a! @ + (POOLN) a! !
pop ;
\ ( from to n -- ) copy n cells, n at least 1
: (COPY)
(CN) a! ! (CTO) a! ! (CFROM) a! !
L: (CN) a! @ if D drop
(CFROM) a! @ a! @ (CTO) a! @ b! !b
(CFROM) a! @ 1 + ! (CTO) a! @ 1 + ! (CN) a! @ -1 + ! jump L
D: drop ;
\ ( value kind p -- rec | 0 ) a new record for the counted string at p,
\ the name copied into the pool; 0 if there is no room
: (NEW-REC)
dup (T3) a! !
a! @ 255 and 4 + 2/ 2/ (ALLOC)
if FULL
dup (T4) a! !
push (T3) a! @ pop (T3) a! @ a! @ 255 and 4 + 2/ 2/ (COPY)
SYMREC (RECN) a! @ + (REC) a! !
(T4) a! @ (REC) a! @ 1 + a! !
(REC) a! @ 2 + a! !
(REC) a! @ 3 + a! !
(T4) a! @ (HASH-AT) (SYMH) + dup a! @ (REC) a! @ a! !
(REC) a! @ over a! ! drop
(RECN) a! @ 4 + !
(REC) a! @ ;
FULL: drop drop drop 0 ;
\ ( -- f ) 1 if the token cannot be a name
: (BADNAME?)
(OPCODE) -if BAD
drop (RESERVED?) if NOTRES drop 1 ;
NOTRES: drop (NUMBER) push drop pop if NOTNUM drop 1 ;
NOTNUM: drop 0 (CHAR@) 38 xor if AMP
drop (TOKN) a! @ -1 + if ONE
(CHAR@) 58 xor if COLON drop 0 ;
COLON: drop 1 ;
ONE: drop 0 ;
AMP: drop 1 ;
BAD: drop 1 ;
\ ( value kind -- ) give the token a kind and a value. A placeholder is
\ filled in, and the branches waiting on it are told (word.f18, (DEFINED)).
: (DEFINE)
(BADNAME?) if OKNAME drop drop drop ERR-BAD-NAME jump (FAIL)
OKNAME: drop
(FIND0) if NEW
dup 2 + a! @ if FILL drop
over 4 xor if K4 drop drop drop drop ERR-DEFINED jump (FAIL)
K4: drop 2 + a! @ 4 xor if BOTH drop drop drop ERR-DEFINED jump (FAIL)
BOTH: drop drop (REC) a! @ 3 + a! ! ;
FILL: drop dup (REC) a! !
2 + a! !
(REC) a! @ 3 + a! !
(REC) a! @ jump (DEFINED)
NEW: drop (TOK) (NEW-REC) drop ;