Files
LithosAnanake/v4/system/boot.c
T
rajamesandClaude Opus 5.5 2930349bbf feat(v4.0.0): POST passes and is part of the boot; U* and U/MOD
The boot is now nucleus, forth79.4th, POST, prompt, on both products.

- forth79.4th: U* and U/MOD, the capsule's first colon definitions.  They
  are in the FORTH-79 Required Word Set and neither v3 nor v4 had them.
- post79.4th: 550 cases, 126 of the 130 required words.  443 are v3's with
  v3's result.  The rest follow three rulings (2026-10-05): address-
  dependent cases are checked for count, not value; where v3 departs from
  FORTH-79 the standard's result is expected; words v3 has no case for get
  cases written by hand.  v4/tools/post79_rules.py holds each exception
  with its reason and docs/v4.0.0/POST79.md lists them all.
- every case starts from an empty stack, DECIMAL and FORTH DEFINITIONS
- the boot requires POST's tally line with fail=0

Verified: tests=550 pass=550 fail=0 and identical PARITY lines on hosted
amd64, aarch64 and riscv64 (make -C v4 hosted-check) and on bare metal,
clean qemu with STARFORTH_V4=1, on the same three (logs/20261005-1619xx,
-1621xx, -1625xx).  A U/MOD broken on purpose fails five cases and stops
the boot.  make -C v4 test passes.

Not shown: all words but those two are still assembled, so POST has so far
tested the assembled words.  Nothing was typed at a bare-metal prompt.
Open: PAD 42 OVER ! faults on v4 (D-1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:28:01 -04:00

315 lines
10 KiB
C

/* boot.c -- the nucleus, then its capsules, then the prompt. See boot.h.
*
* Nothing here uses the C library: the bare-metal kernel links this file.
* It is not part of the engine (v4/src): it needs the capsule directory,
* which only a whole system has.
*/
#include "v4/boot.h"
#include "starkernel/capsule.h"
#include "starkernel/capsule_generated.h"
#include "starkernel/capsule_sig.h"
#include "starkernel/capsule_blocks.h"
/* The capsules, in the order they are loaded. */
/* POST is part of the boot: v4:post79.4th is loaded after the vocabulary it
* tests and must end with a clean tally. -DV4_POST_AT_BOOT=0 leaves it
* out, for work on a vocabulary that does not pass yet. */
#ifndef V4_POST_AT_BOOT
#define V4_POST_AT_BOOT 1
#endif
static const char *const boot_capsules[] = {
"v4:forth79.4th",
#if V4_POST_AT_BOOT
"v4:post79.4th",
#endif
};
#define LINE_MAX 80u /* QUERY takes 80 characters, the new-line among them */
#define STEP_LIMIT 4000000000ULL /* instruction words one line may take */
/* ---- printing ------------------------------------------------------------ */
static void say(const v4_boot *b, const char *s)
{
unsigned len = 0;
while (s[len]) len++;
b->out(s, len);
}
static void say_dec(const v4_boot *b, uint32_t v)
{
char buf[10];
unsigned i = sizeof buf;
do { buf[--i] = (char)('0' + v % 10u); v /= 10u; } while (v);
b->out(buf + i, (unsigned)sizeof buf - i);
}
static void say_hex(const v4_boot *b, uint64_t v)
{
char buf[18];
unsigned i;
buf[0] = '0'; buf[1] = 'x';
for (i = 0; i < 16; i++) buf[2 + i] = "0123456789abcdef"[(v >> (60 - 4 * i)) & 15u];
b->out(buf, sizeof buf);
}
/* ---- hashing: FNV-1a, 64 bits, as kernel/src/vm/parity.c ----------------- */
#define FNV_OFFSET 0xcbf29ce484222325ULL
#define FNV_PRIME 0x00000100000001b3ULL
static uint64_t hash_cell(uint64_t h, v4_cell c)
{
v4_ucell u = (v4_ucell)c;
unsigned i;
for (i = 0; i < V4_CELL_BITS / 8; i++) {
h ^= (uint64_t)((u >> (8 * i)) & 0xffu);
h *= FNV_PRIME;
}
return h;
}
uint64_t v4_boot_dict_hash(const v4_node *n, const v4_image *im)
{
uint64_t h = FNV_OFFSET;
v4_cell here = (n->mem[im->dp] + 3) / 4, k;
if (here < 0) here = 0;
if (here > (v4_cell)V4_NODE_WORDS) here = (v4_cell)V4_NODE_WORDS;
for (k = 0; k < here; k++) h = hash_cell(h, n->mem[k]);
return hash_cell(h, n->mem[im->latest]);
}
static uint64_t image_hash(const v4_image *im)
{
uint64_t h = FNV_OFFSET;
unsigned i;
for (i = 0; i < im->count; i++) {
h = hash_cell(h, im->cells[i].addr);
h = hash_cell(h, im->cells[i].value);
}
return h;
}
/* how many words FORTH holds: the list from LATEST, each entry's link in the
* cell before its code */
static uint32_t word_count(const v4_node *n, const v4_image *im)
{
v4_cell xt = n->mem[im->latest];
uint32_t count = 0;
while (xt > 0 && xt < (v4_cell)V4_NODE_WORDS && count < (uint32_t)V4_NODE_WORDS) {
count++;
xt = n->mem[xt - 1];
}
return count;
}
/* ---- running the node ---------------------------------------------------- */
/* The node ends every line it has taken with " ok" and the next prompt.
* Those eight characters are held back from the console, so that what is
* shown is only what the line itself printed. */
static const char accepted[8] = { ' ', 'o', 'k', '\n', 'o', 'k', '>', ' ' };
typedef struct {
char held[8];
unsigned len;
} tail;
/* POST's verdict. The POST capsule ends by printing one line,
* PARITY:V4_POST tests=N pass=N fail=N
* and the boot passes only if it has seen that line with fail=0. That no
* line was refused is not enough: a POST that was broken half way would
* refuse nothing. */
static char post_line[96];
static unsigned post_len;
static int post_seen, post_clean;
static int text_at(const char *line, unsigned len, unsigned at, const char *want)
{
unsigned i;
for (i = 0; want[i]; i++)
if (at + i >= len || line[at + i] != want[i]) return 0;
return 1;
}
static void post_watch(char c)
{
static const char clean[] = " fail=0";
unsigned i;
if (c != '\n') {
if (post_len < sizeof post_line) post_line[post_len++] = c;
return;
}
/* the node's prompt comes before it on the line, so look along the line */
for (i = 0; i < post_len; i++)
if (text_at(post_line, post_len, i, "PARITY:V4_POST ")) {
post_seen = 1;
post_clean = post_len >= sizeof clean - 1 && text_at(post_line, post_len, post_len - (unsigned)(sizeof clean - 1), clean);
break;
}
post_len = 0;
}
static void tail_put(const v4_boot *b, tail *t, int show, char c)
{
unsigned i;
if (t->len == sizeof t->held) {
if (show) b->out(t->held, 1);
for (i = 1; i < sizeof t->held; i++) t->held[i - 1] = t->held[i];
t->len--;
}
t->held[t->len++] = c;
}
/* Run until the node waits for a character. Returns 1 if what it printed
* ended with " ok" and the prompt; 0 if not -- the rest is then shown too --
* or if the node stopped or never came back. */
static int run_to_prompt(const v4_boot *b, int show)
{
v4_node *n = b->n;
uint64_t steps = 0;
unsigned i;
tail t;
t.len = 0;
for (;;) {
(void)v4_exec_step_word(n, b->es, b->h);
if (n->console_len) {
for (i = 0; i < n->console_len; i++) {
post_watch((char)n->console[i]);
tail_put(b, &t, show, (char)n->console[i]);
}
n->console_len = 0;
}
if (n->stopped) { say(b, "\nV4: the node stopped on a fault\n"); return 0; }
if (v4_image_waiting(n, b->im)) break;
if (++steps > STEP_LIMIT) { say(b, "\nV4: the node did not come back to its prompt\n"); return 0; }
}
if (t.len == sizeof t.held) {
for (i = 0; i < sizeof t.held && t.held[i] == accepted[i]; i++) { }
if (i == sizeof t.held) return 1;
}
if (show) b->out(t.held, t.len);
return 0;
}
/* ---- one capsule --------------------------------------------------------- */
static void say_where(const v4_boot *b, const char *name, uint32_t block, uint32_t line)
{
say(b, "\nV4: capsule "); say(b, name);
say(b, " block "); say_dec(b, block);
say(b, " line "); say_dec(b, line);
}
static int load_capsule(const v4_boot *b, const char *name)
{
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 *p, *end;
CapsuleValidateResult vr;
CapsuleSigResult sr;
uint32_t block = 0, line = 0;
int in_block = 0;
cap = capsule_find_by_name(dir, descs, names, name);
if (!cap) { say(b, "V4: capsule "); say(b, name); say(b, " is not in this binary\n"); return 0; }
vr = capsule_validate(cap, arena, dir->arena_size, 1);
if (vr != CAPSULE_VALID) {
say(b, "V4: capsule "); say(b, name); say(b, ": "); say(b, capsule_validate_result_str(vr)); say(b, "\n");
return 0;
}
sr = capsule_verify_signature(descs, names, capsule_get_signatures(), arena, dir->desc_count, (int)(cap - descs));
if (sr != CAPSULE_SIG_OK) {
say(b, "V4: capsule "); say(b, name); say(b, " signature: "); say(b, capsule_sig_result_str(sr)); say(b, "\n");
if (sr == CAPSULE_SIG_INVALID) return 0;
}
p = capsule_get_payload(cap, arena);
if (!p) { say(b, "V4: capsule "); say(b, name); say(b, " has no payload\n"); return 0; }
end = p + cap->length;
while (p < end) {
const uint8_t *after, *nl;
uint32_t num;
unsigned len, i;
char text[LINE_MAX];
if (capsule_block_header(p, end, &num, &after)) {
block = num; line = 0; in_block = 1; p = after;
continue;
}
for (nl = p; nl < end && *nl != '\n'; nl++) { }
len = (unsigned)(nl - p);
if (len && p[len - 1] == '\r') len--;
if (in_block) {
line++;
if (len >= LINE_MAX) { say_where(b, name, block, line); say(b, " is too long\n"); return 0; }
if (len) {
for (i = 0; i < len; i++) text[i] = (char)p[i];
text[len] = '\n';
if (v4_node_console_feed(b->n, text, len + 1u) != len + 1u) {
say_where(b, name, block, line); say(b, ": the node's input is full\n");
return 0;
}
if (!run_to_prompt(b, 1)) {
say_where(b, name, block, line); say(b, " was not accepted: ");
b->out(text, len); say(b, "\n");
return 0;
}
}
}
p = (nl < end) ? nl + 1 : end;
}
say(b, "PARITY:V4_CAPSULE name="); say(b, name);
say(b, " capsule_id="); say_hex(b, cap->capsule_id);
say(b, " capsule_hash="); say_hex(b, cap->content_hash);
say(b, " dict_hash="); say_hex(b, v4_boot_dict_hash(b->n, b->im));
say(b, "\n");
return 1;
}
/* ---- the whole boot ------------------------------------------------------ */
int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks)
{
unsigned i;
post_len = 0; post_seen = 0; post_clean = 0;
if (!v4_image_boot(b->n, b->es, b->h, b->im, disk, blocks)) {
say(b, "V4: the nucleus image is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n");
return 0;
}
say(b, "PARITY:V4_NUCLEUS words="); say_dec(b, word_count(b->n, b->im));
say(b, " image_hash="); say_hex(b, image_hash(b->im));
say(b, "\n");
/* the node's own first prompt is not shown: the boot prints one at the end */
(void)run_to_prompt(b, 0);
if (b->n->stopped) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
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; }
#if V4_POST_AT_BOOT
if (!post_seen || !post_clean) {
say(b, post_seen ? "V4: POST reported failures\n" : "V4: POST did not report\n");
say(b, "PARITY:FAIL\nPOST: FAILED\n");
return 0;
}
#endif
#if V4_POST_AT_BOOT
say(b, "PARITY:OK\nPOST: PASSED\nok> ");
#else
say(b, "PARITY:OK\nok> ");
#endif
return 1;
}