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
237 lines
14 KiB
C
237 lines
14 KiB
C
/* SPDX-License-Identifier: LicenseRef-Starship-1.0
|
|
* Copyright (c) 2023-2026 Robert A. James
|
|
*/
|
|
/* test_mesh_boot.c -- the kernel's boot of the fabric (v4/mesh.h), on the host, with this program as
|
|
* its owner, as the kernel will be (#189, ENGINE step 4 Phase 1, Task 5b; Captain Bob, 2026-11-13:
|
|
* "Build it to proper spec"). No scheduler: every node steps together.
|
|
*
|
|
* The boot: the nucleus assembled by the ROM, Hera born empty and given it, FORTH-79, POST, the
|
|
* kernel's capsules -- and the same parity lines v4_boot_run prints. Then: a line; NODE-ME; a node
|
|
* born (11 2 2 BIRTH), given the nucleus and FORTH-79 by Hera, answering a line of its own; and
|
|
* killed, its room given back.
|
|
*
|
|
* Linked as the hosted product is (v4/Makefile), with mesh.c, manage.c and a mesh node's ROM.
|
|
*/
|
|
#include "v4/mesh.h"
|
|
#include "v4/message.h"
|
|
#include "block_subsystem.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
static int failures = 0, checks = 0;
|
|
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
|
|
|
|
#define PLACES 8u
|
|
static v4_place places[PLACES];
|
|
static v4_wire_queue queues[32];
|
|
static v4_manage_entry reg[PLACES];
|
|
static v4_blocks blocks[PLACES];
|
|
static v4_device kernel[PLACES];
|
|
static v4_fabric_node nucleus, hera, pool[PLACES];
|
|
static int used[PLACES];
|
|
static unsigned roomed, given_back, borns, gones, parities;
|
|
static v4_image im;
|
|
static v4_mesh ms;
|
|
static v4_mesh_account accounts[PLACES * V4_MESH_WORD_IDS]; /* the words' records (#189, Phase 2) */
|
|
static unsigned char is_word[PLACES * (V4_NODE_WORDS / 8u)];
|
|
static unsigned forgotten;
|
|
static v4_cell forgotten_id = -1;
|
|
static void forgot(void *self, unsigned place, v4_cell id) { (void)self; (void)place; forgotten++; forgotten_id = id; }
|
|
static v4_uheat_t charged[V4_MESH_WORD_IDS];
|
|
static void charge(void *ctx, v4_cell id, v4_uheat_t calls) { (void)ctx; charged[id] += calls; }
|
|
|
|
static int no_room; /* the owner has no room to give */
|
|
static v4_fabric_node *room(void *self) { unsigned k; (void)self; if (no_room) return 0; for (k = 0; k < PLACES; k++) if (!used[k]) { used[k] = 1; roomed++; return &pool[k]; } return 0; }
|
|
static void unroom(void *self, v4_fabric_node *x) { (void)self; used[x - pool] = 0; given_back++; }
|
|
static void born(void *self, unsigned place) { (void)self; (void)place; borns++; }
|
|
static void gone(void *self, unsigned place) { (void)self; (void)place; gones++; }
|
|
static void parity(void *self, unsigned place, v4_cell number, const v4_node *n) { (void)self; (void)place; (void)number; (void)n; parities++; }
|
|
|
|
static char said[1u << 16];
|
|
static unsigned said_len;
|
|
static void out(const char *text, unsigned len)
|
|
{
|
|
if (said_len + len + 1 < sizeof said) { memcpy(said + said_len, text, len); said_len += len; said[said_len] = 0; }
|
|
}
|
|
static int key(void) { return V4_BOOT_KEY_END; }
|
|
static const char *const words[] = { "BYE" };
|
|
static unsigned served;
|
|
static void serve(v4_node *n, unsigned request) { (void)n; if (request == 1) served++; }
|
|
static const char *const capsules[] = { "v4:centre.4th", "v4:hera.4th" };
|
|
|
|
static unsigned char ram[(size_t)BLK_RAM_BLOCKS * BLK_FORTH_SIZE];
|
|
extern void sf_time_init(void);
|
|
extern void log_set_level(int level);
|
|
|
|
/* what a line printed, and how it ended */
|
|
static const char *line(v4_cell node, const char *text, int *how)
|
|
{
|
|
said_len = 0; said[0] = 0;
|
|
*how = v4_mesh_tell(&ms, node, text, (unsigned)strlen(text));
|
|
return said;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int how;
|
|
const char *r;
|
|
|
|
printf("v4 mesh boot: V4_CELL_BITS=%d\n", V4_CELL_BITS);
|
|
sf_time_init();
|
|
log_set_level(-1);
|
|
CHECK(blk_subsys_init(ram, sizeof ram) == BLK_OK, "the kernel's blocks");
|
|
|
|
im = v4_map_image;
|
|
ms.places = places; ms.capacity = PLACES; ms.queues = queues; ms.queue_count = sizeof queues / sizeof queues[0];
|
|
ms.reg = reg; ms.blocks = blocks; ms.nucleus = &nucleus; ms.hera = &hera; ms.kernel = kernel;
|
|
ms.room = room; ms.unroom = unroom; ms.born = born; ms.gone = gone; ms.parity = parity;
|
|
ms.out = out; ms.key = key; ms.words = words; ms.word_count = 1; ms.serve = serve;
|
|
ms.capsules = capsules; ms.capsule_count = 2; ms.im = &im; ms.self = 0;
|
|
ms.accounts = accounts; ms.is_word = is_word; ms.forgot = forgot;
|
|
|
|
/* ---- the boot ---- */
|
|
CHECK(v4_mesh_boot(&ms) == 1, "the boot: %s", said);
|
|
CHECK(strstr(said, "PARITY:V4_ROM ") != 0, "the ROM assembled the nucleus, and said so");
|
|
CHECK(strstr(said, "PARITY:V4_CAPSULE name=v4:forth79.4th ") && strstr(said, "PARITY:V4_CAPSULE name=v4:centre.4th ")
|
|
&& strstr(said, "PARITY:V4_CAPSULE name=v4:hera.4th "), "FORTH-79 and the kernel's capsules, each with its parity line");
|
|
CHECK(strstr(said, "PARITY:V4_POST tests=539 pass=539 fail=0") != 0, "POST passes on Hera, born in the fabric");
|
|
CHECK(strstr(said, "PARITY:V4_SYSTEM word_count=") && strstr(said, "PARITY:OK\nPOST: PASSED\n"), "the system line, and POST: PASSED");
|
|
if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; }
|
|
|
|
/* ---- lines, as messages ---- */
|
|
r = line(V4_MESH_HERA, "3 4 + .", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && strcmp(r, "7 ") == 0, "a line to Hera: \"%s\" (%d)", r, how);
|
|
r = line(V4_MESH_HERA, "NODE-ME .", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && strcmp(r, "0 ") == 0, "NODE-ME: Hera is at place 0: \"%s\"", r);
|
|
r = line(V4_MESH_HERA, "NOSUCH", &how);
|
|
CHECK(how == V4_TEXT_ERROR && strstr(r, "UNKNOWN WORD") != 0, "an unknown word ends in an error: \"%s\"", r);
|
|
r = line(V4_MESH_HERA, "BYE", &how);
|
|
CHECK(served == 1, "a kernel word is served by the owner");
|
|
|
|
/* ---- a birth, and a kill ---- */
|
|
r = line(V4_MESH_HERA, "11 2 2 BIRTH .", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && roomed == 1 && borns == 1 && parities == 1,
|
|
"11 2 2 BIRTH: a node is born in the owner's room, given the system, and its parity recorded: \"%s\" (%d)", r, how);
|
|
CHECK(v4_mesh_node(&ms, 11) != 0, "node 11 is in the fabric");
|
|
r = line(11, "6 7 * .", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && strcmp(r, "42 ") == 0, "node 11 answers a line of its own: \"%s\" (%d)", r, how);
|
|
r = line(V4_MESH_HERA, "S\" 6 7 * .\" 11 SEND 11 AWAIT .", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && strstr(r, "42 ") && strstr(r, "1 "),
|
|
"and through Hera, as the prompt reaches it: she sends node 11 a line and awaits it: \"%s\" (%d)", r, how);
|
|
r = line(V4_MESH_HERA, "11 KILL", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && v4_mesh_node(&ms, 11) == 0 && given_back == 1 && gones == 1,
|
|
"11 KILL: the node is gone, and its room given back: \"%s\" (%d)", r, how);
|
|
r = line(V4_MESH_HERA, "3 4 + .", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && strcmp(r, "7 ") == 0, "and Hera goes on: \"%s\"", r);
|
|
|
|
/* ---- the edges (Captain Bob, 2026-11-13: "Check for the edge cases") ---- */
|
|
r = line(V4_MESH_HERA, "11 2 2 BIRTH .", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && v4_mesh_node(&ms, 11) != 0, "after a kill, 11 is born again, in the room given back (%d)", how);
|
|
r = line(V4_MESH_HERA, "11 3 2 BIRTH .", &how);
|
|
CHECK(how == V4_TEXT_ERROR, "a number that is live is not born again: an error (%d) \"%s\"", how, r);
|
|
r = line(11, "1 0 /", &how);
|
|
CHECK(how == V4_TEXT_ERROR, "a fault on node 11 ends its line in an error (%d) \"%s\"", how, r);
|
|
r = line(11, "2 3 + .", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && strcmp(r, "5 ") == 0, "and node 11 goes on: \"%s\"", r);
|
|
r = line(V4_MESH_HERA, "99 KILL", &how);
|
|
CHECK(how == V4_TEXT_ERROR && strstr(r, "no such node"), "KILL of a node there is not: an error, \"%s\"", r);
|
|
r = line(V4_MESH_HERA, "10 KILL", &how);
|
|
CHECK(how == V4_TEXT_ERROR && v4_mesh_node(&ms, 10) != 0, "Hera does not kill herself: \"%s\"", r);
|
|
no_room = 1;
|
|
r = line(V4_MESH_HERA, "12 3 2 BIRTH .", &how);
|
|
CHECK(how == V4_TEXT_ERROR && v4_mesh_node(&ms, 12) == 0, "no room: the birth fails in an error, \"%s\"", r);
|
|
no_room = 0;
|
|
r = line(V4_MESH_HERA, "3 4 + .", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && strcmp(r, "7 ") == 0, "and Hera goes on: \"%s\"", r);
|
|
{
|
|
char big[V4_MSG_MAX_CHARS + 8];
|
|
memset(big, ' ', sizeof big - 1); big[sizeof big - 1] = 0;
|
|
r = line(V4_MESH_HERA, big, &how);
|
|
CHECK(how == V4_BOOT_LINE_TOO_LONG, "a line too long for a message is not run (%d)", how);
|
|
}
|
|
/* a node set going for ever does not hold the console: Hera's line ends, and node 11 goes on
|
|
* running between lines -- every node steps together, not only while a line runs */
|
|
r = line(V4_MESH_HERA, "S\" VARIABLE N1 : SPIN BEGIN 1 N1 +! AGAIN ;\" 11 SEND 11 AWAIT .", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && strstr(r, "1 "), "node 11 is given SPIN, which runs for ever (%d) \"%s\"", how, r);
|
|
r = line(V4_MESH_HERA, "S\" SPIN\" 11 SEND", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED, "Hera's line ends, though node 11 now runs for ever (%d) \"%s\"", how, r);
|
|
{
|
|
v4_fabric_node *x = v4_mesh_node(&ms, 11);
|
|
v4_uheat_t before = x ? x->es.anticlock : 0;
|
|
unsigned k;
|
|
for (k = 0; k < 1000; k++) v4_mesh_step(&ms); /* the host's idle: the fabric steps on */
|
|
CHECK(x && x->es.anticlock > before + 1000u, "between lines node 11 runs on: every node steps together");
|
|
}
|
|
r = line(V4_MESH_HERA, "11 KILL", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && v4_mesh_node(&ms, 11) == 0, "and a node running for ever is killed (%d) \"%s\"", how, r);
|
|
/* a wait no one will answer: the next line breaks it */
|
|
r = line(V4_MESH_HERA, "11 2 2 BIRTH DROP 12 AWAIT", &how);
|
|
CHECK(how == V4_BOOT_LINE_WAITING, "AWAIT on a node that will never say: the line waits (%d)", how);
|
|
r = line(V4_MESH_HERA, "3 4 + .", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && strstr(r, "Interrupted") && strstr(r, "7 ") > strstr(r, "Interrupted"),
|
|
"and the next line ends the wait \"Interrupted\", and is then run (MESH.md 7b.6): \"%s\" (%d)", r, how);
|
|
r = line(V4_MESH_HERA, "3 4 + .", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && strcmp(r, "7 ") == 0 && ms.owed == 0, "and nothing more is owed: \"%s\"", r);
|
|
|
|
/* ---- the words' accounts (#189, Phase 2; Captain Bob, 2026-11-13: the word ID in the header, charged
|
|
* per heartbeat tick) ---- */
|
|
{
|
|
v4_cell xt, xt2;
|
|
const v4_mesh_account *a;
|
|
r = line(V4_MESH_HERA, ": SQ DUP * ; ' SQ .", &how);
|
|
xt = (v4_cell)strtol(r, 0, 10);
|
|
a = v4_mesh_account_at(&ms, 0, xt);
|
|
CHECK(how == V4_TEXT_COMPLETED && xt > 0 && a && a->calls == 0 && a->xt == xt,
|
|
"a word defined at the prompt has a record, called 0 times: \"%s\"", r);
|
|
r = line(V4_MESH_HERA, "3 SQ .", &how);
|
|
CHECK(strcmp(r, "9 ") == 0 && a && a->calls == 1, "a call to it counts one: %lu", a ? (unsigned long)a->calls : 0ul);
|
|
r = line(V4_MESH_HERA, "5 SQ 6 SQ + .", &how);
|
|
CHECK(strcmp(r, "61 ") == 0 && a && a->calls == 3, "and each call one more: %lu", a ? (unsigned long)a->calls : 0ul);
|
|
r = line(V4_MESH_HERA, ": QUAD SQ SQ ; 2 QUAD .", &how);
|
|
CHECK(strcmp(r, "16 ") == 0 && a && a->calls == 5, "calls from inside a definition count: %lu", a ? (unsigned long)a->calls : 0ul);
|
|
r = line(V4_MESH_HERA, "4 ' SQ EXECUTE .", &how);
|
|
CHECK(strcmp(r, "16 ") == 0 && a && a->calls == 6, "and through EXECUTE: %lu", a ? (unsigned long)a->calls : 0ul);
|
|
{
|
|
v4_cell id = v4_mesh_node(&ms, V4_MESH_HERA)->n.mem[xt - 4];
|
|
memset(charged, 0, sizeof charged);
|
|
v4_mesh_charge(&ms, 0, charge, 0);
|
|
CHECK(id >= 0 && id < (v4_cell)V4_MESH_WORD_IDS && charged[id] == 6 && a && a->since == 0 && a->calls == 6,
|
|
"the owner's charge takes the calls since, and clears them; the record keeps its count");
|
|
memset(charged, 0, sizeof charged);
|
|
v4_mesh_charge(&ms, 0, charge, 0);
|
|
CHECK(charged[id] == 0, "a second charge with no calls between takes nothing for it");
|
|
r = line(V4_MESH_HERA, "FORGET SQ", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && v4_mesh_account_at(&ms, 0, xt) == 0 && forgotten >= 2 && forgotten_id >= id,
|
|
"FORGET: its record goes, and QUAD's after it; the owner told (%u)", forgotten);
|
|
r = line(V4_MESH_HERA, ": CUBE DUP DUP * * ; ' CUBE .", &how);
|
|
xt2 = (v4_cell)strtol(r, 0, 10);
|
|
CHECK(xt2 > 0 && v4_mesh_node(&ms, V4_MESH_HERA)->n.mem[xt2 - 4] == id && v4_mesh_account_at(&ms, 0, xt2)
|
|
&& v4_mesh_account_at(&ms, 0, xt2)->calls == 0, "the next word defined takes SQ's ID, with a record of its own");
|
|
}
|
|
{
|
|
v4_fabric_node *h = v4_mesh_node(&ms, V4_MESH_HERA);
|
|
v4_cell w = h->n.mem[im.latest];
|
|
unsigned k, words = 0, recorded = 0;
|
|
for (k = 0; w > 4 && k < 2000; w = h->n.mem[w - 1], k++) { words++; if (v4_mesh_account_at(&ms, 0, w)) recorded++; }
|
|
CHECK(words > 300 && recorded == words, "every word on Hera has a record: %u of %u", recorded, words);
|
|
}
|
|
r = line(V4_MESH_HERA, "11 2 2 BIRTH .", &how);
|
|
{
|
|
v4_fabric_node *x = v4_mesh_node(&ms, 11);
|
|
unsigned place = 0, k;
|
|
for (k = 0; k < PLACES; k++) if (v4_fabric_node_at(&ms.f, k) == x) place = k;
|
|
r = line(11, ": TWICE 2* ; ' TWICE .", &how);
|
|
xt = (v4_cell)strtol(r, 0, 10);
|
|
r = line(11, "21 TWICE .", &how);
|
|
CHECK(x && place != 0 && strcmp(r, "42 ") == 0 && v4_mesh_account_at(&ms, place, xt)
|
|
&& v4_mesh_account_at(&ms, place, xt)->calls == 1, "a born node's words are kept too, by its own place");
|
|
r = line(V4_MESH_HERA, "11 KILL", &how);
|
|
CHECK(how == V4_TEXT_COMPLETED && v4_mesh_account_at(&ms, place, xt) == 0, "and go when it is killed");
|
|
}
|
|
}
|
|
|
|
printf(" %d checks, %d failures\n", checks, failures);
|
|
return failures ? 1 : 0;
|
|
}
|