diff --git a/.claude/memory/rules.md b/.claude/memory/rules.md index 1928be7b..20af0c4e 100644 --- a/.claude/memory/rules.md +++ b/.claude/memory/rules.md @@ -13,6 +13,10 @@ before acting.** (Bob, 2026-11-12: "you are supposed to stop and ask when something comes up ... What if I had missed that message?") I installed qemu-user unasked when hosted-check could not find qemu-aarch64, and only said so afterwards, in a summary. Telling him after is not asking him first. +6. **Every foreground boot command is wrapped in `timeout 570`.** A boot that hangs past the tool's + 10-minute limit is moved to the background by the tool -- an acceptance run in the background, + against rule 1. It happened 2026-11-13 (a v4 boot that stopped after POST); QEMU was stopped at + once with SIGTERM. `timeout` ends it in the foreground instead. Source of authority: `.claude/CLAUDE.md` (wins), then the FABRIC record (`docs/fabric/`, FABRIC-3 living), then `docs/v4.0.0/`, `docs/reference/ROADMAP.md`, `boards/BRINGUP.md`. Code wins over all documents. diff --git a/docs/reference/v4-dictionary.tsv b/docs/reference/v4-dictionary.tsv index 72703ecd..f7b0fb63 100644 --- a/docs/reference/v4-dictionary.tsv +++ b/docs/reference/v4-dictionary.tsv @@ -321,3 +321,58 @@ XOR ( x1 x2 -- x3 ) Bitwise exclusive or. [LITERAL] ( x -- ) Another name for LITERAL, as v3 has it. \ ( -- ) \ text: a comment to the end of the line; immediate. ] ( -- ) Start compiling what follows. +(FAIL) ( -- ) A birth went wrong: print "BIRTH: node n did not do as it was told" and raise an error (centre.4th). +(JOIN) ( -- ) Join two outer nodes of a unit: port 3 of X to port 4 of Y, each told the other is there (centre.4th). +(KID#) ( -- addr ) Variable: the number of the node being born (centre.4th). +(KID) ( -- addr ) Variable: the place of the node being born (centre.4th). +(KIDC) ( -- addr ) Variable: the newborn node's own port that leads back to its parent (centre.4th). +(KIDP) ( -- addr ) Variable: this node's port that leads to the node being born (centre.4th). +(LINES) ( baddr u -- ) Send the newborn node the named capsule of FORTH blocks, a line at a time, each to be completed (centre.4th). +(N) ( k -- number ) The number of the unit's k-th outer node: the unit's number plus k (centre.4th). +(NO-NODE) ( n -- ) Drop n, print "no such node" and raise an error (hera.4th). +(NUCLEUS) ( -- ) Send the newborn node the system, the capsule of F18 code, cell by cell to its port (centre.4th). +(P1) ( -- addr ) Variable: the place of a unit's first outer node (centre.4th). +(P2) ( -- addr ) Variable: the place of a unit's second outer node (centre.4th). +(P3) ( -- addr ) Variable: the place of a unit's third outer node (centre.4th). +(P4) ( -- addr ) Variable: the place of a unit's fourth outer node (centre.4th). +(PERS#) ( -- addr ) Variable: the length of the newborn node's personality capsule's name; 0: none (centre.4th). +(PERS) ( -- addr ) Variable: the byte address of the newborn node's personality capsule's name (centre.4th). +(SAY) ( them port me -- ) Tell node me that the port, by its number, leads to node them, and that node them is its neighbour there (centre.4th). +(T#) ( -- addr ) Variable: how many characters of text for another node have been built in PAD (centre.4th). +(T) ( -- baddr u ) The text built in PAD for another node (centre.4th). +(T0) ( -- ) Begin building text for another node in PAD: empty (centre.4th). +(TC) ( c -- ) Add a character to the text being built in PAD (centre.4th). +(TELL) ( baddr u -- ) Send the newborn node the text to interpret, wait for how it ended, and fail the birth if it did not complete (centre.4th). +(THERE?) ( n -- n flag ) True if node n is live and is not Hera herself (hera.4th). +(TN) ( n -- ) Add a number and a space to the text being built in PAD (centre.4th). +(TOLD) ( how -- ) Fail the birth unless how is 1: the text completed (centre.4th). +(TS) ( baddr u -- ) Add a string to the text being built in PAD (centre.4th). +(U) ( -- addr ) Variable: the number of the unit being born; its outer nodes are this plus 1 to 4 (centre.4th). +(WHO) ( -- ) Tell the newborn node its number, where its printing goes, its default route and who is at its port back (centre.4th). +(X) ( place number -- ) Set X, the first of the two outer nodes (JOIN) joins (centre.4th). +(XN) ( -- addr ) Variable: X's number (centre.4th). +(XP) ( -- addr ) Variable: X's place (centre.4th). +(Y) ( place number -- ) Set Y, the second of the two outer nodes (JOIN) joins (centre.4th). +(YN) ( -- addr ) Variable: Y's number (centre.4th). +(YP) ( -- addr ) Variable: Y's place (centre.4th). +BIRTH ( number port childport -- place ) A node is born, numbered number, its port childport joined to this node's port: given the system and FORTH-79; its place. +BIRTH-AS ( number port childport baddr u -- place ) BIRTH, and given the capsule named by the string as its personality when u is not 0. +CAPSULE-CELL ( -- w flag ) The next cell of the capsule of F18 code this node has open, and true; 0 0 at its end. A kernel request. +CAPSULE-LINE ( baddr -- u | -1 ) The next line of the capsule of FORTH blocks this node has open, copied to baddr: its length, or -1 at its end. A kernel request. +CAPSULE-OPEN ( baddr u -- flag ) Find the named capsule, check its hash and signature, and open it to be read from its start by this node. A kernel request. +CENTRE ( n -- ) Hera only: a second centre, node n, from its port 6 to Hera's port 6, given centre.4th; it births its own unit, n+1 to n+4. +KILL ( n -- ) Hera only: node n is removed, whatever it was doing; every node is told it is gone, and Hera forgets the way to it. +NODE-BORN ( number -- place | -1 ) A node is born, empty, and entered in the registry with this node as its parent: its place, or -1 if there is no room. A kernel request. +NODE-CHILD ( number i -- child | 0 ) The number of node number's i-th live child, or 0. A kernel request. +NODE-KILL ( number -- ) Hera only: remove the node; its children become hers. A kernel request; KILL is the word to use. +NODE-ME ( -- place ) This node's place: where it is in the kernel's fabric. A kernel request. +NODE-PARITY ( place number -- ) Record the parity of a node this one birthed, under the number it was born as. A kernel request. +NODE-PLACE ( number -- place | -1 ) The node's place in the kernel's fabric, or -1: not live. A kernel request. +NODE-SLEEP ( number -- ) Hera only: put the node to sleep. A kernel request; PAUSE is the word to use. +NODE-UNWIRE ( a pa -- ) Cut the wire on port pa of node a, this node or one it birthed. A kernel request. +NODE-WAKE ( number -- ) Hera only: wake the node. A kernel request; RESUME is the word to use. +NODE-WIRE ( a pa b pb -- flag ) Join port pa of node a to port pb of node b, each this node or one it birthed: true if done. A kernel request. +PAUSE ( n -- ) Hera only: put node n to sleep; it executes nothing until RESUME. +RESUME ( n -- ) Hera only: wake node n. +UNIT ( n -- ) Birth a unit: four outer nodes, n+1 to n+4, on ports 2 to 5 of this one, joined in a square. +UNIT-KILL ( n -- ) Hera only: remove node n's children, one by one, then n. diff --git a/docs/v4.0.0/plans/2026-11-13-engine-step4-phase1-v4-in-the-kernels-boot.md b/docs/v4.0.0/plans/2026-11-13-engine-step4-phase1-v4-in-the-kernels-boot.md index aa695a9b..679813d7 100644 --- a/docs/v4.0.0/plans/2026-11-13-engine-step4-phase1-v4-in-the-kernels-boot.md +++ b/docs/v4.0.0/plans/2026-11-13-engine-step4-phase1-v4-in-the-kernels-boot.md @@ -139,10 +139,17 @@ own sources. Each its own increment, each test first, each a boot: -- [ ] **5a. A fabric in the kernel, Hera in it.** `v4/src/fabric.c` and `manage.c` into the kernel - build; Hera, node 10, at a place of a kernel fabric, entered in the registry; the prompt's lines go - to her through it. `centre.4th` and `hera.4th` loaded on her: `NODE-ME` answers her place (word - count and `dict_hash` change). +- [x] **5a. A fabric in the kernel, Hera in it.** Ruled 2026-11-13 (Captain Bob): **"Hera as now, in + a fabric"** -- she boots by `v4_boot_run` as before (the hosted product's parity kept), and her node + is place 0 of the kernel's fabric (`sk_v4.c`), entered in the registry as node 10; her registry + requests go to `v4_manage_serve` through `v4_boot`'s new `request` hook, and `centre.4th` and + `hera.4th` are loaded on her through its new `capsules` list -- before the seal, so POST runs on + them. The fabric has one place: `NODE-BORN` is answered "no room" 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. She is not told her number ((ME) stays 0): the boot's console addresses node 0. Word + count 375 (55 from the two capsules), each in the dictionary; `mkdictionary.py` loads the kernel's + capsules into the hosted system before `WORDS`, so the live list is the kernel's. + Seen to fail first: the acceptance check `NODE-ME .` gave UNKNOWN WORD; then `0 ok`. - [ ] **5b. Births.** `NODE-BORN`: room for a node from the kernel's heap, a place in the fabric, a Stadium quota granted from Hera's under the new node's key (`stadium_node_key(number)`), its opcode patrons; `NODE-KILL` gives the room and the quota back. The capsule words diff --git a/experiments/bare_metal/runs/doe-amd64-20261113-132410.csv b/experiments/bare_metal/runs/doe-amd64-20261113-132410.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-amd64-20261113-133755.csv b/experiments/bare_metal/runs/doe-amd64-20261113-133755.csv new file mode 100644 index 00000000..e69de29b diff --git a/kernel/Makefile b/kernel/Makefile index e9e49081..05171050 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -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 diff --git a/kernel/src/v4/sk_v4.c b/kernel/src/v4/sk_v4.c index 5b2e815d..91c526c9 100644 --- a/kernel/src/v4/sk_v4.c +++ b/kernel/src/v4/sk_v4.c @@ -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 */ diff --git a/logs/20261113-132410/amd64/README.txt b/logs/20261113-132410/amd64/README.txt new file mode 100644 index 00000000..b298dd05 --- /dev/null +++ b/logs/20261113-132410/amd64/README.txt @@ -0,0 +1 @@ +# #189 Phase 1 Task 5a: the registry check (qemu_session.py registry()) before Hera is in the kernel's fabric: NODE-ME is an unknown word. diff --git a/logs/20261113-132410/amd64/qemu-amd64-20261113-132410.log b/logs/20261113-132410/amd64/qemu-amd64-20261113-132410.log new file mode 100644 index 00000000..f4516cb0 --- /dev/null +++ b/logs/20261113-132410/amd64/qemu-amd64-20261113-132410.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:055e5258daca1e56dcbedd86cda6356e002f4cc4513803d5acc915c49bc96d7e +size 7299 diff --git a/logs/20261113-132410/amd64/registry-check-before.txt b/logs/20261113-132410/amd64/registry-check-before.txt new file mode 100644 index 00000000..5324e8c7 --- /dev/null +++ b/logs/20261113-132410/amd64/registry-check-before.txt @@ -0,0 +1,53 @@ +LOG: logs/20261113-132410/amd64/qemu-amd64-20261113-132410.log +PASS: POST: PASSED +PASS: Boot disk: not a StarshipOS stick (an ISO or ESP boot) +PASS: Zuse: genesis minted onto attached thumbdrive (a fresh data stick) +PASS: the Stadium: its cells allocated +PASS: the Stadium: conservation CONSERVED +PASS: the heartbeat: ticking +PASS: [zuse@Hera] ok> +PASS: 7 ok, and a prompt +FAIL: NODE-ME: Hera is at place 0 of the kernel's fabric + --- serial log, the log's last 40 lines (logs/20261113-132410/amd64/qemu-amd64-20261113-132410.log): + 142 | PARITY:V4_F18 name=v4:sys:log.f18 capsule_hash=0xfc6540e6b824a52b + 143 | V4: capsule v4:sys:acl.f18 signature: missing (unsigned) + 144 | PARITY:V4_F18 name=v4:sys:acl.f18 capsule_hash=0x6845aa048db7eb22 + 145 | V4: capsule v4:sys:end.f18 signature: missing (unsigned) + 146 | PARITY:V4_F18 name=v4:sys:end.f18 capsule_hash=0x5ef01312c07b79cc + 147 | PARITY:V4_ROM words=2372 rom_hash=0x87b23d3ae2643c8d instructions=59537530 + 148 | V4: capsule v4:forth79.4th signature: missing (unsigned) + 149 | PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x7d5965f7983485b9 + 150 | PARITY:V4_POST tests=539 pass=539 fail=0 + 151 | PARITY:V4_SYSTEM word_count=320 dict_hash=0x47570d4ced6ce255 + 152 | PARITY:OK + 153 | POST: PASSED + 154 | PCI: init... + 155 | PCI: ECAM mapped (amd64) + 156 | virtio-rng: no device on PCI bus 0 + 157 | rng: backend = rdrand + 158 | entropy: ready + 159 | virtio-input: no device on PCI bus 0 + 160 | Starting heartbeat... + 161 | APIC Timer: started (LVT=0x00020020 ICR=10052969) + 162 | + 163 | I/O APIC: keyboard IRQ1 unmasked + 164 | Heartbeat running. + 165 | Stadium opcodes: 30 resident, hottest opcode 8 + 166 | Stadium words: promotions=0 evictions=0 + 167 | Stadium conservation: resident_sum=43673 reservoir=21863 sum=65536 (Q48_ONE=65536) + 168 | Stadium conservation: CONSERVED + 169 | Stadium quota grant self-test: OK + 170 | Hera reservoir=21863 + 171 | test-vm reservoir=65536 + 172 | Heartbeat: 4 ticks, trust=0x00010000, variance=0x00000000 + 173 | blkio_usb: MSC device ready + 174 | Artemis: the stick's partition attached + 175 | \x1b[38;2;255;165;0m[Hera]\x1b[39m Zuse: genesis minted onto attached thumbdrive + 176 | \x1b[38;2;255;165;0m[zuse@Hera]\x1b[39m \x1b[38;2;85;255;255mok> \x1b[39m3 4 + . + 177 | \x1b[38;2;255;165;0m[zuse@Hera]\x1b[39m 7 ok + 178 | \x1b[38;2;255;165;0m[zuse@Hera]\x1b[39m \x1b[38;2;85;255;255mok> \x1b[39mNODE-ME . + 179 | \x1b[38;2;255;165;0m[zuse@Hera]\x1b[39m UNKNOWN WORD: 'NODE-ME' + 180 | \x1b[38;2;255;165;0m[zuse@Hera]\x1b[39m ERROR + 181 | \x1b[38;2;255;165;0m[zuse@Hera]\x1b[39m \x1b[38;2;85;255;255mok> \x1b[39m + --- +RESULT: FAIL diff --git a/logs/20261113-132712/amd64/README.txt b/logs/20261113-132712/amd64/README.txt new file mode 100644 index 00000000..ab143a41 --- /dev/null +++ b/logs/20261113-132712/amd64/README.txt @@ -0,0 +1,5 @@ +# Stopped, not a result: #189 Task 5a's first boot, with Hera told '10 (ME) !' after the boot. +# The boot passed POST, then stopped at 'StarForth v4: the node did not let go of POST's blocks': +# the console addresses node 0, and told she was 10 she let its next line go. The boot outlasted +# the tool's 10 minutes and was moved to the background; QEMU was stopped with SIGTERM at once. +# The (ME) line was taken out; every boot command is now under 'timeout 570' (memory rule 6). diff --git a/logs/20261113-132712/amd64/qemu-amd64-20261113-132712.log b/logs/20261113-132712/amd64/qemu-amd64-20261113-132712.log new file mode 100644 index 00000000..9b9c03b0 --- /dev/null +++ b/logs/20261113-132712/amd64/qemu-amd64-20261113-132712.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a52d204d2bef308a7a7b7ff7776cb41543ec26479a4168d5d6c3a6837f600b46 +size 6750 diff --git a/logs/20261113-133755/amd64/qemu-amd64-20261113-133755.log b/logs/20261113-133755/amd64/qemu-amd64-20261113-133755.log new file mode 100644 index 00000000..96afa86e --- /dev/null +++ b/logs/20261113-133755/amd64/qemu-amd64-20261113-133755.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fd725855615634147b1fbb953e364536f6f879c1ebc4a30c800b9376bc292ef +size 7605 diff --git a/logs/20261113-133755/task5a/README.txt b/logs/20261113-133755/task5a/README.txt new file mode 100644 index 00000000..e0a9cb74 --- /dev/null +++ b/logs/20261113-133755/task5a/README.txt @@ -0,0 +1,5 @@ +# #189 Phase 1 Task 5a +../../20261113-132410/amd64: the registry check before -- NODE-ME is an unknown word. +../amd64: the amd64 boot after -- NODE-ME . gives 0 ok; POST 539/539; word_count 375. +test-test32.log: make -C v4 test test-32: 99 programs, 3724180 checks, 0 failures. +../../20261113-135049/v4-checks: scripts/nightly.sh v4-checks: 79 programs under the sanitizers, 0 failures; the hosted builds alike. diff --git a/logs/20261113-133755/task5a/test-test32.log b/logs/20261113-133755/task5a/test-test32.log new file mode 100644 index 00000000..d8c5913e --- /dev/null +++ b/logs/20261113-133755/task5a/test-test32.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bc5752a75e0939831955f50f773678b24b4474d8ef7324b49c6e05bea7e5501 +size 132335 diff --git a/logs/20261113-135049/v4-checks/v4-checks-20261113-135049.log b/logs/20261113-135049/v4-checks/v4-checks-20261113-135049.log new file mode 100644 index 00000000..859b21d6 --- /dev/null +++ b/logs/20261113-135049/v4-checks/v4-checks-20261113-135049.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28bffa1c7dc97762d1356066ca0c08f2bfa3d04815b8d929a2ab5a2ec9f86321 +size 152224 diff --git a/scripts/qemu_session.py b/scripts/qemu_session.py index 7d12f2ff..494c3e41 100755 --- a/scripts/qemu_session.py +++ b/scripts/qemu_session.py @@ -487,6 +487,16 @@ def physics(): check("the heartbeat: ticking", wait_for(r"Heartbeat: [1-9]\d* ticks", 120)) +def registry(): + """Hera in the kernel's fabric and registry (#189, Phase 1 Task 5a): NODE-ME, made by centre.4th, + is answered by the kernel with her place, 0 -- as the host mesh answers it (test_host_unit.c).""" + n = count(PROMPT) + type_line("NODE-ME .") + check("NODE-ME: Hera is at place 0 of the kernel's fabric", + wait_for(PROMPT, 60, n) and re.search(r"(?:^|[^0-9])0\s+ok", text()[-200:]) is not None + and "UNKNOWN WORD" not in text()[-200:]) + + check("POST: PASSED", wait_for(r"POST: PASSED", 900)) m = a.mode @@ -503,6 +513,7 @@ elif m == "zuse": n = count(PROMPT) type_line("3 4 + .") check("7 ok, and a prompt", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()) is not None) + registry() elif m in ("stick_boot", "two_sticks"): def disks(): @@ -575,6 +586,7 @@ elif m in ("genesis", "stick_genesis"): n = count(PROMPT) type_line("3 4 + .") check("7 ok", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()[-400:]) is not None) + registry() check("no fault", count(r"EXCEPTION|PANIC|panic") == 0) if m == "genesis": shutdown() diff --git a/v4/include/v4/boot.h b/v4/include/v4/boot.h index 08786039..652ef678 100644 --- a/v4/include/v4/boot.h +++ b/v4/include/v4/boot.h @@ -62,6 +62,22 @@ typedef struct { 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. */ diff --git a/v4/system/boot.c b/v4/system/boot.c index 5b2caaba..a5b2b8f7 100644 --- a/v4/system/boot.c +++ b/v4/system/boot.c @@ -268,6 +268,8 @@ static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep } else if (b->serve && n->request >= 1 && n->request <= (v4_cell)b->word_count) { b->serve(n, (unsigned)n->request); v4_node_port_served(n); + } else if (b->request && b->request(b->request_self, n, n->request)) { + v4_node_port_served(n); /* the registry's, served or refused (#189) */ } else { /* a request no one serves is an error, as any other (D-18): * 12, Argument out of range */ @@ -596,6 +598,8 @@ int v4_boot_run(const v4_boot *b) for (i = 0; i < sizeof boot_capsules / sizeof boot_capsules[0]; i++) if (!load_capsule(b, boot_capsules[i])) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; } + for (i = 0; i < b->capsule_count; i++) /* the kernel's own (#189) */ + if (!load_capsule(b, b->capsules[i])) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; } /* The kernel's words and the capsules are the system: COLD comes back * to here, and FORGET will not go below it. */ v4_image_seal(b->n, b->im); diff --git a/v4/tools/hosted.c b/v4/tools/hosted.c index cd6a0451..ea26190a 100644 --- a/v4/tools/hosted.c +++ b/v4/tools/hosted.c @@ -84,6 +84,8 @@ int main(void) boot.n = &n; boot.es = &es; boot.h = &h; boot.im = &im; boot.out = console_out; boot.key = console_key; boot.words = kernel_words; boot.word_count = sizeof kernel_words / sizeof kernel_words[0]; boot.serve = serve; + boot.request = 0; boot.request_self = 0; /* no registry, and no capsules of its own: the hosted product */ + boot.capsules = 0; boot.capsule_count = 0; sf_time_init(); if (blk_subsys_init(block_ram, sizeof block_ram) != BLK_OK) { fputs("V4: no block storage\n", stderr); return 1; } if (!v4_boot_run(&boot)) { fflush(stdout); return 1; } diff --git a/v4/tools/mkdictionary.py b/v4/tools/mkdictionary.py index 1ad21e6d..782db243 100644 --- a/v4/tools/mkdictionary.py +++ b/v4/tools/mkdictionary.py @@ -14,8 +14,8 @@ with its stack notation, a description, and where it is defined. v4/tools/mkdictionary.py md TSV OUT.md the dictionary as Markdown, alphabetical, for the book The live word list is the system's own: the hosted v4 (`make -C v4 hosted`) booted and asked -`WORDS`, plus the kernel's words of the lone node (`sk_v4_words[]` in kernel/src/v4/sk_v4.c) -- -the words a booted StarshipOS has. The table (docs/reference/v4-dictionary.tsv) holds the stack +`WORDS` with the kernel's own capsules loaded (`sk_v4_capsules[]`), plus the kernel's words +(`sk_v4_words[]`), both in kernel/src/v4/sk_v4.c -- the words a booted StarshipOS has. The table (docs/reference/v4-dictionary.tsv) holds the stack notation and the description, which only a person can write; the names are never typed by hand. """ import os, re, subprocess, sys @@ -29,22 +29,30 @@ SOURCES = ([os.path.join("capsules/v4/sys", f) for f in sorted(os.listdir(os.pat KERNEL_WORDS_SRC = "kernel/src/v4/sk_v4.c" +def kernel_capsules(): + """The kernel's own capsules (sk_v4_capsules[] in kernel/src/v4/sk_v4.c), as files of capsules/v4.""" + src = open(os.path.join(ROOT, KERNEL_WORDS_SRC)).read() + m = re.search(r"sk_v4_capsules\[\]\s*=\s*\{([^}]*)\}", src) + return [os.path.join("capsules/v4", n.split(":", 1)[1]) for n in re.findall(r'"([^"]+)"', m.group(1) if m else "")] + + def live_words(): - """The hosted system's WORDS, then the kernel's own words not among them.""" + """The hosted system's WORDS, with the kernel's own capsules loaded first, as the kernel loads + them (#189: centre.4th, hera.4th); then the kernel's own words not among them.""" if not os.path.exists(HOSTED): sys.exit(f"mkdictionary: {HOSTED} is missing: make -C v4 hosted") - out = subprocess.run([HOSTED], input="WORDS\n", capture_output=True, text=True, timeout=120).stdout - lines = out.split("\n") - try: - start = next(k for k, l in enumerate(lines) if l.startswith("ok> ") and l[4:].strip()) - except StopIteration: + feed = [] + for path in kernel_capsules(): + for l in open(os.path.join(ROOT, path)): + if not re.match(r"^Block \d+\s*$", l): + feed.append(l.rstrip("\n")) + feed.append('." [[WORDS]]" CR WORDS') + out = subprocess.run([HOSTED], input="\n".join(feed) + "\n", capture_output=True, text=True, timeout=120).stdout + if "ERROR" in out.split("[[WORDS]]")[0]: + sys.exit("mkdictionary: a kernel capsule did not load in the hosted system") + if "[[WORDS]]" not in out: sys.exit("mkdictionary: the hosted system printed no WORDS") - got = [] - for l in lines[start:]: - if l.startswith("ok> ") and got: - break - got.append(l[4:] if l.startswith("ok> ") else l) - text = " ".join(got) + text = out.split("[[WORDS]]", 1)[1].replace("ok>", " ") if " ok" in text: text = text[:text.rfind(" ok")] words = text.split()