feat(v4.0.0): step 6e -- a node removed at any step leaves the rest at rest; written up

test_host_mesh.c, 220 checks: three exchanges on the row of three -- text
from the console for node 12 with what it prints and how it ended; node 11
SENDing node 12 text and waiting for its answer; a message node 12 has no
way for and its NACK to node 10 -- are each played once for every engine
step they take with node 11 removed at that step, and again with node 12:
17,642 removals at 64 bits.  Every node left comes to rest asleep, blocked
at no port and in no operation, and answers.  What is typed is a word
defined beforehand, so that the steps are the exchange's and not the
dictionary being searched.

Removed from the engine, nothing using them: v4_fabric_interrupt_error, a
device's pending hook, v4_node_words_since_look, V4_ERROR_INTERRUPTED.

MESH.md: 4.1's table has the addresses after the ports; 7a and 7b say what
of them still holds; 7b.7's limit is closed; step 6e is written up.
v4/README.md likewise.

make -C v4 test and sanitize at both widths, hosted-check, lint.  Three
bare-metal boots with the typed session and a loop printing 400 numbers:
logs/20261013-165124 (amd64), -165428 (aarch64), -165818 (riscv64).  POST
538 of 538, word_count=317, dict_hash=0x0951d7e02e251554 on all six, and
the three sessions are the same character for character.  The boots were
built with the working tree's uncommitted change to kernel/Makefile that
adds the ISA to the kernel's version line; nothing else differs from this
commit.

The plan's final review is not done yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-13 17:47:05 -04:00
co-authored by Claude Opus 5.5
parent ab96a9f2be
commit 06da5700bc
17 changed files with 893 additions and 142 deletions
+82 -2
View File
@@ -38,11 +38,11 @@ static v4_wire_queue queues[QUEUES];
/* ---- the console: a device on a port ---- */
#define CONSOLE_ID 1
static const v4_device console = { 0, 0, 0, 0 }; /* it takes and gives no words: only whole messages */
static const v4_device console = { 0, 0, 0 }; /* it takes and gives no words: only whole messages */
/* a kernel on a node's port 0: it takes the words written to it, and no messages (MESH.md 7d.5) */
static unsigned kernel_words;
static int kernel_take(void *self, v4_cell value) { (void)self; (void)value; kernel_words++; return 1; }
static const v4_device kernel_dev = { kernel_take, 0, 0, 0 };
static const v4_device kernel_dev = { kernel_take, 0, 0 };
static unsigned hera, mid, far; /* where the three nodes are: 10, 11, 12 */
static char printed[32768]; /* what has come back since the console last sent */
static unsigned printed_len;
@@ -246,6 +246,56 @@ static void watch_far_waits(void)
if (waits_for_room(far)) { saw_room_wait++; if (going_from(far, 3) && going_from(far, 3)->used == V4_WIRE_CELLS - 8u) saw_full++; }
}
/* ---- a node removed at every step of an exchange (MESH.md 7d.7, acceptance 6) ---- */
/* 1 if the node is doing nothing and waits for nothing but a message: asleep, and blocked at no port. */
static int at_rest(unsigned id)
{
const v4_fabric_node *x = v4_fabric_node_at(&f, id);
return x && asleep(id) && !x->n.asking && !x->n.reading && !x->n.stopped;
}
/* 1 if what came back ends with the answer to `1 2 + .`, asked by the word Q that each node was given; a wait
* the removal left is first ended by the line, and says so. */
#define ASK ": Q 1 2 + . ;"
static int answers(v4_cell node, unsigned id)
{
const char *got = tell(node, "Q");
size_t len = strlen(got);
return len >= 2 && strcmp(got + len - 2, "3 ") == 0 && ended >= 1 && ended_from == node && at_rest(id);
}
/* The exchange that `text`, sent to `node`, begins is played from the mesh as kept, once for every step it
* takes, with the node at place `gone` removed at that step. Every node left must come to rest, blocked in
* nothing, and answer. With the middle node gone the far one is wired to the first, as whoever removed it
* would, so that it can be asked. How many steps the exchange takes; how many removals went wrong, and
* the first of them. */
static v4_fabric_node now_pool[3];
static v4_place now_places[3];
static v4_wire_queue now_queues[QUEUES];
static v4_fabric now_f;
static unsigned removed_at_every_step(v4_cell node, const char *text, unsigned gone, unsigned *wrong, unsigned *first_wrong)
{
unsigned long before;
unsigned whole, s;
*wrong = 0; *first_wrong = 0;
put_back();
before = steps_run;
if (!put_text(node, text) || !run()) { *wrong = 1; return 0; }
whole = (unsigned)(steps_run - before);
put_back();
if (!put_text(node, text)) { *wrong = 1; return 0; }
for (s = 0; s <= whole; s++) {
int ok;
memcpy(now_pool, pool, sizeof pool); memcpy(now_places, places, sizeof places); memcpy(now_queues, queues, sizeof queues); now_f = f; /* the exchange, s steps on */
ok = v4_fabric_remove(&f, gone) == &pool[gone] && run();
ok = ok && at_rest(hera) && (gone == mid ? at_rest(far) : at_rest(mid));
if (ok && gone == mid) ok = v4_fabric_wire(&f, hera, 2, far, 2) && answers(12, far) && answers(10, hera);
else if (ok) ok = answers(11, mid) && answers(10, hera);
if (!ok) { if (!*wrong) *first_wrong = s; (*wrong)++; }
memcpy(pool, now_pool, sizeof pool); memcpy(places, now_places, sizeof places); memcpy(queues, now_queues, sizeof queues); f = now_f;
(void)v4_fabric_step(&f); (void)console_takes();
}
return whole;
}
int main(void)
{
unsigned i;
@@ -944,6 +994,36 @@ int main(void)
printf(" between texts the nucleus used at most %u cells of the data stack and %u entries of the return stack\n", between_data, between_ret);
CHECK(between_data <= 4 && between_ret <= 7, "between texts: four cells of the data stack, and seven return entries");
/* ---- A NODE REMOVED AT EVERY STEP (MESH.md 7d.7, acceptance 6) ----
* Three exchanges, each played once whole and then once for every step it takes, with node 11 removed
* at that step, and again with node 12: the nodes left come to rest, blocked in nothing, and answer.
* What is typed is a word defined beforehand, and so is what a node left is asked (Q, which is
* `1 2 + .`): a line of several words is mostly the dictionary searched, thousands of steps in which the
* mesh does nothing, and every one of them would be a removal played to the end. */
{
static const struct { const char *what, *set10, *set11, *set12; v4_cell node; const char *text; } ex[3] = {
{ "text from the console for node 12, what it prints and how it ended", "1 DROP", "1 DROP", ": A 7 8 * . ;", 12, "A" },
{ "node 11 SENDs node 12 text and waits for its answer", "1 DROP", ": T S\" Q\" 12 SEND 12 AWAIT . ;", "1 DROP", 11, "T" },
{ "a message node 12 has no way for, and its NACK to node 10", "55 2 ROUTE : N S\" 1 DROP\" 55 SEND ;", "55 3 ROUTE", "NO-ROUTES 1 2 ROUTE 10 2 ROUTE 11 2 ROUTE", 10, "N" }
};
unsigned e, g, whole, wrong, first;
step_cap = 2000000;
for (e = 0; e < 3; e++) {
CHECK(row() && told(10, ex[e].set10) && told(11, ex[e].set11) && told(12, ex[e].set12) && told(10, ASK) && told(11, ASK) && told(12, ASK), "a row made afresh for: %s", ex[e].what);
step_cap = 2000000;
keep();
for (g = 1; g <= 2; g++) {
whole = removed_at_every_step(ex[e].node, ex[e].text, g == 1 ? mid : far, &wrong, &first);
CHECK(whole > 0 && wrong == 0, "%s, in %u steps: with node %u removed at each of them the rest come to rest and answer: %u did not, the first at step %u",
ex[e].what, whole, 10u + g, wrong, first);
}
}
put_back();
CHECK(is_empty(tell(10, "N")) && pool[hera].n.mem[REFUSED] == 1 && pool[far].n.mem[LOST] == 1, "the last of them, whole: node 12 let the message go and node 10 was told it was refused");
step_cap = 20000000;
}
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}