feat(v4.0.0): the node tells its kernel of its words; the boot seals the system
ENGINE.md 3b, the node's side of ruling A (a word's code is the node's, its
accounts the kernel's).
- dict.v4, system.v4: (WORD-DEFINED) ( xt -- ) is run when an entry is
made, (WORD-FORGOTTEN) ( w -- ) when FORGET or COLD removes entries; with
0 there no one is told, as on the hosted product
- test_host_quit.c: a kernel that keeps the list of words and is checked to
hold exactly the node's dictionary after definitions, a vocabulary, an
abandoned definition, FORGET, a refused FORGET and COLD; KERNEL-WORD
called from the prompt and from a definition
Fixed, found while writing that test: since the capsules moved from build
time to boot time (294e6946), what COLD returns to and FORGET protects was
still the nucleus alone, so COLD lost U*, U/MOD and BYE and FORGET U* was
allowed. The boot now seals the system when it has loaded it
(v4_image_seal), and hosted-check checks COLD, the capsule word after it,
the refused FORGET and BYE.
Verified: make -C v4 test passes at both widths (1283 checks in
test_host_quit.c); hosted-check passes on three ISAs; clean qemu with
STARFORTH_V4=1 on amd64, aarch64 and riscv64 passes POST, and COLD, U*
after it, FORGET U* (refused), an unserved kernel word and BYE typed at
each prompt are answered correctly (logs/20261005-193045, -193307, -193636).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e8e8ea13ea
commit
25fc5fd5e3
@@ -83,6 +83,72 @@ static char typed[8192]; /* typed and not yet taken */
|
||||
static unsigned typed_len;
|
||||
static int line_open; /* a line has been handed over and has not ended */
|
||||
|
||||
/* THE KERNEL. A node asks its kernel by writing to its port (node.h), and
|
||||
* these tests are that too. Request 1 is "an entry has been made" ( xt -- )
|
||||
* and request 2 "the entries from here up have gone" ( w -- ): the kernel's
|
||||
* record of the words on the node (ENGINE.md 3b), kept here as the list of
|
||||
* their xts. Any other request is noted and nothing is done. */
|
||||
static v4_cell known[1024];
|
||||
static unsigned known_n;
|
||||
static v4_cell last_request;
|
||||
|
||||
static void kernel_serve(void)
|
||||
{
|
||||
last_request = n.request;
|
||||
if (n.request == 1) {
|
||||
v4_cell xt = v4_dstack_pop(&n.ds);
|
||||
if (known_n < 1024) known[known_n++] = xt;
|
||||
} else if (n.request == 2) {
|
||||
v4_cell w = v4_dstack_pop(&n.ds);
|
||||
unsigned i, kept = 0;
|
||||
for (i = 0; i < known_n; i++) if (known[i] < w) known[kept++] = known[i];
|
||||
known_n = kept;
|
||||
}
|
||||
}
|
||||
|
||||
/* Every entry on the node above the capsule's own, in every vocabulary:
|
||||
* FORTH's list from LATEST, and each vocabulary's from its head. */
|
||||
static unsigned node_words(v4_cell *out, unsigned max)
|
||||
{
|
||||
unsigned count = 0;
|
||||
v4_cell xt, voc;
|
||||
for (xt = n.mem[LATEST]; xt >= DICT_W; xt = n.mem[xt - 1]) if (count < max) out[count++] = xt;
|
||||
for (voc = n.mem[VOC_LINK]; voc != 0; voc = n.mem[voc + 1])
|
||||
for (xt = n.mem[voc]; xt >= DICT_W; xt = n.mem[xt - 1]) if (count < max) out[count++] = xt;
|
||||
return count;
|
||||
}
|
||||
|
||||
/* True if the kernel's record is exactly the words that are on the node. */
|
||||
static int kernel_agrees(void)
|
||||
{
|
||||
static v4_cell there[1024];
|
||||
unsigned count = node_words(there, 1024), i, k;
|
||||
if (count != known_n) return 0;
|
||||
for (i = 0; i < count; i++) {
|
||||
for (k = 0; k < known_n && known[k] != there[i]; k++) { }
|
||||
if (k == known_n) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The name of the entry whose code is at xt, as the kernel reads it: the
|
||||
* word before the link holds the word address of the counted name, its
|
||||
* bytes four to a cell. */
|
||||
static const char *node_name(v4_cell xt)
|
||||
{
|
||||
static char name[40];
|
||||
v4_cell at = n.mem[xt - 2] * 4;
|
||||
unsigned len, i;
|
||||
len = (unsigned)(((v4_ucell)n.mem[at / 4] >> (8 * (at % 4))) & 0xFFu);
|
||||
if (len > 31) len = 31;
|
||||
for (i = 0; i < len; i++) {
|
||||
v4_cell b = at + 1 + (v4_cell)i;
|
||||
name[i] = (char)(((v4_ucell)n.mem[b / 4] >> (8 * (b % 4))) & 0xFFu);
|
||||
}
|
||||
name[len] = 0;
|
||||
return name;
|
||||
}
|
||||
|
||||
/* Run until the line ends, or until it has been inside KEY with nothing to
|
||||
* read for 64 instruction words; or until `max` words. 1: ended, 2: waiting
|
||||
* for a character, 0: neither. */
|
||||
@@ -94,6 +160,7 @@ static int run_line(long max)
|
||||
if (v4_line_done(&n, &img)) { last_steps = steps; return 1; }
|
||||
(void)v4_exec_step_word(&n, &es, &h);
|
||||
steps++;
|
||||
if (n.asking) { kernel_serve(); v4_node_port_served(&n); } /* blocked at its port: the kernel's turn */
|
||||
if (n.input_pos == n.input_len && n.p >= w_key && n.p < w_key_end) idle++; else idle = 0;
|
||||
}
|
||||
last_steps = steps;
|
||||
@@ -136,6 +203,11 @@ static void boot_with(unsigned depth)
|
||||
v4_heat_reset(&h);
|
||||
for (i = 0; i < depth; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i));
|
||||
v4_dstack_push(&n.ds, CANARY);
|
||||
v4_node_port_attach(&n, PORT);
|
||||
n.mem[WORD_DEFINED] = 0; /* no one is told of entries, until a test says so */
|
||||
n.mem[WORD_FORGOTTEN] = 0;
|
||||
known_n = 0;
|
||||
last_request = 0;
|
||||
n.mem[LINE_STATUS] = V4_LINE_COMPLETED;
|
||||
n.p = img.idle; /* idle, waiting to be handed a line */
|
||||
typed_len = 0;
|
||||
@@ -1354,6 +1426,40 @@ int main(void)
|
||||
|
||||
CHECK(v4_node_guards_intact(&n), "guards intact");
|
||||
|
||||
/* ---- the kernel is told of every entry made and of every entry that goes (ENGINE.md 3b) ---- */
|
||||
boot();
|
||||
CHECK(is(say("5 KERNEL-WORD ASK5\n"), " ok\nok> "), "KERNEL-WORD makes a word");
|
||||
CHECK(is(say("ASK5\n"), " ok\nok> ") && last_request == 5, "whose body writes its number to the port: %ld", (long)last_request);
|
||||
CHECK(is(say(": TWICE ASK5 ASK5 ; TWICE\n"), " ok\nok> "), "and which a definition can call, again and again");
|
||||
boot();
|
||||
CHECK(is(say(": Q1 ;\n"), " ok\nok> ") && known_n == 0, "with nothing in (WORD-DEFINED) no one is told of a new entry");
|
||||
CHECK(is(say("1 KERNEL-WORD KD 2 KERNEL-WORD KF\n"), " ok\nok> "), "the kernel's two words");
|
||||
CHECK(is(say("' KD (WORD-DEFINED) ! ' KF (WORD-FORGOTTEN) !\n"), " ok\nok> "), "are put where the node will run them");
|
||||
known_n = node_words(known, 1024); /* what was there before the kernel was listening */
|
||||
n.mem[BOOT_CELLS] = n.mem[DP]; /* and all of it is the system, which COLD keeps */
|
||||
n.mem[BOOT_CELLS + 1] = n.mem[LATEST];
|
||||
n.mem[FENCE] = (n.mem[DP] + 3) / 4;
|
||||
{
|
||||
unsigned before = known_n;
|
||||
CHECK(kernel_agrees() && before == 3, "the kernel starts knowing the words that are there: %u", before);
|
||||
CHECK(is(say(": A1 ;\n"), " ok\nok> ") && known_n == before + 1 && kernel_agrees(), "a colon definition is told of");
|
||||
CHECK(strcmp(node_name(known[known_n - 1]), "A1") == 0, "by its xt, from which its name is read: %s", node_name(known[known_n - 1]));
|
||||
CHECK(is(say("VARIABLE V1 5 CONSTANT C1 CREATE D1\n"), " ok\nok> ") && known_n == before + 4 && kernel_agrees(),
|
||||
"and a variable, a constant and a CREATEd word");
|
||||
CHECK(is(say("VOCABULARY VOC1 VOC1 DEFINITIONS : IN1 ; FORTH DEFINITIONS : A2 ;\n"), " ok\nok> ")
|
||||
&& known_n == before + 7 && kernel_agrees(), "a vocabulary, a word in it, and a word after it");
|
||||
CHECK(strstr(say(": BAD NOSUCH ;\n"), "ERROR") != NULL && known_n == before + 8 && kernel_agrees(),
|
||||
"a definition that is abandoned is still an entry, and is told of");
|
||||
CHECK(is(say("FORGET C1\n"), " ok\nok> ") && known_n == before + 2 && kernel_agrees(),
|
||||
"FORGET tells that every entry from that one up has gone: %u left of ours", known_n - before);
|
||||
CHECK(is(say(": A3 ;\n"), " ok\nok> ") && known_n == before + 3 && kernel_agrees(), "and what is defined next is told of as before");
|
||||
CHECK(strstr(say("FORGET KD\n"), "Protected word") != NULL && known_n == before + 3 && kernel_agrees(),
|
||||
"a FORGET that is refused tells nothing");
|
||||
(void)say("COLD\n");
|
||||
CHECK(known_n == before && kernel_agrees(), "COLD tells that everything above the system has gone: %u", known_n);
|
||||
CHECK(is(say(": A4 ; A4\n"), " ok\nok> ") && known_n == before + 1 && kernel_agrees(), "and the node goes on telling");
|
||||
}
|
||||
|
||||
printf(" %d checks, %d failures\n", checks, failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user