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
595 lines
25 KiB
C
595 lines
25 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 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);
|
|
}
|
|
|
|
/* ---- the kernel on a place's port 0 ---------------------------------------- */
|
|
|
|
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;
|
|
}
|
|
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->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);
|
|
}
|
|
|
|
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);
|
|
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 */
|
|
{
|
|
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;
|
|
}
|