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
This commit is contained in:
@@ -122,9 +122,14 @@ static int line_cb(void *self, unsigned asker, char *text)
|
||||
if (asker >= MAX_PLACES || the->reading_nucleus[asker]) return -1;
|
||||
return next_line(&reads[asker], text, V4_MANAGE_LINE);
|
||||
}
|
||||
static void words_attach(unsigned place, v4_fabric_node *x);
|
||||
static void parity_cb(void *self, unsigned place, v4_cell number, const v4_node *n)
|
||||
{
|
||||
(void)self;
|
||||
{
|
||||
v4_fabric_node *x = v4_fabric_node_at(&the->f, place);
|
||||
if (x && &x->n == n) words_attach(place, x); /* it has its system: its words are kept from now */
|
||||
}
|
||||
if (the->parity) the->parity(the->self, place, number, n);
|
||||
}
|
||||
|
||||
@@ -132,12 +137,129 @@ static void parity_cb(void *self, unsigned place, v4_cell number, const v4_node
|
||||
|
||||
static unsigned place_of_device(const void *self) { return (unsigned)((const v4_device *)self - the->kernel); }
|
||||
|
||||
/* ---- the words' accounts (#189, Phase 2; mesh.h) ----------------------------------------------------- */
|
||||
|
||||
#define WORD_BITS_BYTES ((unsigned)V4_NODE_WORDS / 8u)
|
||||
|
||||
static unsigned char *bits_of(unsigned place) { return the->is_word + (unsigned long)place * WORD_BITS_BYTES; }
|
||||
static v4_mesh_account *accounts_of(unsigned place) { return the->accounts + (unsigned long)place * V4_MESH_WORD_IDS; }
|
||||
static int is_word(unsigned place, v4_cell a)
|
||||
{
|
||||
return a > 4 && a < (v4_cell)V4_NODE_WORDS && (bits_of(place)[a >> 3] >> (a & 7)) & 1u;
|
||||
}
|
||||
|
||||
/* an entry has been made, its code at xt: it has a record from now */
|
||||
static void word_made(unsigned place, const v4_node *n, v4_cell xt)
|
||||
{
|
||||
v4_cell id;
|
||||
if (!the->accounts || xt <= 4 || xt >= (v4_cell)V4_NODE_WORDS) return;
|
||||
bits_of(place)[xt >> 3] |= (unsigned char)(1u << (xt & 7));
|
||||
id = n->mem[xt - 4];
|
||||
if (id >= 0 && id < (v4_cell)V4_MESH_WORD_IDS) {
|
||||
v4_mesh_account *a = &accounts_of(place)[id];
|
||||
a->calls = 0; a->since = 0; a->xt = xt;
|
||||
}
|
||||
}
|
||||
|
||||
/* every entry from word address w up has gone: their records go, and the owner forgets their IDs */
|
||||
static void words_gone(unsigned place, const v4_node *n, v4_cell w)
|
||||
{
|
||||
v4_cell a;
|
||||
if (!the->accounts) return;
|
||||
if (w < 5) w = 5;
|
||||
for (a = w; a < (v4_cell)V4_NODE_WORDS; a++) {
|
||||
v4_cell id;
|
||||
if (!is_word(place, a)) continue;
|
||||
bits_of(place)[a >> 3] &= (unsigned char)~(1u << (a & 7));
|
||||
id = n->mem[a - 4];
|
||||
if (id >= 0 && id < (v4_cell)V4_MESH_WORD_IDS && accounts_of(place)[id].xt == a) {
|
||||
v4_mesh_account *r = &accounts_of(place)[id];
|
||||
r->calls = 0; r->since = 0; r->xt = 0;
|
||||
if (the->forgot) the->forgot(the->self, place, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* at `call` (exec.h): a word's record counts one; any other target, nothing */
|
||||
static void on_call(void *ctx, v4_node *n, v4_cell target)
|
||||
{
|
||||
unsigned place = place_of_device(ctx);
|
||||
v4_cell id;
|
||||
if (!is_word(place, target)) return;
|
||||
id = n->mem[target - 4];
|
||||
if (id >= 0 && id < (v4_cell)V4_MESH_WORD_IDS) { accounts_of(place)[id].calls++; accounts_of(place)[id].since++; }
|
||||
}
|
||||
|
||||
/* is the entry at xt named `name`? (dict.f18: the name's word address at xt-2, its count byte, then its
|
||||
* characters, four to a cell, low byte first) */
|
||||
static int named(const v4_node *n, v4_cell xt, const char *name)
|
||||
{
|
||||
v4_cell nfa = n->mem[xt - 2];
|
||||
unsigned len = 0, i;
|
||||
while (name[len]) len++;
|
||||
if (nfa < 0 || nfa >= xt) return 0;
|
||||
for (i = 0; i <= len; i++) {
|
||||
v4_ucell c = (v4_ucell)n->mem[nfa + (v4_cell)((i) / 4u)];
|
||||
unsigned char b = (unsigned char)((c >> (8u * (i % 4u))) & 0xFFu);
|
||||
if (i == 0 ? b != len : b != (unsigned char)name[i - 1]) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* the node in `place` is sealed: its words as they are now each have a record, and from now on it
|
||||
* tells of entries made and gone, and every call to a word counts */
|
||||
static void words_attach(unsigned place, v4_fabric_node *x)
|
||||
{
|
||||
const v4_image *im = the->im;
|
||||
v4_cell xt, told = 0, gone = 0;
|
||||
unsigned k;
|
||||
if (!the->accounts || !the->is_word) return;
|
||||
for (k = 0; k < WORD_BITS_BYTES; k++) bits_of(place)[k] = 0;
|
||||
for (k = 0; k < V4_MESH_WORD_IDS; k++) { accounts_of(place)[k].calls = 0; accounts_of(place)[k].since = 0; accounts_of(place)[k].xt = 0; }
|
||||
for (xt = x->n.mem[im->latest], k = 0; xt > 4 && xt < (v4_cell)V4_NODE_WORDS && k < V4_NODE_WORDS; xt = x->n.mem[xt - 1], k++) {
|
||||
word_made(place, &x->n, xt);
|
||||
if (!told && named(&x->n, xt, "(TELL-DEFINED)")) told = xt;
|
||||
if (!gone && named(&x->n, xt, "(TELL-FORGOTTEN)")) gone = xt;
|
||||
}
|
||||
if (told && gone) {
|
||||
x->n.mem[im->word_defined] = told;
|
||||
x->n.mem[im->word_forgotten] = gone;
|
||||
}
|
||||
x->es.on_call = on_call;
|
||||
x->es.on_call_ctx = &the->kernel[place];
|
||||
}
|
||||
|
||||
const v4_mesh_account *v4_mesh_account_at(const v4_mesh *ms, unsigned place, v4_cell xt)
|
||||
{
|
||||
v4_fabric_node *x = v4_fabric_node_at(&ms->f, place);
|
||||
v4_cell id;
|
||||
the = (v4_mesh *)ms;
|
||||
if (!x || !ms->accounts || place >= ms->capacity || !is_word(place, xt)) return 0;
|
||||
id = x->n.mem[xt - 4];
|
||||
if (id < 0 || id >= (v4_cell)V4_MESH_WORD_IDS || ms->accounts[(unsigned long)place * V4_MESH_WORD_IDS + (unsigned long)id].xt != xt) return 0;
|
||||
return &ms->accounts[(unsigned long)place * V4_MESH_WORD_IDS + (unsigned long)id];
|
||||
}
|
||||
|
||||
void v4_mesh_charge(v4_mesh *ms, unsigned place, void (*each)(void *ctx, v4_cell id, v4_uheat_t calls), void *ctx)
|
||||
{
|
||||
unsigned k;
|
||||
if (!ms->accounts || place >= ms->capacity) return;
|
||||
for (k = 0; k < V4_MESH_WORD_IDS; k++) {
|
||||
v4_mesh_account *a = &ms->accounts[(unsigned long)place * V4_MESH_WORD_IDS + k];
|
||||
if (a->since == 0) continue;
|
||||
each(ctx, (v4_cell)k, a->since);
|
||||
a->since = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int kernel_take(void *self, v4_cell value) /* a request */
|
||||
{
|
||||
unsigned place = place_of_device(self);
|
||||
v4_fabric_node *x = v4_fabric_node_at(&the->f, place);
|
||||
if (!x) return 1;
|
||||
if (v4_blocks_serve(&the->blocks[place], &x->n, value)) return 1;
|
||||
if (value == V4_REQ_WORD_DEFINED) { word_made(place, &x->n, v4_dstack_pop(&x->n.ds)); return 1; }
|
||||
if (value == V4_REQ_WORD_FORGOTTEN) { words_gone(place, &x->n, v4_dstack_pop(&x->n.ds)); return 1; }
|
||||
if (place == the->hera_place && the->serve && value >= 1 && value <= (v4_cell)the->word_count) {
|
||||
the->serve(&x->n, (unsigned)value);
|
||||
return 1;
|
||||
@@ -198,6 +320,11 @@ static void gone_cb(void *self, unsigned place, v4_fabric_node *was)
|
||||
{
|
||||
(void)self;
|
||||
if (place < MAX_PLACES) { the->reading_nucleus[place] = 0; reads[place].p = reads[place].end = 0; }
|
||||
if (the->accounts && place < the->capacity) { /* its words' records go with it */
|
||||
unsigned k;
|
||||
for (k = 0; k < WORD_BITS_BYTES; k++) bits_of(place)[k] = 0;
|
||||
for (k = 0; k < V4_MESH_WORD_IDS; k++) { accounts_of(place)[k].calls = 0; accounts_of(place)[k].since = 0; accounts_of(place)[k].xt = 0; }
|
||||
}
|
||||
if (the->gone) the->gone(the->self, place);
|
||||
if (the->unroom) the->unroom(the->self, was);
|
||||
}
|
||||
@@ -432,6 +559,7 @@ static int boot(v4_mesh *ms)
|
||||
if (!load(ms, "v4:forth79.4th")) return 0;
|
||||
for (i = 0; i < ms->capsule_count; i++) if (!load(ms, ms->capsules[i])) return 0;
|
||||
v4_image_seal(&h->n, ms->im);
|
||||
words_attach(ms->hera_place, h); /* her words' accounts, from now (#189, Phase 2) */
|
||||
|
||||
/* POST, run on her as the kernel's, through her console; then she is put back as sealed */
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user