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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1bf19d6224
commit
a73580e949
+167
-72
@@ -1,4 +1,5 @@
|
||||
/* boot.c -- the nucleus, then its capsules, then the prompt. See boot.h.
|
||||
/* 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,
|
||||
@@ -311,25 +312,19 @@ static void post_say(void *self, const char *text, unsigned len)
|
||||
|
||||
/* ---- 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)
|
||||
/* 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, *end;
|
||||
const uint8_t *p;
|
||||
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; }
|
||||
@@ -348,6 +343,27 @@ static int load_capsule(const v4_boot *b, const char *name)
|
||||
|
||||
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) {
|
||||
@@ -391,78 +407,157 @@ static int load_capsule(const v4_boot *b, const char *name)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- the nucleus ----------------------------------------------------------- */
|
||||
/* ---- the system: the machine's ROM assembles it ----------------------------- */
|
||||
|
||||
/* 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)
|
||||
/* 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 */
|
||||
|
||||
static int load_nucleus(const v4_boot *b)
|
||||
/* 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)
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
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");
|
||||
/* 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;
|
||||
}
|
||||
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;
|
||||
v4_node_fault_attach(n, b->im->fault_table);
|
||||
|
||||
/* until it has taken every word and is where the nucleus waits for a
|
||||
* message: asleep, no message having come */
|
||||
/* until it is where the system 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");
|
||||
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 node did not take the nucleus in\n");
|
||||
say(b, "V4: the system did not come to where it waits\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;
|
||||
}
|
||||
|
||||
@@ -472,15 +567,15 @@ int v4_boot_run(const v4_boot *b)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
/* a node is born with nothing in it, and is sent the nucleus */
|
||||
/* 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 nucleus is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n");
|
||||
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_nucleus(b)) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
|
||||
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++) {
|
||||
|
||||
Reference in New Issue
Block a user