feat(kernel): Hera in the kernel's fabric and registry; centre.4th and hera.4th on her
#189, ENGINE step 4 Phase 1, Task 5a. Captain Bob, 2026-11-13: "Full births now"; "Hera as now, in a fabric". - sk_v4.c: the kernel's fabric (v4/fabric.h), Hera's node at its one place, entered in its registry (v4/manage.h) as node 10. The registry's answers are real: a birth is "no room" -- the fabric has one place until 5b; CAPSULE-OPEN/-LINE read the kernel's capsule directory, checked as the boot checks (no capsule of F18 code is there until 5b); a born node's parity line. She is not told her number: the boot's console addresses node 0 (told 10, the boot stopped after POST -- kept, labelled, logs/20261113-132712). - boot.h/boot.c: v4_boot's `request` hook (the registry's requests) and `capsules` (the kernel's own, loaded before the seal, so POST runs on them). hosted.c sets neither: the hosted product's boot is as it was. - kernel/Makefile: manage.c in the kernel's v4 engine. - qemu_session.py: a v4 acceptance boot asks NODE-ME . and wants 0. - v4-dictionary.tsv: the 55 words of centre.4th and hera.4th, written from their source and manage.h; mkdictionary.py loads the kernel's capsules into the hosted system before WORDS, so the live list is the kernel's -- 375 words. - .claude/memory/rules.md, rule 6: every boot command under timeout 570. Seen to fail first (logs/20261113-132410): NODE-ME was an unknown word. After: amd64 NODE-ME . gives 0 ok; POST 539/539; word_count 375. v4 suite: test, test-32 99 programs 0 failures; v4-checks 79 under the sanitizers 0 failures, the hosted builds alike. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
This commit is contained in:
+1
-1
@@ -727,7 +727,7 @@ V4_DEFS := -DSTARFORTH_V4=1 -DSK_V4_DEFAULT=$(V4_DEFAULT) -Iv4/include \
|
||||
KERNEL_CFLAGS += $(V4_DEFS)
|
||||
LOADER_CFLAGS += $(V4_DEFS)
|
||||
|
||||
V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c message.c wire.c)
|
||||
V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c message.c wire.c manage.c)
|
||||
V4_ROM_C := $(BUILD_DIR)/v4_rom_64.c
|
||||
V4_MAP_C := $(BUILD_DIR)/v4_map_64.c
|
||||
|
||||
|
||||
+142
-5
@@ -32,7 +32,13 @@
|
||||
#include "starkernel/arch.h"
|
||||
#include "starkernel/timer.h"
|
||||
#include "v4/boot.h"
|
||||
#include "v4/fabric.h"
|
||||
#include "v4/manage.h"
|
||||
#include "block_subsystem.h"
|
||||
#include "starkernel/capsule.h"
|
||||
#include "starkernel/capsule_generated.h"
|
||||
#include "starkernel/capsule_sig.h"
|
||||
#include "starkernel/capsule_blocks.h"
|
||||
#ifdef STARFORTH_ENABLE_VM
|
||||
#include "starkernel/vm/stadium.h"
|
||||
#include "starkernel/vm/stadium_ops.h"
|
||||
@@ -44,10 +50,119 @@
|
||||
* console's own "[zuse@Hera] " line prefix. */
|
||||
#define SK_V4_PROMPT "\x1b[38;2;85;255;255mok> \x1b[39m"
|
||||
|
||||
static v4_node sk_v4_node;
|
||||
static v4_exec_state sk_v4_es;
|
||||
static v4_heat sk_v4_heat;
|
||||
static v4_image sk_v4_image;
|
||||
/* THE KERNEL'S FABRIC (#189, ENGINE step 4 Phase 1, Task 5a; Captain Bob, 2026-11-13: "Hera as now,
|
||||
* in a fabric"). Hera -- node 10, the one the kernel brings up -- is booted as before (v4_boot_run)
|
||||
* and is the node at place 0 of the kernel's fabric, entered in its registry (v4/manage.h,
|
||||
* MESH.md 9a): her registry requests (centre.4th, hera.4th) are served by v4_manage_serve. The
|
||||
* fabric has her one place: a birth is refused, "no room" (-1), until the fabric has room for more
|
||||
* (Task 5b). */
|
||||
#define SK_V4_PLACES 1u
|
||||
static v4_fabric_node sk_v4_hera; /* her node, its exec state and its heat */
|
||||
static v4_place sk_v4_places[SK_V4_PLACES];
|
||||
static v4_fabric sk_v4_fabric;
|
||||
static v4_manage sk_v4_manage;
|
||||
static v4_manage_entry sk_v4_registry[SK_V4_PLACES];
|
||||
static unsigned sk_v4_hera_place;
|
||||
static v4_image sk_v4_image;
|
||||
#define sk_v4_heat (sk_v4_hera.h)
|
||||
|
||||
/* What each place has open of a capsule (CAPSULE-OPEN, -LINE): a FORTH capsule's payload, read a
|
||||
* block line at a time, as the boot reads one (v4/system/boot.c, load_capsule). */
|
||||
typedef struct { const uint8_t *p, *end; int in_block; } sk_v4_reader;
|
||||
static sk_v4_reader sk_v4_reads[SK_V4_PLACES];
|
||||
|
||||
static int sk_v4_born(void *self)
|
||||
{
|
||||
(void)self;
|
||||
return -1; /* the fabric has no room beyond Hera's place (Task 5b gives it room) */
|
||||
}
|
||||
static void sk_v4_gone(void *self, unsigned place, v4_fabric_node *was)
|
||||
{
|
||||
(void)self; (void)was;
|
||||
if (place < SK_V4_PLACES) sk_v4_reads[place].p = sk_v4_reads[place].end = 0;
|
||||
}
|
||||
/* The capsule `name`: found in the kernel's directory, its content and signature checked as the
|
||||
* boot checks them (a signature that does not verify refuses it), and opened for `asker`. Only a
|
||||
* capsule of FORTH blocks can be opened: there is none of F18 code in the directory to read a cell
|
||||
* at a time until Task 5b puts the nucleus there. */
|
||||
static int sk_v4_open(void *self, unsigned asker, const char *name, unsigned len)
|
||||
{
|
||||
const CapsuleDirHeader *dir = capsule_get_directory();
|
||||
const CapsuleDesc *descs = capsule_get_descriptors();
|
||||
const CapsuleNameEntry *names = capsule_get_names();
|
||||
const uint8_t *arena = capsule_get_arena();
|
||||
const CapsuleDesc *cap;
|
||||
const uint8_t *payload;
|
||||
char cname[V4_MANAGE_LINE + 1u];
|
||||
unsigned i;
|
||||
|
||||
(void)self;
|
||||
if (asker >= SK_V4_PLACES || len == 0 || len > V4_MANAGE_LINE) return 0;
|
||||
for (i = 0; i < len; i++) cname[i] = name[i];
|
||||
cname[len] = 0;
|
||||
if (len < 4 || cname[len - 4] != '.' || cname[len - 3] != '4' || cname[len - 2] != 't' || cname[len - 1] != 'h') return 0;
|
||||
cap = capsule_find_by_name(dir, descs, names, cname);
|
||||
if (!cap || capsule_validate(cap, arena, dir->arena_size, 1) != CAPSULE_VALID) return 0;
|
||||
if (capsule_verify_signature(descs, names, capsule_get_signatures(), arena, dir->desc_count, (int)(cap - descs))
|
||||
== CAPSULE_SIG_INVALID) return 0;
|
||||
payload = capsule_get_payload(cap, arena);
|
||||
if (!payload) return 0;
|
||||
sk_v4_reads[asker].p = payload;
|
||||
sk_v4_reads[asker].end = payload + cap->length;
|
||||
sk_v4_reads[asker].in_block = 0;
|
||||
return 1;
|
||||
}
|
||||
static int sk_v4_cell(void *self, unsigned asker, v4_cell *w)
|
||||
{
|
||||
(void)self; (void)asker; (void)w;
|
||||
return 0; /* no capsule of F18 code is open: none can be (sk_v4_open) */
|
||||
}
|
||||
/* The next line of the open capsule that is part of a block, at most V4_MANAGE_LINE characters:
|
||||
* its length, or -1 at its end (or a line too long, which no capsule mkcapsule passes has). */
|
||||
static int sk_v4_line(void *self, unsigned asker, char *text)
|
||||
{
|
||||
sk_v4_reader *r;
|
||||
(void)self;
|
||||
if (asker >= SK_V4_PLACES) return -1;
|
||||
r = &sk_v4_reads[asker];
|
||||
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 > V4_MANAGE_LINE) { 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) return (int)len;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/* A node the asker birthed has booted: its parity line, as the boot prints a capsule's. */
|
||||
static void sk_v4_parity(void *self, unsigned place, v4_cell number, const v4_node *n)
|
||||
{
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
uint64_t h = v4_image_dict_hash(n, &sk_v4_image);
|
||||
char line[64];
|
||||
unsigned at = 0, k;
|
||||
const char *s;
|
||||
(void)self; (void)place;
|
||||
for (s = "PARITY:V4_BIRTH node="; *s; s++) line[at++] = *s;
|
||||
{ char d[20]; unsigned nd = 0; v4_ucell v = (v4_ucell)number; do { d[nd++] = (char)('0' + v % 10u); v /= 10u; } while (v && nd < 20);
|
||||
while (nd) line[at++] = d[--nd]; }
|
||||
for (s = " dict_hash=0x"; *s; s++) line[at++] = *s;
|
||||
for (k = 16; k-- > 0; ) line[at++] = hex[(h >> (4 * k)) & 0xFu];
|
||||
line[at] = 0;
|
||||
console_println(line);
|
||||
}
|
||||
static int sk_v4_request(void *self, v4_node *n, v4_cell request)
|
||||
{
|
||||
(void)self; (void)n;
|
||||
return v4_manage_serve(&sk_v4_manage, sk_v4_hera_place, request);
|
||||
}
|
||||
static const char *const sk_v4_capsules[] = { "v4:centre.4th", "v4:hera.4th" };
|
||||
|
||||
static char sk_v4_text[SK_V4_LINE + 1u];
|
||||
|
||||
@@ -170,7 +285,25 @@ void sk_v4_boot(void (*chain)(void), const BootDisk *boot_disk)
|
||||
{
|
||||
console_println("StarForth v4: one host node, the F18-derived engine");
|
||||
sk_v4_image = v4_map_image;
|
||||
boot.n = &sk_v4_node; boot.es = &sk_v4_es; boot.h = &sk_v4_heat; boot.im = &sk_v4_image;
|
||||
/* the kernel's fabric, Hera at its one place, entered in its registry as node 10 (#189) */
|
||||
v4_fabric_init(&sk_v4_fabric, sk_v4_places, SK_V4_PLACES);
|
||||
v4_fabric_gone_error(&sk_v4_fabric, V4_ERROR_NO_ONE);
|
||||
{
|
||||
int place = v4_fabric_add(&sk_v4_fabric, &sk_v4_hera, sk_v4_image.port);
|
||||
if (place < 0) { console_println("StarForth v4: no place for Hera in the fabric"); sk_v4_stop(); }
|
||||
sk_v4_hera_place = (unsigned)place;
|
||||
}
|
||||
sk_v4_manage.f = &sk_v4_fabric; sk_v4_manage.reg = sk_v4_registry; sk_v4_manage.reg_cap = SK_V4_PLACES;
|
||||
sk_v4_manage.hera = sk_v4_hera_place;
|
||||
sk_v4_manage.born = sk_v4_born; sk_v4_manage.gone = sk_v4_gone; sk_v4_manage.open = sk_v4_open;
|
||||
sk_v4_manage.cell = sk_v4_cell; sk_v4_manage.line = sk_v4_line; sk_v4_manage.parity = sk_v4_parity;
|
||||
sk_v4_manage.self = 0;
|
||||
if (!v4_manage_enter(&sk_v4_manage, (v4_cell)SK_V4_HERA_NODE, sk_v4_hera_place, 0)) {
|
||||
console_println("StarForth v4: Hera not entered in the registry"); sk_v4_stop();
|
||||
}
|
||||
boot.n = &sk_v4_hera.n; boot.es = &sk_v4_hera.es; boot.h = &sk_v4_hera.h; boot.im = &sk_v4_image;
|
||||
boot.request = sk_v4_request; boot.request_self = 0;
|
||||
boot.capsules = sk_v4_capsules; boot.capsule_count = sizeof sk_v4_capsules / sizeof sk_v4_capsules[0];
|
||||
boot.out = sk_v4_out; boot.key = sk_v4_key;
|
||||
boot.words = sk_v4_words; boot.word_count = sizeof sk_v4_words / sizeof sk_v4_words[0]; boot.serve = sk_v4_serve;
|
||||
blk_subsys_init(sk_v4_post_blk_ram, sizeof(sk_v4_post_blk_ram));
|
||||
@@ -178,6 +311,10 @@ void sk_v4_boot(void (*chain)(void), const BootDisk *boot_disk)
|
||||
console_println("StarForth v4: not started");
|
||||
sk_v4_stop(); /* #177: halts, not a 100% spin */
|
||||
}
|
||||
/* She is not told her number ((ME) stays 0): the boot's console addresses its lines to node 0,
|
||||
* and a node takes a message as its own only when its first word is its number (quit.f18). The
|
||||
* registry knows her as 10; NODE-ME answers her place. (Told 10, she let the console's next line
|
||||
* go -- the boot stopped at "did not let go of POST's blocks", 2026-11-13.) */
|
||||
if (chain) {
|
||||
/* the blocks are other blocks now: what the node has in its two
|
||||
* buffers is POST's, and is let go */
|
||||
|
||||
Reference in New Issue
Block a user