Files
LithosAnanake/v4/system/boot.c
T
rajamesandClaude Opus 5.5 b1d09af043 feat(v4.0.0): messages are whole and wait on the wire -- a node keeps none but the one it is doing
Step 6e, tasks 2 and 3 (docs/v4.0.0/MESH.md 7d).  The nucleus puts, looks
at, takes, moves and drops whole messages through the six addresses after
its ports; its own ring of messages waiting, the refusals owed, (GATE) and
the looking words' use are gone.  SEND, an answer, a NACK and a GONE go or
are refused at once; what a text prints waits for room; a text is not
begun until the wire its answer goes by has room for it, and that room is
kept.  The lone node's host (v4/system/boot.c) keeps its two wires and
does its operations; the test hosts' consoles put and take whole messages
through the fabric.  New: tests/test_host_depth.c, every message path of
a mesh node at every depth of both stacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:59:37 -04:00

540 lines
23 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/post.h"
#include "v4/blocks.h"
#include "v4/boot.h"
#include "v4/message.h"
#include "v4/wire.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. */
static const char *const boot_capsules[] = {
"v4:forth79.4th",
};
/* POST is part of the boot, and is the kernel's (v4/include/v4/post.h;
* docs/v4.0.0/NUCLEUS.md 6.3): when the capsules are in and the system is
* sealed, the kernel feeds its cases to the node and judges them, puts the
* node back to the system as sealed, and the boot passes only if none
* failed. -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
#define LINE_MAX 1025u /* a line is at most 1024 characters, a block */
#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 ----------------- */
uint64_t v4_boot_dict_hash(const v4_node *n, const v4_image *im)
{
return v4_image_dict_hash(n, im);
}
/* 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;
}
/* ---- handing the node a line ---------------------------------------------- */
/* The boot is the node's neighbour on two of its ports. On port 0 it is
* the kernel: the node writes the number of a request there, a word
* (ENGINE.md 3.3) -- for a block (blocks.h), or for one of the host's
* words. On port 1 it is the console: it puts a message of text on the
* node's wire and takes off it the messages of what the node printed and
* of how the text ended (message.h). */
#define KERNEL_PORT 0u
#define CONSOLE_PORT 1u
#define CONSOLE_ID 1 /* the console's number as a sender; the node's is 0 until it is given one */
/* What the kernel keeps of the node's block window (blocks.h). */
static v4_blocks boot_blocks;
/* THE WIRES (docs/v4.0.0/MESH.md 7d.5). The lone node has no fabric: the
* boot keeps the queues of its two wires, a queue each way (wire.h), and
* does what the node asks of them, as a fabric does for a node of a mesh
* (fabric.c). Every other port has no one on it. The kernel's wire has
* its queues as any wire has; the kernel puts nothing on it and takes
* nothing off: its requests are words, not messages. */
static v4_wire_queue boot_wire[2][2]; /* by port: [0] to the node, [1] from it */
#define TO_NODE(port) (&boot_wire[port][0])
#define FROM_NODE(port) (&boot_wire[port][1])
/* 1 if `count` words from `addr` are the node's memory and not its ports
* or the addresses after them: they may be copied to and from. */
static int span_ok(const v4_node *n, v4_cell addr, unsigned count)
{
if (count == 0) return 1;
if (count > V4_NODE_WORDS || addr < 0 || (v4_ucell)addr > (v4_ucell)(V4_NODE_WORDS - count)) return 0;
if (n->port >= 0 && addr <= n->port + (v4_cell)V4_PORT_HAVE && addr + (v4_cell)count > n->port) return 0;
return 1;
}
static int boot_arrived(void)
{
return TO_NODE(KERNEL_PORT)->arrived || TO_NODE(CONSOLE_PORT)->arrived;
}
/* The node is blocked at an operation on a wire (node.h, v4_node_doing):
* do it, whole, and let the node go on; the operations and their answers
* are MESH.md 7d.3's, as fabric.c does them. Returns 0 if the node sleeps
* on: it asked to sleep (8), and no message has come since it last asked
* which wires have one; or for room (9) that is not there. */
static int boot_do(const v4_boot *bt)
{
v4_node *n = bt->n;
v4_wire_queue *rx = n->wire <= CONSOLE_PORT ? TO_NODE(n->wire) : 0, *tx = n->wire <= CONSOLE_PORT ? FROM_NODE(n->wire) : 0;
v4_cell a = n->wire_a, b = n->wire_b, h[V4_WIRE_HEADER], how = V4_WIRE_NO_ONE;
unsigned cells;
switch (n->do_op) {
case 1: /* look */
if (!rx) break;
if (rx->mark >= rx->used) how = V4_WIRE_NO_MESSAGE;
else how = span_ok(n, a, V4_WIRE_HEADER) ? v4_wire_look(rx, &n->mem[a]) : V4_WIRE_NOT_A_MESSAGE;
break;
case 2: /* take */
if (!rx) break;
if (v4_wire_look(rx, h) != V4_WIRE_DONE) { how = V4_WIRE_NO_MESSAGE; break; }
cells = v4_wire_cells(h[6]) - V4_WIRE_HEADER;
if (!span_ok(n, a, V4_WIRE_HEADER) || !span_ok(n, b, cells)) how = V4_WIRE_NOT_A_MESSAGE;
else how = v4_wire_take(rx, &n->mem[a], cells ? &n->mem[b] : &n->mem[0], cells);
break;
case 3: /* move */
if (!rx) break;
if (rx->mark >= rx->used) how = V4_WIRE_NO_MESSAGE;
else how = a >= 0 && a <= (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, FROM_NODE((unsigned)a)) : V4_WIRE_NO_ONE;
break;
case 4: /* drop */
if (rx) how = v4_wire_drop(rx);
break;
case 5: /* put */
if (!tx) break;
cells = span_ok(n, a, V4_WIRE_HEADER) ? v4_wire_cells(n->mem[a + 6]) : 0;
if (cells == 0 || !span_ok(n, b, cells - V4_WIRE_HEADER)) how = V4_WIRE_NOT_A_MESSAGE;
else how = v4_wire_put(tx, &n->mem[a], cells > V4_WIRE_HEADER ? &n->mem[b] : &n->mem[0]);
break;
case 6: /* first */
if (rx) { v4_wire_first(rx); how = V4_WIRE_DONE; }
break;
case 7: /* next */
if (rx) how = v4_wire_next(rx);
break;
case 8: /* sleep */
if (!boot_arrived()) return 0;
how = V4_WIRE_DONE;
break;
case 9: /* sleep for room */
if (!tx) break;
if (a < 0 || a > (v4_cell)V4_WIRE_CELLS) { how = V4_WIRE_NO_ROOM; break; }
if (v4_wire_room(tx, (unsigned)a) == V4_WIRE_DONE) { how = V4_WIRE_DONE; break; }
if (!boot_arrived()) return 0;
how = V4_WIRE_NO_ROOM;
break;
case 10: /* room */
if (!tx) break;
how = a < 0 ? V4_WIRE_NOT_A_MESSAGE : a <= (v4_cell)V4_WIRE_CELLS ? v4_wire_room(tx, (unsigned)a) : V4_WIRE_NO_ROOM;
break;
default: /* not an operation */
how = V4_WIRE_NOT_A_MESSAGE;
break;
}
v4_node_done(n, how);
return 1;
}
/* The node is told which of its wires have a message and executes one
* instruction word; if it fetched which wires have one, it has seen what
* had come. The first half of a fabric's step (fabric.c). */
static void boot_exec(const v4_boot *b)
{
v4_node *n = b->n;
v4_node_have(n, (TO_NODE(KERNEL_PORT)->used ? 1u << KERNEL_PORT : 0u) | (TO_NODE(CONSOLE_PORT)->used ? 1u << CONSOLE_PORT : 0u));
(void)v4_exec_step_word(n, b->es, b->h);
if (n->have_fetched) {
TO_NODE(KERNEL_PORT)->arrived = 0;
TO_NODE(CONSOLE_PORT)->arrived = 0;
n->have_fetched = 0;
}
}
/* Time passes for the node, in the order a fabric's step has: that, and
* then what it asked of a wire is done. Returns 0 if it is asleep at a
* wire and sleeps on. */
static int boot_step(const v4_boot *b)
{
boot_exec(b);
return v4_node_doing(b->n) ? boot_do(b) : 1;
}
/* Hand the node a line and run it to its end. What it prints goes to the
* console; or, if `keep` is given, into `keep`, at most `cap` characters,
* and none of it is shown: *printed is then how many it printed in all.
* The console takes every message off the node's wire each time round, so
* what the node prints never waits for room for long. */
static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep, unsigned cap, unsigned *printed)
{
static v4_message out; /* one line at a time: the boot is not re-entered */
static v4_cell in[V4_WIRE_HEADER], in_text[V4_MSG_MAX_CHARS / 4u];
v4_node *n = b->n;
uint64_t steps = 0;
unsigned i;
if (text) { /* with none, the node has a line waiting on its wire (boot.h) */
if (!v4_message_text(&out, 0, CONSOLE_ID, V4_MSG_TEXT, text, len)) return V4_BOOT_LINE_TOO_LONG;
if (v4_wire_put(TO_NODE(CONSOLE_PORT), out.word, out.word + V4_MSG_HEADER) != V4_WIRE_DONE) return V4_BOOT_LINE_STOPPED;
}
for (;;) {
int awake = boot_step(b);
if (n->stopped) return V4_BOOT_LINE_STOPPED;
v4_wire_first(FROM_NODE(CONSOLE_PORT)); /* what the node has sent its console, in the order it was sent */
while (v4_wire_take(FROM_NODE(CONSOLE_PORT), in, in_text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) {
if (in[2] == V4_MSG_OUTPUT) {
for (i = 0; i < (unsigned)in[6]; i++) {
char c = (char)(((v4_ucell)in_text[i / 4u] >> (8u * (i % 4u))) & 0xFFu);
if (!keep) b->out(&c, 1);
else { if (*printed < cap) keep[*printed] = c; (*printed)++; }
}
} else if (in[2] == V4_MSG_DONE) {
/* The text has ended. The node goes on to where it next asks something of a wire, with
* nothing of its own on either stack: whoever called may then look at its stacks, or
* empty them. */
for (i = 0; i < 4096u && !v4_node_doing(n) && !n->stopped && !n->asking; i++) boot_exec(b);
return (int)in_text[0];
}
}
if (!awake) {
if (n->do_op == 9) continue; /* waiting for room, which the console has just made */
return V4_BOOT_LINE_WAITING; /* waiting for an answer: only the console can end that */
}
if (n->asking && n->ask_port == KERNEL_PORT) { /* a request: the kernel's turn */
if (v4_blocks_serve(&boot_blocks, n, n->request)) { /* a block, as for any node */
v4_node_port_served(n);
} 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 {
/* a request no one serves is an error, as any other (D-18):
* 12, Argument out of range */
v4_node_port_served(n);
v4_node_store(n, n->error_reg, 12); /* a store to NODE-ERROR raises it */
}
continue;
}
if (n->asking) { /* a word written to a port with no one on it: an error on the node */
if (!v4_node_port_gone(n, V4_ERROR_NO_ONE)) return V4_BOOT_LINE_STOPPED;
continue;
}
if (n->reading && !n->given) { /* a word read from a port: no one here writes one */
if (n->read_port != V4_PORT_ANY && v4_node_port_gone(n, V4_ERROR_NO_ONE)) continue;
return V4_BOOT_LINE_STOPPED; /* waiting for what will not come */
}
if (v4_image_waiting(n, b->im)) { /* the text is reading the keyboard */
int c = b->key ? b->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 >= 0) (void)v4_node_console_feed(n, &ch, 1u);
continue;
}
if (++steps > STEP_LIMIT) return V4_BOOT_LINE_STOPPED;
}
}
int v4_boot_line(const v4_boot *b, const char *text, unsigned len)
{
return run_line(b, text, len, 0, 0u, 0);
}
#if V4_POST_AT_BOOT
/* POST's host (post.h): a line is handed to the node as any line is, and
* what the node prints is kept for the runner and not shown. */
static int post_line(void *self, const char *text, unsigned text_len, char *out, unsigned cap, unsigned *len)
{
int how;
*len = 0;
how = run_line((const v4_boot *)self, text, text_len, out, cap, 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)
{
((const v4_boot *)self)->out(text, len);
}
#endif
/* ---- 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];
int how = v4_boot_line(b, text, len);
if (how != V4_TEXT_COMPLETED) {
say_where(b, name, block, line);
say(b, how == V4_TEXT_ERROR ? " ended in an error: "
: how == V4_TEXT_QUIT ? " ended with QUIT: "
: how == V4_BOOT_LINE_NO_INPUT ? " reads the keyboard: "
: " stopped the node: ");
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 nucleus ----------------------------------------------------------- */
/* The nucleus is a capsule of F18 code (v4/include/v4/capsule.h): the words
* themselves, to be written to the port of a node that has nothing in it.
* The boot is that node's neighbour. It finds the capsule, checks its hash
* and its signature as it does any capsule's, and gives the node a word
* each time it reads its port, until the node has taken them all and is
* where the nucleus waits to be handed a line. */
#define NUCLEUS_NAME_(bits) "v4:nucleus-" #bits ".f18"
#define NUCLEUS_NAME__(bits) NUCLEUS_NAME_(bits)
#define NUCLEUS_NAME NUCLEUS_NAME__(V4_CELL_BITS)
#define CELL_BYTES (V4_CELL_BITS / 8)
static int load_nucleus(const v4_boot *b)
{
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;
CapsuleValidateResult vr;
CapsuleSigResult sr;
v4_node *n = b->n;
uint64_t words, at = 0, steps = 0;
cap = capsule_find_by_name(dir, descs, names, NUCLEUS_NAME);
if (!cap) { say(b, "V4: capsule " NUCLEUS_NAME " 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 " NUCLEUS_NAME ": "); 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 " NUCLEUS_NAME " 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 || cap->length % CELL_BYTES != 0) { say(b, "V4: capsule " NUCLEUS_NAME " is not whole words\n"); return 0; }
words = cap->length / CELL_BYTES;
/* until it has taken every word and is where the nucleus waits for a
* message: asleep, no message having come */
for (;;) {
if (!boot_step(b)) {
if (at >= words && n->do_op == 8) break;
say(b, "V4: the node did not take the nucleus in\n");
return 0;
}
if (n->stopped || n->asking || ++steps > STEP_LIMIT) {
say(b, "V4: the node did not take the nucleus in\n");
return 0;
}
if (n->reading && !n->given) {
v4_ucell word = 0;
unsigned i;
if (at >= words) {
say(b, "V4: the node wants more than the nucleus holds\n");
return 0;
}
for (i = 0; i < CELL_BYTES; i++) word |= (v4_ucell)p[at * CELL_BYTES + i] << (8 * i);
v4_node_port_give(n, 0, (v4_cell)word);
at++;
}
}
say(b, "PARITY:V4_NUCLEUS name=" NUCLEUS_NAME);
say(b, " capsule_id="); say_hex(b, cap->capsule_id);
say(b, " capsule_hash="); say_hex(b, cap->content_hash);
say(b, " words="); say_dec(b, word_count(n, b->im));
say(b, "\n");
return 1;
}
/* ---- the whole boot ------------------------------------------------------ */
int v4_boot_run(const v4_boot *b)
{
unsigned i;
/* a node is born with nothing in it, and is sent the nucleus */
if (!v4_image_born(b->n, b->es, b->h, b->im)) {
say(b, "V4: the nucleus is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n");
return 0;
}
/* its two wires, the kernel's and the console's, with nothing on them */
v4_wire_reset(TO_NODE(KERNEL_PORT)); v4_wire_reset(FROM_NODE(KERNEL_PORT));
v4_wire_reset(TO_NODE(CONSOLE_PORT)); v4_wire_reset(FROM_NODE(CONSOLE_PORT));
v4_blocks_init(&boot_blocks, b->im->block_window);
if (!load_nucleus(b)) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
/* the kernel's words, before any capsule that might use one */
for (i = 0; i < b->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; b->words[i][k] && at < sizeof text; k++) text[at++] = b->words[i][k];
if (b->words[i][k] || v4_boot_line(b, text, at) != V4_TEXT_COMPLETED) {
say(b, "V4: kernel word "); say(b, b->words[i]); say(b, " could not be made\nPARITY: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; }
/* 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);
#if V4_POST_AT_BOOT
{
static const char cold[] = "COLD";
v4_post_host host;
v4_post_tally tally;
unsigned printed = 0;
int how;
host.self = (void *)b; host.n = b->n; host.line = post_line; host.say = post_say;
if (!v4_post_run(&host, v4_post_cases, v4_post_case_count, &tally)) {
say(b, "V4: POST reported failures\nPARITY:FAIL\nPOST: FAILED\n");
return 0;
}
/* POST leaves nothing: the node is put back to the system as it was
* sealed, as v3 puts the dictionary back after each of its suites
* (v3/src/test_runner/test_common.c, run_test_suite). What COLD
* prints is not shown. */
v4_dstack_reset(&b->n->ds);
how = run_line(b, cold, (unsigned)sizeof cold - 1u, (char *)cold, 0u, &printed);
if (how != V4_TEXT_COMPLETED && how != V4_TEXT_QUIT) {
say(b, "V4: the node did not come back to the system after POST\nPARITY:FAIL\nPOST: FAILED\n");
return 0;
}
}
#endif
say(b, "PARITY:V4_SYSTEM word_count="); say_dec(b, word_count(b->n, b->im));
say(b, " dict_hash="); say_hex(b, v4_boot_dict_hash(b->n, b->im));
say(b, "\n");
#if V4_POST_AT_BOOT
say(b, "PARITY:OK\nPOST: PASSED\n");
#else
say(b, "PARITY:OK\n");
#endif
return 1;
}