fix(v4): the final review's four defects in the ROM boot

- The boot left the source's last new line in the console input, and the
  first KEY after the boot read it.  What the ROM has not read when it
  starts the system is taken back (boot.c, rom_harness.h).  hosted-check:
  KEY after the boot reads the key typed.
- A comment or macro left open at the end of a blob ran on into the next,
  and the ROM accepted source text.c refuses.  The loader ends each blob
  with FS (V4_ROM_BLOB_END) after its new line; the ROM ends a blob there
  as text.c ends a file: an open comment or macro is an error, and the word
  being assembled ends.  test_host_rom_asm: seven blob pairs against text.c.
- store to an address outside RAM faulted the ROM with no message.  It is
  now refused, as text.c refuses it: ERR-RANGE, "address out of range".
- The kernel build did not regenerate capsules/v4/sys/map-<w>.f18 before
  packing the capsule directory; it now does.

ROM: 2084 words, rom_hash 0x4c458c6e48b35456.  dict_hash unchanged,
0xe5a57f3eb72ff94a.  make -C v4 test test-32 hosted-check pass.  QEMU:
amd64, aarch64, riscv64 to ok>, POST 538/538, POST: PASSED.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-15 16:35:07 -04:00
co-authored by Claude Opus 5.5
parent a73580e949
commit ff75e69e91
14 changed files with 592 additions and 16 deletions
+2
View File
@@ -232,6 +232,8 @@ hosted-check: $(foreach i,$(HOSTED_ISAS),$(BINDIR)/boot-$(i).txt) $(BINDIR)/star
@grep -q '^ok> 0 12 ok$$' $(BINDIR)/cold-amd64.txt || { echo "hosted-check: COLD lost the capsule's words"; cat $(BINDIR)/cold-amd64.txt; exit 1; }
@grep -q '^ok> Protected word$$' $(BINDIR)/cold-amd64.txt || { echo "hosted-check: a capsule word could be forgotten"; exit 1; }
@grep -q '^ok> Goodbye!$$' $(BINDIR)/cold-amd64.txt || { echo "hosted-check: COLD lost BYE"; exit 1; }
@printf 'KEY .\nA\n' | $(BINDIR)/starforth4-amd64 > $(BINDIR)/key-amd64.txt
@grep -q '^ok> 65 ok$$' $(BINDIR)/key-amd64.txt || { echo "hosted-check: the first KEY after the boot did not read the key typed: the ROM's source left something in the console's input"; tail -3 $(BINDIR)/key-amd64.txt; exit 1; }
@printf 'RS1\nT{\nWORDS\n' | $(BINDIR)/starforth4-amd64 > $(BINDIR)/after-post-amd64.txt
@grep -q "^ok> UNKNOWN WORD: 'RS1'$$" $(BINDIR)/after-post-amd64.txt || { echo "hosted-check: a word a POST case defined is still there"; exit 1; }
@grep -q "^ok> UNKNOWN WORD: 'T{'$$" $(BINDIR)/after-post-amd64.txt || { echo "hosted-check: POST's harness is on the node"; exit 1; }
+6
View File
@@ -69,6 +69,12 @@
#endif
#define V4_ROM_BASE ((v4_cell)V4_NODE_WORDS)
/* What the ROM reads at the end of each blob of the source it is fed (ASCII
* FS, the file separator), after the blob's new line: the end of a file, as
* text.c ends one -- a comment or macro still open there is an error, and the
* word being assembled ends. The end of the whole source is EOT (4). */
#define V4_ROM_BLOB_END 28
/* 5% safety boundary at each end of node memory, per guard.h. Note the
* arithmetic: at 1024 words that is 52 words either side, 5.2% of memory
* spent on a boundary. That is the cost of the convention on a resource that
+11 -5
View File
@@ -454,7 +454,7 @@ static const char *const rom_errors[] = {
"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",
"too many unresolved branches", "not a value", "the source ended with no start", "start of something not a word", "address out of range",
};
#define ROM_ERRORS (sizeof rom_errors / sizeof rom_errors[0])
@@ -474,8 +474,11 @@ static uint64_t rom_hash(void)
/* 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
* as any capsule is, with a new line and V4_ROM_BLOB_END after each (where
* the ROM ends a blob as text.c ends a file), and then EOT, until the ROM
* has assembled the system and started it. What the ROM had not read of
* the source when it started the system is the source's, not the system's
* console input, and is taken back. 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. */
@@ -483,7 +486,7 @@ 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;
static const char blob_end[2] = { '\n', V4_ROM_BLOB_END }, eot = EOT;
v4_node *n = b->n;
uint64_t steps = 0;
uint32_t lines = 0, at = 0;
@@ -513,7 +516,8 @@ static int load_system(const v4_boot *b)
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; }
at += v4_node_console_feed(n, blob_end + (at - length[blob]), length[blob] + 2u - at);
if (at == length[blob] + 2u) { blob++; at = 0; }
} else if (v4_node_console_feed(n, &eot, 1)) {
fed_all = 1;
}
@@ -534,6 +538,8 @@ static int load_system(const v4_boot *b)
}
}
n->input_pos = n->input_len = 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);
+1
View File
@@ -222,6 +222,7 @@
#define ROM_ERR_VALUE 22 /* "not a value", "not a number" */
#define ROM_ERR_NO_START 23 /* the input ended with no start */
#define ROM_ERR_START 24 /* start: not a word */
#define ROM_ERR_RANGE 25 /* "address out of range": store outside RAM */
#define ROM_ADDR_TOP ((v4_cell)(V4_NODE_WORDS + V4_ROM_WORDS - 1u)) /* the top of the address space */
#define WORD_DEFINED (BUF0_W - 11) /* the xt of the word that is told of each new entry, or 0 */
#define WORD_FORGOTTEN (BUF0_W - 12) /* the xt of the word that is told when entries go, or 0 */
+4 -2
View File
@@ -106,7 +106,7 @@ static void rom_boot(v4_node *n)
/* THE SYSTEM, AS THE MACHINE BUILDS IT (MACHINE.md section 6, step 1). The
* system's source, capsules/v4/sys (V4_SYS_DIR): the map, the fourteen and
* the end, each followed by a new line, then EOT -- what v4/system/boot.c
* the end, each followed by a new line and V4_ROM_BLOB_END, then EOT -- what v4/system/boot.c
* feeds the console. rom_system_text gives it whole, *len its length;
* rom_build_system has `n`, booted with its ROM, assemble it, and runs it to
* its start: 1 if it started, with *words the instruction words it took. */
@@ -129,12 +129,13 @@ static char *rom_system_text(size_t *len)
f = fopen(path, "rb");
if (!f) { printf(" cannot read %s\n", path); free(text); return NULL; }
fseek(f, 0, SEEK_END); n = ftell(f); fseek(f, 0, SEEK_SET);
if (n < 0 || total + (size_t)n + 2 > cap || fread(text + total, 1, (size_t)n, f) != (size_t)n) {
if (n < 0 || total + (size_t)n + 3 > cap || fread(text + total, 1, (size_t)n, f) != (size_t)n) {
fclose(f); free(text); return NULL;
}
fclose(f);
total += (size_t)n;
text[total++] = '\n'; /* a blob that does not end with a new line still ends a token */
text[total++] = V4_ROM_BLOB_END; /* and the blob ends, as a file ends for text.c */
}
text[total++] = 4; /* EOT */
*len = total;
@@ -157,6 +158,7 @@ static int rom_build_system(v4_node *n, long *words)
if (w % 1000 == 0 && n->mem[ROM_FAILED] != 0) break;
}
free(text);
n->input_pos = n->input_len = 0; /* what was not read is the source's: boot.c takes it back */
if (words) *words = w;
return n->p < V4_ROM_BASE && n->mem[ROM_FAILED] == 0;
}
+61 -1
View File
@@ -38,7 +38,7 @@ static v4_cell code_of(const char *m)
{ "write the number", ROM_ERR_FETCH_P }, { "too many unresolved", ROM_ERR_REFS },
{ "not a value", ROM_ERR_VALUE }, { "not a number", ROM_ERR_VALUE },
{ "too many names", ROM_ERR_NAMES }, { "out of name space", ROM_ERR_NAMES },
{ "without", ROM_ERR_MISSING },
{ "address out of range", ROM_ERR_RANGE }, { "without", ROM_ERR_MISSING },
};
unsigned i;
for (i = 0; i < sizeof map / sizeof map[0]; i++) if (strstr(m, map[i].s)) return map[i].c;
@@ -117,6 +117,45 @@ static void same(const char *text)
}
/* text.c on each blob as a file of its own, as the host tests and text.c
* read the system; the ROM on the same blobs as the boot feeds them, each
* followed by a new line and V4_ROM_BLOB_END (Review Focus 1: a blob that
* ends mid-comment, mid-macro or mid-word). */
static void same_blobs(const char *const *blobs, unsigned count)
{
unsigned i;
int okA = 1, rb;
v4_cell w, hereA;
v4_node_reset(&a);
v4_text_begin(&tx, &a, ROM_ORIGIN);
for (i = 0; i < count && okA; i++) okA = v4_text_assemble(&tx, blobs[i]);
okA = okA && v4_text_assemble(&tx, ": ZZEND ;") && v4_text_finish(&tx);
in[0] = 0;
for (i = 0; i < count; i++) {
size_t l = strlen(in);
snprintf(in + l, sizeof in - l, "%s\n%c", blobs[i], (char)V4_ROM_BLOB_END);
}
strcat(in, ": ZZEND ;\nstart ZZEND\n\x04");
rb = rom_run(in);
if (okA) {
hereA = v4_text_here(&tx);
CHECK(rb == 1, "[%s | %s] text.c accepts; the ROM %s (code %ld)", blobs[0], blobs[count - 1],
rb == 0 ? "refused" : "did not finish", (long)b.mem[ROM_FAILED]);
if (rb != 1) return;
CHECK(b.p == v4_text_word(&tx, "ZZEND"), "[%s | %s] the ROM started at ZZEND", blobs[0], blobs[count - 1]);
for (w = ROM_ORIGIN; w < hereA; w++) if (a.mem[w] != b.mem[w]) break;
CHECK(w == hereA, "[%s | %s] word %ld differs", blobs[0], blobs[count - 1], (long)w);
} else {
const char *m = v4_text_error(&tx);
CHECK(rb == 0, "[%s | %s] text.c refuses (%s); the ROM %s", blobs[0], blobs[count - 1], m,
rb == 1 ? "accepted" : "did not finish");
if (rb != 0) return;
CHECK(b.mem[ROM_FAILED] == code_of(m), "[%s | %s] text.c: %s; ROM code %ld, want %ld", blobs[0], blobs[count - 1], m,
(long)b.mem[ROM_FAILED], (long)code_of(m));
}
}
int main(void)
{
unsigned i;
@@ -186,6 +225,27 @@ int main(void)
for (i = 0; i < 260u; i++) if (b.mem[TIB_W + (v4_cell)i] != 0) break;
CHECK(i == 260u, "the text input buffer is cleared: cell %u is not", i);
/* blobs: each ends as a file ends for text.c (Review Focus 1) */
{
static const char *const comment[] = { ": A ;\n( a comment left open", ": B 1 ; )" };
static const char *const macro[] = { "macro M 1", "endmacro : A M ;" };
static const char *const nfor[] = { ": A 3 FOR", "NEXT ;" };
static const char *const label[] = { ": A L1: 1", "jump L1 ;" };
static const char *const bsl[] = { ": A ;\n\\ a comment with no new line", ": B A ;" };
static const char *const word[] = { ": A 1", "2 ;" };
static const char *const closed[] = { "( closed ) : A ;", ": B A ;" };
same_blobs(comment, 2); same_blobs(macro, 2); same_blobs(nfor, 2); same_blobs(label, 2);
same_blobs(bsl, 2); same_blobs(word, 2); same_blobs(closed, 2);
}
/* store outside RAM: refused with an error, as text.c refuses it, not a fault */
{
static char t[80];
same("store -1 5");
snprintf(t, sizeof t, "store %ld 5", (long)V4_NODE_WORDS); same(t);
snprintf(t, sizeof t, "store %ld 5", (long)V4_NODE_WORDS - 1); same(t);
}
printf("test_host_rom_asm: %d checks, %d failures\n", checks, failures);
return failures != 0;
}
+1
View File
@@ -147,6 +147,7 @@ int main(int argc, char **argv)
constant("ERR-VALUE", (long)ROM_ERR_VALUE);
constant("ERR-NO-START", (long)ROM_ERR_NO_START);
constant("ERR-START", (long)ROM_ERR_START);
constant("ERR-RANGE", (long)ROM_ERR_RANGE);
constant("DSTACK-REG", (long)DSTACK_REG);
constant("RSTACK-REG", (long)RSTACK_REG);
/* the cell width's constants (NUMBER) needs, written as signed cells */