Files
LithosAnanake/v4/system/boot.c
T
rajamesandClaude Opus 5.5 a73580e949 feat(v4): the machine boots from its ROM; the prebuilt nucleus is gone
The host node starts in its ROM (entry 12), and the boot feeds it the
system's source through the console: capsules/v4/sys/map-64.f18, the
fourteen .f18 blobs and end.f18, each found, hash-checked and
signature-checked as any capsule, then EOT.  The ROM assembles the system
and starts it at (IDLE); the host reads the image record
(v4_image_from_node) for what only the assembled system knows.  A ROM
error is reported with its blob, line and meaning, then PARITY:FAIL.

Deleted: v4/tools/mkimage.c, capsules/v4/nucleus-64.f18, v4/capsule/*.v4,
v4/tools/v4tof18.py.  The .f18 files are the source now.

- text.c speaks the rest of the ROM's language (set, here, store, start;
  v4_text_value, v4_text_start), so the oracle reads the same source:
  test_host_rom_nucleus compares the ROM against text.c on the .f18 files,
  word for word, image record included.  Host tests load the .f18 files.
- mkmap image writes v4_map_image (the map's description of the system);
  hosted and the kernel link the ROM and the map instead of v4_image_64.c.
- Hera (Captain Bob's ruling, option 1): births stream the system the ROM
  assembled, as assembled code; hera.4th is unchanged.
  test_host_unit's host builds the system with the ROM.
- New parity lines: PARITY:V4_F18 per blob, PARITY:V4_ROM words=2049
  rom_hash=0xa6c792b5821010b7 instructions=60725216.

Acceptance: amd64, aarch64, riscv64 boot to ok> with PARITY:V4_POST
tests=538 pass=538 fail=0, POST: PASSED, and PARITY:V4_SYSTEM
word_count=317 dict_hash=0xe5a57f3eb72ff94a on all three -- the same hash
as the prebuilt nucleus gave.  make -C v4 test test-32 hosted-check: all
pass.  Logs: logs/20261015-141648, -141938, -142350.

Startup cost under QEMU TCG, the ROM's assembly (last V4_F18 line to
V4_ROM): amd64 26.7 s of 132 s to ok>; aarch64 41.0 s of 195 s; riscv64
20.5 s of 101 s.  Hosted native: +0.85 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:26:02 -04:00

637 lines
27 KiB
C

/* boot.c -- the machine's ROM assembles the system, 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 WIRE (docs/v4.0.0/MESH.md 7d.5). The lone node has no fabric: the
* boot keeps the two queues of its console's wire, one each way (wire.h),
* and does what the node asks of them, as a fabric does for a node of a
* mesh (fabric.c).
* THE KERNEL'S PORT HAS NO QUEUE (ruled 2026-10-08). The kernel is asked by
* words written to port 0 and takes no messages: a message for that port
* -- SEND-ON to it, or what a text prints when the way to its console has
* been set there -- is refused at once, as on any port with no one to take
* messages: the node's error 18, "No one on that port". */
static v4_wire_queue boot_wire[2]; /* the console's: [0] to the node, [1] from it */
#define TO_NODE (&boot_wire[0])
#define FROM_NODE (&boot_wire[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;
}
/* 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, and nothing
* has come off its wire either. */
static int boot_do(const v4_boot *bt)
{
v4_node *n = bt->n;
v4_wire_queue *rx = n->wire == CONSOLE_PORT ? TO_NODE : 0, *tx = n->wire == CONSOLE_PORT ? FROM_NODE : 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 (!rx->looked || v4_wire_look(rx, h) != V4_WIRE_DONE) { how = V4_WIRE_NO_MESSAGE; break; } /* only the message last looked at (wire.h) */
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 || !rx->looked) how = V4_WIRE_NO_MESSAGE;
else how = a == (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, FROM_NODE) : V4_WIRE_NO_ONE;
break;
case 4: /* drop */
if (rx) how = rx->looked ? v4_wire_drop(rx) : V4_WIRE_NO_MESSAGE;
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 (!TO_NODE->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 (!TO_NODE->arrived && !FROM_NODE->freed) return 0; /* a message has come, or the console has taken one */
FROM_NODE->freed = 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->used ? 1u << CONSOLE_PORT : 0u);
(void)v4_exec_step_word(n, b->es, b->h);
if (n->have_fetched) {
TO_NODE->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, out.word, out.word + V4_MSG_HEADER) != V4_WIRE_DONE) return V4_BOOT_LINE_REFUSED; /* no room: refused at once, as any message is */
}
for (;;) {
int awake = boot_step(b);
if (n->stopped) return V4_BOOT_LINE_STOPPED;
v4_wire_first(FROM_NODE); /* what the node has sent its console, in the order it was sent */
while (v4_wire_take(FROM_NODE, 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) { /* waiting for room, which the console has just made */
if (++steps > STEP_LIMIT) return V4_BOOT_LINE_STOPPED;
continue;
}
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 --------------------------------------------------------- */
/* The capsule `name`: found, its hash checked, its signature verified -- one
* that does not verify is refused, a missing one is only reported. Its
* payload, or NULL with what was wrong said. */
static const uint8_t *open_capsule(const v4_boot *b, const char *name, const CapsuleDesc **found)
{
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;
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; }
*found = cap;
return p;
}
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 CapsuleDesc *cap;
const uint8_t *p, *end;
uint32_t block = 0, line = 0;
int in_block = 0;
p = open_capsule(b, name, &cap);
if (!p) 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 system: the machine's ROM assembles it ----------------------------- */
/* The ROM (docs/v4.0.0/MACHINE.md section 2): the machine's one binary,
* built by v4/tools/mkrom.c for this cell width. The machine starts at its
* entry 12, (ROM) (capsules/v4/rom/main.f18), which reads the system's
* source from the console, assembles it into RAM and, at the source's
* `start`, runs it. */
#if V4_CELL_BITS == 64
extern const v4_cell v4_rom_64[];
extern const unsigned v4_rom_64_words;
#define BOOT_ROM v4_rom_64
#define BOOT_ROM_WORDS v4_rom_64_words
#else
extern const v4_cell v4_rom_32[];
extern const unsigned v4_rom_32_words;
#define BOOT_ROM v4_rom_32
#define BOOT_ROM_WORDS v4_rom_32_words
#endif
#define ROM_START 12u /* the entry where the machine starts */
#define ROM_STEP_LIMIT 400000000ULL /* instruction words the ROM may take: it takes about 16 million */
#define EOT 4 /* the end of the source: the ROM's (KEY) sees no more */
/* The system's source, in the order the ROM reads it: the memory map, then
* the system, then its end -- the state it is switched on in, and `start`.
* capsules/v4/sys; each blob is a capsule like any other. */
#define MAP_NAME_(bits) "v4:sys:map-" #bits ".f18"
#define MAP_NAME__(bits) MAP_NAME_(bits)
static const char *const system_blobs[] = {
MAP_NAME__(V4_CELL_BITS),
"v4:sys:core.f18", "v4:sys:input.f18", "v4:sys:dict.f18", "v4:sys:codegen.f18", "v4:sys:compile.f18",
"v4:sys:quit.f18", "v4:sys:forth.f18", "v4:sys:numout.f18", "v4:sys:words.f18", "v4:sys:system.f18",
"v4:sys:qmath.f18", "v4:sys:blocks.f18", "v4:sys:log.f18", "v4:sys:acl.f18",
"v4:sys:end.f18",
};
#define SYSTEM_BLOBS (sizeof system_blobs / sizeof system_blobs[0])
/* What the ROM's error codes mean (v4/tests/host_map.h, ROM_ERR_*): the ROM
* stops with the code, and the loader, which owns the console, says it. */
static const char *const rom_errors[] = {
"", "name too long", "comment not closed", "cannot be used as a name", "already defined",
"too many names", "does not fit: memory is full, or a branch cannot reach", "not an opcode the packer takes there",
"not allowed in a macro", "macro without endmacro, or endmacro without macro",
"label already defined in this word", "too many labels in one word",
"FOR nested too deep, NEXT or UNEXT without FOR, or FOR without NEXT",
"FOR ... UNEXT must fit one instruction word and hold no literal", "not something to branch to",
"never defined", "number does not fit a cell", "& needs a word that is already defined",
"write the number, not @p", "a name, a target, a value or a string is missing", "macros nested too deep",
"too many unresolved branches", "not a value", "the source ended with no start", "start of something not a word",
};
#define ROM_ERRORS (sizeof rom_errors / sizeof rom_errors[0])
/* FNV-1a over the ROM, a cell at a time, low byte first, as the dictionary
* hash is (image.c). */
static uint64_t rom_hash(void)
{
uint64_t h = 0xcbf29ce484222325ULL;
unsigned w, i;
for (w = 0; w < BOOT_ROM_WORDS; w++)
for (i = 0; i < V4_CELL_BITS / 8; i++) {
h ^= (uint64_t)(((v4_ucell)BOOT_ROM[w] >> (8 * i)) & 0xFFu);
h *= 0x00000100000001b3ULL;
}
return h;
}
/* The machine starts in its ROM, and the boot is its console: it feeds the
* system's source to the console a blob at a time, each found and checked
* as any capsule is, with a new line after each, and then EOT, until the
* ROM has assembled the system and started it. Then the node is where the
* system waits for a message, and its host reads from the image record
* what only the system knows (v4_image_from_node). If the ROM stops on an
* error, the blob and line are said, and what the error is. */
static int load_system(const v4_boot *b)
{
static const uint8_t *payload[SYSTEM_BLOBS];
static uint32_t length[SYSTEM_BLOBS], first_line[SYSTEM_BLOBS];
static const char newline = '\n', eot = EOT;
v4_node *n = b->n;
uint64_t steps = 0;
uint32_t lines = 0, at = 0;
unsigned i, blob = 0;
int fed_all = 0;
for (i = 0; i < SYSTEM_BLOBS; i++) {
const CapsuleDesc *cap;
uint32_t k;
payload[i] = open_capsule(b, system_blobs[i], &cap);
if (!payload[i]) return 0;
length[i] = (uint32_t)cap->length;
first_line[i] = lines;
for (k = 0; k < length[i]; k++) if (payload[i][k] == '\n') lines++;
lines++; /* the new line after it */
say(b, "PARITY:V4_F18 name="); say(b, system_blobs[i]);
say(b, " capsule_hash="); say_hex(b, cap->content_hash);
say(b, "\n");
}
v4_node_rom_attach(n, BOOT_ROM, BOOT_ROM_WORDS);
n->p = V4_ROM_BASE + (v4_cell)ROM_START;
while (n->p >= V4_ROM_BASE) {
/* the console is given more of the source whenever it has taken
* all it had */
if (!fed_all && n->input_pos == n->input_len) {
if (blob < SYSTEM_BLOBS && at < length[blob]) {
at += v4_node_console_feed(n, payload[blob] + at, length[blob] - at);
} else if (blob < SYSTEM_BLOBS) {
if (v4_node_console_feed(n, &newline, 1)) { blob++; at = 0; }
} else if (v4_node_console_feed(n, &eot, 1)) {
fed_all = 1;
}
}
(void)v4_exec_step_word(n, b->es, b->h);
if (n->stopped || ++steps > ROM_STEP_LIMIT) {
say(b, "V4: the machine's ROM did not assemble the system\n");
return 0;
}
if ((steps & 1023u) == 0 && n->mem[b->im->rom_failed] != 0) {
v4_cell code = n->mem[b->im->rom_failed];
uint32_t line = (uint32_t)n->mem[b->im->rom_errline];
for (i = SYSTEM_BLOBS; i-- > 1 && first_line[i] > line; ) { }
say(b, "V4: "); say(b, system_blobs[i]); say(b, " line "); say_dec(b, line - first_line[i] + 1u); say(b, ": ");
say(b, code > 0 && (unsigned)code < ROM_ERRORS ? rom_errors[code] : "an error the ROM has no words for");
say(b, "\n");
return 0;
}
}
say(b, "PARITY:V4_ROM words="); say_dec(b, BOOT_ROM_WORDS);
say(b, " rom_hash="); say_hex(b, rom_hash());
say(b, " instructions="); say_dec(b, (uint32_t)b->es->anticlock);
say(b, "\n");
if (!v4_image_from_node(n, b->im) || n->p != b->im->idle) {
say(b, "V4: the system the ROM assembled has no image record, or did not start where it says it waits\n");
return 0;
}
v4_node_fault_attach(n, b->im->fault_table);
/* until it is where the system waits for a message: asleep, no message
* having come */
for (;;) {
if (!boot_step(b)) {
if (n->do_op == 8) break;
say(b, "V4: the system did not come to where it waits\n");
return 0;
}
if (n->stopped || n->asking || ++steps > STEP_LIMIT) {
say(b, "V4: the system did not come to where it waits\n");
return 0;
}
}
return 1;
}
/* ---- the whole boot ------------------------------------------------------ */
int v4_boot_run(const v4_boot *b)
{
unsigned i;
/* a node is born with nothing in it, and its ROM assembles the system */
if (!v4_image_born(b->n, b->es, b->h, b->im)) {
say(b, "V4: the memory map is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n");
return 0;
}
/* its console's wire, with nothing on it */
v4_wire_reset(TO_NODE); v4_wire_reset(FROM_NODE);
v4_blocks_init(&boot_blocks, b->im->block_window);
if (!load_system(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;
}