Files
LithosAnanke/v4/system/mesh.c
T
Claude 046ce0be65 feat(v4): the mesh boot to spec -- Hera born empty in the kernel's fabric; births, kills, every node stepping together (#189)
ENGINE step 4 Phase 1, Tasks 5b-5d, and the edge cases (Captain Bob, 2026-11-13: "Build it to proper
spec"; "Check for the edge cases"; "Yes, add it to v4-checks").

- v4/system/mesh.c, v4/include/v4/mesh.h: the nucleus assembled by the ROM and given to Hera as F18
  cells; Hera born empty at place 0, the kernel on port 0, the console on port 1; told she is 10, in the
  registry, the kernel's words, FORTH-79, sealed, POST, the kernel's capsules. NODE-BORN / NODE-KILL in
  the owner's room.
- No scheduler: v4_mesh_tell returns once the node told says how its text ended, and
  v4_mesh_step / v4_mesh_busy let the host step the fabric while it waits, so nodes run between
  lines as during them. A text left "waiting" is owed its end: the next line ends it "Interrupted"
  and is then run (MESH.md 7b.6).
- kernel: sk_v4.c boots through the mesh; its idle steps the fabric while a node can run, and halts
  only when none can (#151 kept). stadium_return_quota and stadium_ops_forget on a kill.
- Tests: v4/tests/test_mesh_boot.c (32 checks: the boot, births, kills, rebirth, a live number, a
  fault, no such node, Hera not killing herself, no room, a line too long, a node running for ever,
  a wait broken by the next line), run by `make -C v4 test` and under ASan+UBSan by `sanitize`
  (v4-checks); kernel/tests quota return; scripts/qemu_session.py births() and the edges on bare
  metal.
- Docs: ENGINE.md 6 "who runs next" struck (settled: no scheduler); the Phase 1 plan; FABRIC-3
  XLVIII.23; .claude/memory/rules.md: alpha.5 closed to new work.
- Evidence: v4-checks 80 programs under the sanitizers, 0 failures; the hosted builds alike; two
  stopped v4-checks runs labelled STOPPED; three development amd64 boots labelled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
2026-11-13 17:00:08 +00:00

467 lines
19 KiB
C

/* SPDX-License-Identifier: LicenseRef-Starship-1.0
* Copyright (c) 2023-2026 Robert A. James
*/
/* mesh.c -- the kernel's boot of the fabric. See v4/mesh.h (#189).
*
* What v4/tests/test_host_unit.c does on the host, made the kernel's: the
* nucleus, Hera born empty and given it, her system, POST, and every node
* stepped together by the fabric. No scheduler.
*
* Nothing here uses the C library: the bare-metal kernel links this file.
*/
#include "v4/mesh.h"
#include "v4/capsule.h"
#include "v4/image.h"
#include "v4/message.h"
#include "v4/post.h"
#include "starkernel/capsule_blocks.h"
#if V4_CELL_BITS == 64
extern const v4_cell v4_rom_64[];
extern const unsigned v4_rom_64_words;
extern const v4_cell v4_mesh_rom_64[];
extern const unsigned v4_mesh_rom_64_words;
#define HERA_ROM v4_rom_64
#define HERA_ROM_WORDS v4_rom_64_words
#define NODE_ROM v4_mesh_rom_64
#define NODE_ROM_WORDS v4_mesh_rom_64_words
#define NUCLEUS_NAME "v4:nucleus-64.f18"
#else
extern const v4_cell v4_rom_32[];
extern const unsigned v4_rom_32_words;
extern const v4_cell v4_mesh_rom_32[];
extern const unsigned v4_mesh_rom_32_words;
#define HERA_ROM v4_rom_32
#define HERA_ROM_WORDS v4_rom_32_words
#define NODE_ROM v4_mesh_rom_32
#define NODE_ROM_WORDS v4_mesh_rom_32_words
#define NUCLEUS_NAME "v4:nucleus-32.f18"
#endif
#define KERNEL_PORT 0u
#define MAX_PLACES 64u /* mesh.h: reading_nucleus[64] */
#define STEP_LIMIT 4000000000ULL /* fabric steps one line may take */
#define WAIT_STEPS 2000000ULL /* steps Hera may sleep awaiting a node that runs on, before the line is "waiting" */
#define TOLD_FIRST "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE"
static v4_mesh *the; /* one mesh: the devices' callbacks find it here */
static v4_boot bt; /* for saying, and for the ROM's assembly of the nucleus */
/* ---- saying ---------------------------------------------------------------- */
static void say(const char *s) { unsigned len = 0; while (s[len]) len++; the->out(s, len); }
static int same(const char *a, unsigned alen, const char *b) { unsigned i; for (i = 0; i < alen && b[i] && a[i] == b[i]; i++) { } return i == alen && !b[i]; }
/* ---- the nucleus, as a capsule of F18 cells -------------------------------- */
static void put_cell(void *self, v4_cell w)
{
v4_mesh *ms = (v4_mesh *)self;
if (ms->cell_count < V4_MESH_NUCLEUS_CELLS) ms->cells[ms->cell_count++] = w;
}
/* ---- each place's capsule reader (CAPSULE-OPEN, -CELL, -LINE) -------------- */
typedef struct { const uint8_t *p, *end; int in_block; } reader;
static reader reads[MAX_PLACES];
static int open_cb(void *self, unsigned asker, const char *name, unsigned len)
{
char cname[V4_MANAGE_LINE + 1u];
uint32_t length;
uint64_t id, hash;
const uint8_t *p;
unsigned i;
(void)self;
if (asker >= MAX_PLACES || len == 0 || len > V4_MANAGE_LINE) return 0;
if (same(name, len, NUCLEUS_NAME)) { /* the system, assembled: a cell at a time */
the->reading_nucleus[asker] = 1;
reads[asker].p = reads[asker].end = 0;
return 1;
}
for (i = 0; i < len; i++) cname[i] = name[i];
cname[len] = 0;
p = v4_boot_capsule(&bt, cname, &length, &id, &hash);
if (!p) return 0;
the->reading_nucleus[asker] = 0;
reads[asker].p = p; reads[asker].end = p + length; reads[asker].in_block = 0;
return 1;
}
static int cell_cb(void *self, unsigned asker, v4_cell *w)
{
unsigned k;
(void)self;
if (asker >= MAX_PLACES || !the->reading_nucleus[asker]) return 0;
k = the->reading_nucleus[asker] - 1u;
if (k >= the->cell_count) return 0;
*w = the->cells[k];
the->reading_nucleus[asker]++;
return 1;
}
/* The next line of a block of the open capsule, at most V4_MANAGE_LINE characters: its length, or -1. */
static int next_line(reader *r, char *text, unsigned cap)
{
while (r->p && r->p < r->end) {
const uint8_t *after, *nl;
uint32_t num;
unsigned len, i;
if (capsule_block_header(r->p, r->end, &num, &after)) { r->in_block = 1; r->p = after; continue; }
for (nl = r->p; nl < r->end && *nl != '\n'; nl++) { }
len = (unsigned)(nl - r->p);
if (len && r->p[len - 1] == '\r') len--;
if (r->in_block && len > cap) { r->p = r->end; return -1; }
if (r->in_block) for (i = 0; i < len; i++) text[i] = (char)r->p[i];
r->p = nl < r->end ? nl + 1 : r->end;
if (r->in_block && len) return (int)len;
}
return -1;
}
static int line_cb(void *self, unsigned asker, char *text)
{
(void)self;
if (asker >= MAX_PLACES || the->reading_nucleus[asker]) return -1;
return next_line(&reads[asker], text, V4_MANAGE_LINE);
}
static void parity_cb(void *self, unsigned place, v4_cell number, const v4_node *n)
{
(void)self;
if (the->parity) the->parity(the->self, place, number, n);
}
/* ---- the kernel on a place's port 0 ---------------------------------------- */
static unsigned place_of_device(const void *self) { return (unsigned)((const v4_device *)self - the->kernel); }
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 (place == the->hera_place && the->serve && value >= 1 && value <= (v4_cell)the->word_count) {
the->serve(&x->n, (unsigned)value);
return 1;
}
if (v4_manage_serve(&the->m, place, value)) return 1; /* the registry's: any node may ask, the kernel checks who */
v4_node_store(&x->n, x->n.error_reg, 12); /* a request no one serves: 12, Argument out of range */
return 1;
}
static int kernel_give(void *self, v4_cell *value) /* the nucleus, to Hera, who has nothing in her */
{
if (place_of_device(self) != the->hera_place || the->given >= the->cell_count) return 0;
*value = the->cells[the->given++];
return 1;
}
static const v4_device console = { 0, 0, 0 }; /* it speaks whole messages only */
/* What a node's hardware gives it, in place `place`: its registers, its ROM, the kernel on port 0. */
static int fit(unsigned place, v4_fabric_node *x, const v4_cell *rom, unsigned rom_words, int gives)
{
const v4_image *im = the->im;
v4_node_stack_regs_attach(&x->n, im->dstack_reg, im->rstack_reg);
v4_node_error_attach(&x->n, im->node_error);
v4_node_fault_attach(&x->n, im->fault_table);
v4_node_heat_regs_attach(&x->n, im->heat_reg, x->h.op, &x->es.anticlock);
v4_node_rom_attach(&x->n, rom, rom_words);
v4_blocks_init(&the->blocks[place], im->block_window);
the->kernel[place].take = kernel_take;
the->kernel[place].give = gives ? kernel_give : 0;
the->kernel[place].self = &the->kernel[place];
return v4_fabric_wire_kernel(&the->f, place, KERNEL_PORT, &the->kernel[place]);
}
/* ---- births ------------------------------------------------------------------ */
static int born_cb(void *self)
{
v4_fabric_node *x;
int place;
(void)self;
x = the->room ? the->room(the->self) : 0;
if (!x) return -1;
place = v4_fabric_add(&the->f, x, the->im->port);
if (place < 0 || (unsigned)place >= MAX_PLACES) {
if (place >= 0) (void)v4_fabric_remove(&the->f, (unsigned)place);
the->unroom(the->self, x);
return -1;
}
if (!fit((unsigned)place, x, NODE_ROM, NODE_ROM_WORDS, 0)) {
(void)v4_fabric_remove(&the->f, (unsigned)place);
the->unroom(the->self, x);
return -1;
}
the->reading_nucleus[place] = 0;
if (the->born) the->born(the->self, (unsigned)place);
return place;
}
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->gone) the->gone(the->self, place);
if (the->unroom) the->unroom(the->self, was);
}
v4_fabric_node *v4_mesh_node(const v4_mesh *ms, v4_cell number)
{
unsigned k;
for (k = 0; k < ms->capacity; k++) {
v4_fabric_node *x = v4_fabric_node_at(&ms->f, k);
if (x && x->n.mem[ms->im->me] == number) return x;
}
return 0;
}
/* ---- the console: text to a node, and what comes back -------------------- */
/* Does any node execute at the next step? One asleep at a wire, blocked, or stopped does not. */
static int any_running(const v4_mesh *ms)
{
unsigned k;
for (k = 0; k < ms->capacity; k++) {
const v4_fabric_node *x = v4_fabric_node_at(&ms->f, k);
if (!x || ms->places[k].asleep || x->n.stopped || x->n.asking || x->n.doing || (x->n.reading && !x->n.given)) continue;
return 1;
}
return 0;
}
/* What has come back to the console: printing goes to `out`, and how a text ended is kept. */
static int takes(v4_mesh *ms, v4_cell *ended_from, v4_cell *ended_how, unsigned *ended)
{
static v4_cell hd[V4_WIRE_HEADER], text[V4_MSG_MAX_CHARS / 4u];
int took = 0;
while (v4_fabric_device_take(&ms->f, ms->hera_place, V4_MESH_CONSOLE, hd, text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) {
took = 1;
if (hd[2] == V4_MSG_OUTPUT) {
char buf[64];
unsigned i, at = 0;
for (i = 0; i < (unsigned)hd[6]; i++) {
buf[at++] = (char)(((v4_ucell)text[i / 4u] >> (8u * (i % 4u))) & 0xFFu);
if (at == sizeof buf) { ms->out(buf, at); at = 0; }
}
if (at) ms->out(buf, at);
} else if (hd[2] == V4_MSG_DONE) {
/* the end of a text left "waiting" (7b.6: it ends "Interrupted" when the next comes, or
* with its answer): owed, not the end of the text being told now */
if (ms->owed && hd[1] == ms->owed_from) ms->owed--;
else (*ended)++;
*ended_from = hd[1];
*ended_how = text[0];
ms->dones++;
ms->done_from = hd[1];
ms->done_how = text[0];
}
}
return took;
}
unsigned v4_mesh_step(v4_mesh *ms)
{
v4_cell f = -1, h = -1;
unsigned e = 0, did;
the = ms;
did = v4_fabric_step(&ms->f);
(void)takes(ms, &f, &h, &e);
return did;
}
int v4_mesh_busy(const v4_mesh *ms) { return any_running(ms); }
int v4_mesh_tell(v4_mesh *ms, v4_cell node, const char *text, unsigned len)
{
static v4_message msg;
v4_cell ended_from = -1, ended_how = -1;
unsigned ended = 0;
unsigned long long steps = 0, asleep = 0;
v4_fabric_node *h = v4_fabric_node_at(&ms->f, ms->hera_place);
the = ms;
if (len > V4_MSG_MAX_CHARS) return V4_BOOT_LINE_TOO_LONG;
if (!v4_message_text(&msg, node, V4_MESH_CONSOLE, V4_MSG_TEXT, text, len)) return V4_BOOT_LINE_TOO_LONG;
if (v4_fabric_device_put(&ms->f, ms->hera_place, V4_MESH_CONSOLE, msg.word, msg.word + V4_MSG_HEADER) != V4_WIRE_DONE)
return V4_BOOT_LINE_REFUSED;
while (steps++ < STEP_LIMIT) {
unsigned did = v4_fabric_step(&ms->f);
int took = takes(ms, &ended_from, &ended_how, &ended);
int running;
if (h && h->n.stopped) return V4_BOOT_LINE_STOPPED;
/* the node it was for has said how its text ended -- or, for node 0, one not yet told its
* number, whoever it became: the first line Hera is told makes her 10 */
if (ended && (node == 0 || ended_from == node)) return (int)ended_how;
/* the text reads the keyboard: Hera's console registers, fed from the host's keys */
if (h && v4_image_waiting(&h->n, ms->im)) {
int c = ms->key ? ms->key() : V4_BOOT_KEY_END;
unsigned char ch = (unsigned char)c;
if (c == V4_BOOT_KEY_END) return V4_BOOT_LINE_NO_INPUT;
if (c != V4_BOOT_KEY_NONE) (void)v4_node_console_feed(&h->n, &ch, 1);
continue;
}
running = any_running(ms);
/* Hera asleep at her wires, her text not ended: awaiting an answer -- "waiting" when nothing
* else can give it, or when other nodes have run a long while without giving it */
if (h && h->n.doing && h->n.do_op == 8) {
if ((did == 0 && !took && !running) || ++asleep > WAIT_STEPS) {
if (ms->owed && ms->owed_from == node) ms->owed++;
else { ms->owed_from = node == 0 ? V4_MESH_HERA : node; ms->owed = 1; }
return V4_BOOT_LINE_WAITING;
}
} else asleep = 0;
if (did == 0 && !took && !running) return V4_BOOT_LINE_STOPPED;
}
return V4_BOOT_LINE_STOPPED;
}
/* ---- POST's host: a line is told to Hera, what she prints handed to the runner ---- */
static char post_out[V4_POST_OUT];
static unsigned post_len, post_cap;
static void post_capture(const char *text, unsigned len)
{
unsigned i;
for (i = 0; i < len; i++) { if (post_len < post_cap) post_out[post_len] = text[i]; post_len++; }
}
static int post_line(void *self, const char *text, unsigned text_len, char *out, unsigned cap, unsigned *len)
{
v4_mesh *ms = (v4_mesh *)self;
void (*was)(const char *, unsigned) = ms->out;
int how;
unsigned i;
post_len = 0; post_cap = cap < sizeof post_out ? cap : (unsigned)sizeof post_out;
ms->out = post_capture;
how = v4_mesh_tell(ms, V4_MESH_HERA, text, text_len);
ms->out = was;
for (i = 0; i < post_len && i < post_cap; i++) out[i] = post_out[i];
*len = post_len;
return how == V4_TEXT_QUIT || how == V4_TEXT_COMPLETED || how == V4_TEXT_ERROR ? how : -1;
}
static void post_say(void *self, const char *text, unsigned len) { ((v4_mesh *)self)->out(text, len); }
/* ---- the boot ----------------------------------------------------------------- */
/* Every line of a capsule of FORTH blocks, told to Hera; then its parity line, as v4_boot_run prints it. */
static int load(v4_mesh *ms, const char *name)
{
static reader r;
char text[V4_MSG_MAX_CHARS + 1u];
uint32_t length;
uint64_t id, hash;
const uint8_t *p = v4_boot_capsule(&bt, name, &length, &id, &hash);
int n;
v4_fabric_node *h = v4_fabric_node_at(&ms->f, ms->hera_place);
if (!p) return 0;
r.p = p; r.end = p + length; r.in_block = 0;
while ((n = next_line(&r, text, V4_MSG_MAX_CHARS)) >= 0) {
int how = v4_mesh_tell(ms, V4_MESH_HERA, text, (unsigned)n);
if (how != V4_TEXT_COMPLETED) {
say("V4: "); say(name); say(" did not complete: "); ms->out(text, (unsigned)n); say("\n");
return 0;
}
}
say("PARITY:V4_CAPSULE name="); say(name);
say(" capsule_id="); v4_boot_say_hex(&bt, id);
say(" capsule_hash="); v4_boot_say_hex(&bt, hash);
say(" dict_hash="); v4_boot_say_hex(&bt, v4_image_dict_hash(&h->n, ms->im));
say("\n");
return 1;
}
static int boot(v4_mesh *ms)
{
v4_fabric_node *h = ms->hera;
unsigned long long steps = 0;
unsigned i;
int place;
if (ms->capacity == 0 || ms->capacity > MAX_PLACES) { say("V4: the mesh has no room, or more than it can keep\n"); return 0; }
/* 1. the nucleus: the ROM assembles the system on a node of its own, written as F18 cells */
bt.n = &ms->nucleus->n; bt.es = &ms->nucleus->es; bt.h = &ms->nucleus->h; bt.im = ms->im;
bt.out = ms->out; bt.key = 0; bt.words = 0; bt.word_count = 0; bt.serve = 0;
bt.request = 0; bt.request_self = 0; bt.capsules = 0; bt.capsule_count = 0;
if (!v4_boot_assemble(&bt)) return 0;
ms->cell_count = 0; ms->given = 0;
(void)v4_capsule_write(&ms->nucleus->n, ms->im->idle, put_cell, ms);
if (ms->cell_count == 0 || ms->cell_count >= V4_MESH_NUCLEUS_CELLS) { say("V4: the nucleus did not fit\n"); return 0; }
/* 2. the fabric, and Hera born empty at place 0: the kernel on port 0, the console on port 1 */
v4_fabric_init(&ms->f, ms->places, ms->capacity);
v4_fabric_queues(&ms->f, ms->queues, ms->queue_count);
v4_fabric_gone_error(&ms->f, V4_ERROR_NO_ONE);
for (i = 0; i < MAX_PLACES; i++) { ms->reading_nucleus[i] = 0; reads[i].p = reads[i].end = 0; }
place = v4_fabric_add(&ms->f, h, ms->im->port);
if (place != 0) { say("V4: Hera is not at place 0\n"); return 0; }
ms->hera_place = (unsigned)place;
v4_node_console_input_attach(&h->n, ms->im->console_rx, ms->im->console_status); /* KEY reads these */
if (!fit(ms->hera_place, h, HERA_ROM, HERA_ROM_WORDS, 1)
|| !v4_fabric_wire_device(&ms->f, ms->hera_place, V4_MESH_CONSOLE, &console)) {
say("V4: Hera's ports could not be wired\n");
return 0;
}
while (!(h->n.doing && h->n.do_op == 8 && ms->given == ms->cell_count)) { /* she takes it in, and sleeps */
(void)v4_fabric_step(&ms->f);
if (h->n.stopped || ++steps > STEP_LIMIT) { say("V4: Hera did not take in the nucleus\n"); return 0; }
}
/* 3. who she is; the registry; the kernel's words; FORTH-79 and the kernel's capsules; sealed */
ms->m.f = &ms->f; ms->m.reg = ms->reg; ms->m.reg_cap = ms->capacity; ms->m.hera = ms->hera_place;
ms->m.born = born_cb; ms->m.gone = gone_cb; ms->m.open = open_cb;
ms->m.cell = cell_cb; ms->m.line = line_cb; ms->m.parity = parity_cb; ms->m.self = ms;
{
int how = v4_mesh_tell(ms, 0, TOLD_FIRST, (unsigned)(sizeof TOLD_FIRST - 1u));
if (how != V4_TEXT_COMPLETED) {
say("V4: Hera was not told who she is: it ended ");
if (how < 0) { say("-"); v4_boot_say_dec(&bt, (uint32_t)-how); } else v4_boot_say_dec(&bt, (uint32_t)how);
say("\n");
return 0;
}
}
if (!v4_manage_enter(&ms->m, (v4_cell)V4_MESH_HERA, ms->hera_place, 0)) { say("V4: Hera not entered in the registry\n"); return 0; }
for (i = 0; i < ms->word_count; i++) {
char text[64];
unsigned at = 0, k, num = i + 1, div = 1;
while (num / div >= 10) div *= 10;
for (; div; div /= 10) text[at++] = (char)('0' + num / div % 10);
for (k = 0; " KERNEL-WORD "[k]; k++) text[at++] = " KERNEL-WORD "[k];
for (k = 0; ms->words[i][k] && at < sizeof text; k++) text[at++] = ms->words[i][k];
if (ms->words[i][k] || v4_mesh_tell(ms, V4_MESH_HERA, text, at) != V4_TEXT_COMPLETED) {
say("V4: kernel word "); say(ms->words[i]); say(" could not be made\n");
return 0;
}
}
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);
/* POST, run on her as the kernel's, through her console; then she is put back as sealed */
{
v4_post_host host;
v4_post_tally tally;
host.self = ms; host.n = &h->n; host.line = post_line; host.say = post_say;
if (!v4_post_run(&host, v4_post_cases, v4_post_case_count, &tally)) { say("V4: POST reported failures\n"); return 0; }
v4_dstack_reset(&h->n.ds);
{
void (*was)(const char *, unsigned) = ms->out;
int how;
post_len = 0; post_cap = 0;
ms->out = post_capture; /* what COLD prints is not shown */
how = v4_mesh_tell(ms, V4_MESH_HERA, "COLD", 4);
ms->out = was;
if (how != V4_TEXT_COMPLETED && how != V4_TEXT_QUIT) { say("V4: Hera did not come back to the system after POST\n"); return 0; }
}
}
say("PARITY:V4_SYSTEM word_count="); v4_boot_say_dec(&bt, v4_boot_word_count(&h->n, ms->im));
say(" dict_hash="); v4_boot_say_hex(&bt, v4_image_dict_hash(&h->n, ms->im));
say("\n");
return 1;
}
int v4_mesh_boot(v4_mesh *ms)
{
the = ms;
if (!boot(ms)) { say("PARITY:FAIL\nPOST: FAILED\n"); return 0; }
say("PARITY:OK\nPOST: PASSED\n");
return 1;
}