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
139 lines
7.7 KiB
C
139 lines
7.7 KiB
C
/* SPDX-License-Identifier: LicenseRef-Starship-1.0
|
|
* Copyright (c) 2023-2026 Robert A. James
|
|
*/
|
|
/* boot.h -- how a StarForth v4 system comes up: the machine's ROM assembles
|
|
* the system, then its capsules are loaded, then the prompt.
|
|
* docs/v4.0.0/MACHINE.md, NUCLEUS.md.
|
|
*
|
|
* The hosted binary (tools/hosted.c) calls v4_boot_run: one node, which its
|
|
* ROM brings up and which is handed its lines. The bare-metal kernel
|
|
* (kernel/src/v4/sk_v4.c) did too, until 2026-11-13; it now boots the fabric
|
|
* (v4/mesh.h; #189, ENGINE step 4 Phase 1): the ROM assembles the nucleus by
|
|
* v4_boot_assemble, and Hera is born empty and given it. Corrected
|
|
* 2026-11-13 -- this said the two "differ only in where the text goes".
|
|
* Both print the same parity lines, in the same order:
|
|
*
|
|
* PARITY:V4_F18 name=v4:sys:map-64.f18 capsule_hash=0x... (one for each blob of the system's source)
|
|
* PARITY:V4_ROM words=N rom_hash=0x... instructions=N (the ROM, and what assembling the system cost)
|
|
* PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x... capsule_hash=0x... dict_hash=0x...
|
|
* PARITY:V4_POST tests=N pass=N fail=N (POST is the kernel's: v4/post.h)
|
|
* PARITY:V4_SYSTEM word_count=N dict_hash=0x... (the system as sealed, after POST)
|
|
* PARITY:OK
|
|
* POST: PASSED
|
|
*
|
|
* or, if anything is wrong, what was wrong and then
|
|
*
|
|
* PARITY:FAIL
|
|
* POST: FAILED
|
|
*
|
|
* A capsule is found by name in the directory tools/mkcapsule.c baked into
|
|
* the binary, its hash is recomputed, its signature is checked -- one that
|
|
* does not verify refuses the capsule, a missing one is only reported, as
|
|
* for v3's capsules -- and its blocks are handed to the node a line at a
|
|
* time (docs/v4.0.0/ENGINE.md 3.1). Every line must complete; the first
|
|
* that ends in an error, or in QUIT, ends the boot. What a line prints is
|
|
* shown.
|
|
*
|
|
* The dictionary hash is FNV-1a over the node's memory below HERE, a cell at
|
|
* a time, low byte first, then LATEST. It does not depend on the machine:
|
|
* every build of one commit, with one cell width, prints the same hashes.
|
|
*/
|
|
#ifndef V4_BOOT_H
|
|
#define V4_BOOT_H
|
|
|
|
#include "v4/image.h"
|
|
#include "v4/message.h"
|
|
|
|
typedef struct {
|
|
v4_node *n;
|
|
v4_exec_state *es;
|
|
v4_heat *h;
|
|
v4_image *im; /* the system: the map's description (v4_map_image), which the boot completes */
|
|
void (*out)(const char *text, unsigned len); /* the console */
|
|
int (*key)(void); /* a character for a line that reads the keyboard, or 0: there is none */
|
|
|
|
/* THE KERNEL'S WORDS (docs/v4.0.0/ENGINE.md 3.3). `words` names them,
|
|
* `word_count` of them; the boot makes each a word on the node (KERNEL-
|
|
* WORD, capsules/v4/sys/compile.f18) whose request number is its place in the
|
|
* list, counting from 1. When the node writes a request to its port it
|
|
* is blocked, and `serve` is called with the number: it takes the
|
|
* word's arguments from the node's data stack and leaves its results
|
|
* there, and when it returns the node goes on. The kernel's two block
|
|
* requests (v4/blocks.h) are served for every node and are not in the
|
|
* list. Any other request is an error on the node: 12, Argument out of
|
|
* range. */
|
|
const char *const *words;
|
|
unsigned word_count;
|
|
void (*serve)(v4_node *n, unsigned request);
|
|
|
|
/* THE KERNEL'S REGISTRY (MESH.md 9a; #189, ENGINE step 4 Phase 1). A
|
|
* request that is not a block's and not one of the kernel's words goes
|
|
* to `request`, if there is one: it returns 1 when it served the request
|
|
* -- or refused it, raising error 12 on the node itself (v4/manage.h) --
|
|
* and 0 when it is not one of its; then the request is error 12, as
|
|
* before. 0: none (the hosted product). */
|
|
int (*request)(void *self, v4_node *n, v4_cell request);
|
|
void *request_self;
|
|
|
|
/* The kernel's own capsules, by name, loaded after the boot's
|
|
* (forth79.4th) and before the system is sealed, so POST runs on them
|
|
* and COLD comes back to them: on bare metal, centre.4th and hera.4th
|
|
* (#189). 0 of them: the hosted product. */
|
|
const char *const *capsules;
|
|
unsigned capsule_count;
|
|
|
|
} v4_boot;
|
|
|
|
/* What `key` returns when it has no character. */
|
|
#define V4_BOOT_KEY_NONE (-1) /* not yet: it is asked again */
|
|
#define V4_BOOT_KEY_END (-2) /* and there never will be */
|
|
|
|
/* A node is born empty (v4_image_born); its ROM assembles the system from
|
|
* its source, capsules/v4/sys, fed to it through its console; and the
|
|
* capsules are loaded. Its blocks are the kernel's block
|
|
* subsystem's, which whoever calls this has set up (v4/blocks.h). Returns 1 with the node idle,
|
|
* waiting to be handed a line; or 0, the failure printed. It prints no
|
|
* prompt: that is the host's. */
|
|
int v4_boot_run(const v4_boot *b);
|
|
|
|
/* Hand the node a line and run it to its end, sending what it prints to
|
|
* `out`. The line is put on the node's wire as a message, whole, and what
|
|
* it prints comes back as messages (message.h; docs/v4.0.0/MESH.md 7d.5). Returns how it ended -- V4_TEXT_QUIT,
|
|
* V4_TEXT_COMPLETED or V4_TEXT_ERROR -- or one of these: */
|
|
#define V4_BOOT_LINE_TOO_LONG (-1) /* more than the node's input buffer holds; nothing was run */
|
|
#define V4_BOOT_LINE_STOPPED (-2) /* the node stopped on a fault, or never finished */
|
|
#define V4_BOOT_LINE_NO_INPUT (-3) /* the line read the keyboard and there is no more input */
|
|
#define V4_BOOT_LINE_WAITING (-4) /* the line is waiting for an answer (AWAIT) that no one here can send: see below */
|
|
#define V4_BOOT_LINE_REFUSED (-5) /* the node's wire has no room for the line: it was not put, and nothing was run. The node
|
|
* is taking nothing -- it waits for an answer and does not know these lines for its
|
|
* console's -- and its wire has filled (MESH.md 7d.6). It has not stopped. */
|
|
int v4_boot_line(const v4_boot *b, const char *text, unsigned len);
|
|
|
|
/* A LINE THAT WAITS (docs/v4.0.0/MESH.md 7b.6). V4_BOOT_LINE_WAITING means
|
|
* the line has not ended: the node is asleep, waiting for an answer,
|
|
* and here there is no one but the console to end that. The host reads the
|
|
* next line and hands it over as usual: that breaks the wait, and what is
|
|
* returned is how the line that was waiting ended -- in an error,
|
|
* Interrupted. The line just handed over is still on the node's wire; the host
|
|
* then calls v4_boot_line with no text (0, 0) to have it done, and that returns
|
|
* how it ended, which may be V4_BOOT_LINE_WAITING again. */
|
|
|
|
/* The dictionary hash of a node started from `im`. */
|
|
uint64_t v4_boot_dict_hash(const v4_node *n, const v4_image *im);
|
|
|
|
/* WHAT THE MESH BOOT SHARES (v4/mesh.h; #189). v4_boot_assemble: the node
|
|
* `b->n` is born empty and its ROM assembles the system from its source,
|
|
* the PARITY:V4_F18 and PARITY:V4_ROM lines printed, `b->im` completed from
|
|
* the image record; 1, or 0 with the failure said -- what v4_boot_run does
|
|
* first. v4_boot_capsule: the capsule `name` found in the directory, its
|
|
* hash and signature checked as the boot checks them, a failure said; its
|
|
* payload, or 0. And the boot's own way of printing numbers, so the mesh
|
|
* prints the same parity lines. */
|
|
int v4_boot_assemble(const v4_boot *b);
|
|
uint32_t v4_boot_word_count(const v4_node *n, const v4_image *im);
|
|
const uint8_t *v4_boot_capsule(const v4_boot *b, const char *name, uint32_t *length, uint64_t *id, uint64_t *hash);
|
|
void v4_boot_say_dec(const v4_boot *b, uint32_t v);
|
|
void v4_boot_say_hex(const v4_boot *b, uint64_t v);
|
|
|
|
#endif /* V4_BOOT_H */
|