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:
co-authored by
Claude Opus 5.5
parent
ab96a9f2be
commit
06da5700bc
+2
-62
@@ -51,10 +51,6 @@ static unsigned loaded(unsigned k)
|
||||
/* the programme ends by waiting at a port nothing is wired to */
|
||||
static void wait_for_ever(void) { LIT(PB + 7); O(BANG_B); O(FETCH_B); (void)v4_asm_label(&as); } /* and the last word is written out */
|
||||
|
||||
/* a device that says whether it has something pending */
|
||||
static int *dev_has;
|
||||
static int device_has(void *self) { (void)self; return *dev_has; }
|
||||
|
||||
/* Let time pass until nothing more can happen, or `max` steps. */
|
||||
static unsigned settle(unsigned max)
|
||||
{
|
||||
@@ -435,62 +431,6 @@ int main(void)
|
||||
CHECK(ND(a)->n.asking && ND(a)->n.request == 5 && ND(c)->n.reading && ND(c)->n.mem[OUT] == 0, "looking takes nothing and gives nothing: both wait as they did");
|
||||
CHECK(!ND(b)->n.stopped && ND(b)->n.reading, "and the one that looked did not wait to look");
|
||||
|
||||
/* ---- a node blocked on the first word of a message, while a device of its own has something for it ---- */
|
||||
{
|
||||
static int has; /* what the device says it has pending */
|
||||
static stream none;
|
||||
static v4_device dev;
|
||||
v4_fabric_init(&f, places, 4);
|
||||
for (i = 0; i < 3; i++) v4_node_reset(&pool[i].n);
|
||||
a = loaded(0); /* looks about, then writes two words to its port 0 */
|
||||
LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); O(DROP);
|
||||
LIT(PB + 0); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); wait_for_ever();
|
||||
b = loaded(1); /* never reads */
|
||||
LIT(OUT); O(BANG_A);
|
||||
{
|
||||
v4_cell again = v4_asm_label(&as);
|
||||
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
|
||||
v4_asm_branch(&as, V4_OP_JUMP, again);
|
||||
}
|
||||
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "a writer that looks first, wired to a node that never reads");
|
||||
dev.take = stream_take; dev.give = 0; dev.self = &none; dev.pending = device_has; has = 1; dev_has = &has;
|
||||
CHECK(v4_fabric_wire_device(&f, a, 5, &dev), "and to a device, which has something for it");
|
||||
for (i = 0; i < 40; i++) (void)v4_fabric_step(&f);
|
||||
CHECK(ND(a)->n.asking && ND(a)->n.request == 1 && v4_node_words_since_look(&ND(a)->n) == 0, "the writer is blocked on its first word, and waits: the fabric has no error for it");
|
||||
v4_fabric_interrupt_error(&f, 21);
|
||||
for (i = 0; i < 40; i++) (void)v4_fabric_step(&f);
|
||||
CHECK(ND(a)->n.asking && ND(a)->n.faults == 0, "nor is a bare node let go when it has: it cannot take an error");
|
||||
v4_node_error_attach(&ND(a)->n, OUT + 20);
|
||||
v4_node_fault_attach(&ND(a)->n, 900);
|
||||
has = 0;
|
||||
for (i = 0; i < 40; i++) (void)v4_fabric_step(&f);
|
||||
CHECK(ND(a)->n.asking && ND(a)->n.faults == 0, "a node that can take one waits all the same while the device has nothing for it");
|
||||
has = 1;
|
||||
(void)v4_fabric_step(&f);
|
||||
CHECK(!ND(a)->n.asking && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 21 && ND(a)->n.mem[OUT + 20] == 21,
|
||||
"when it has, the writer is let go and the error raised on it");
|
||||
|
||||
/* once the first word has gone, it is not let go: the message must be finished */
|
||||
v4_fabric_init(&f, places, 4);
|
||||
for (i = 0; i < 3; i++) v4_node_reset(&pool[i].n);
|
||||
a = loaded(0);
|
||||
LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); O(DROP);
|
||||
LIT(PB + 0); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); wait_for_ever();
|
||||
b = loaded(1); /* reads one word, and no more */
|
||||
LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A);
|
||||
{
|
||||
v4_cell again = v4_asm_label(&as);
|
||||
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
|
||||
v4_asm_branch(&as, V4_OP_JUMP, again);
|
||||
}
|
||||
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire_device(&f, a, 5, &dev), "the same, to a node that reads one word and no more");
|
||||
v4_node_error_attach(&ND(a)->n, OUT + 20);
|
||||
v4_node_fault_attach(&ND(a)->n, 900);
|
||||
v4_fabric_interrupt_error(&f, 21);
|
||||
for (i = 0; i < 60; i++) (void)v4_fabric_step(&f);
|
||||
CHECK(ND(a)->n.asking && ND(a)->n.request == 2 && ND(a)->n.faults == 0, "blocked on its second word with the device still pending, the writer waits");
|
||||
}
|
||||
|
||||
/* ---- messages on the wire (MESH.md 7d): the engine only ---- */
|
||||
{
|
||||
static v4_wire_queue qpool[8];
|
||||
@@ -800,7 +740,7 @@ int main(void)
|
||||
CHECK(!ND(c)->n.doing && !ND(a)->n.doing && ND(a)->n.how == 1, "room that comes in the step the node goes to sleep wakes it: no wake-up is lost");
|
||||
/* a device's take is a take */
|
||||
{
|
||||
static const v4_device dev = { 0, 0, 0, 0 };
|
||||
static const v4_device dev = { 0, 0, 0 };
|
||||
v4_cell hh[7], tt[V4_WIRE_CELLS / 4];
|
||||
CHECK(v4_fabric_wire_device(&f, a, 4, &dev), "a device on a third wire");
|
||||
fill(f.place[a].wire[4].tx, 0);
|
||||
@@ -855,7 +795,7 @@ int main(void)
|
||||
static stream ks;
|
||||
static v4_device kern;
|
||||
unsigned free_before = f.queue_free;
|
||||
ks.took_n = 0; kern.take = stream_take; kern.give = 0; kern.self = &ks; kern.pending = 0;
|
||||
ks.took_n = 0; kern.take = stream_take; kern.give = 0; kern.self = &ks;
|
||||
CHECK(v4_fabric_wire_kernel(&f, a, 5, &kern) && f.queue_free == free_before && !f.place[a].wire[5].rx && !f.place[a].wire[5].tx, "a kernel is wired to a port, and is given no queues");
|
||||
put_msg(&ND(a)->n, 100, 110, 4, 7);
|
||||
CHECK(ask(a, 5, 100, 110, 5) == 2 && ask(a, 5, 8, 0, 10) == 2 && ask(a, 5, 8, 0, 9) == 2 && ask(a, 5, 200, 220, 1) == 2 && ask(a, 5, 0, 0, 6) == 2 && !ND(a)->n.doing,
|
||||
|
||||
@@ -45,7 +45,7 @@ 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 */
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ static int host_born(void *self)
|
||||
v4_node_fault_attach(&pool[k].n, w_fault);
|
||||
v4_blocks_init(&node_blocks[k], BUF0_W);
|
||||
if (k != 0) { /* Hera's kernel is wired where she is made */
|
||||
node_kernel[k].take = node_kernel_take; node_kernel[k].give = 0; node_kernel[k].self = &pool[k]; node_kernel[k].pending = 0;
|
||||
node_kernel[k].take = node_kernel_take; node_kernel[k].give = 0; node_kernel[k].self = &pool[k];
|
||||
if (!v4_fabric_wire_kernel(&f, (unsigned)place, 0, &node_kernel[k])) return -1; /* it takes words, and no messages */
|
||||
}
|
||||
return place;
|
||||
@@ -196,7 +196,7 @@ static int kernel_take(void *self, v4_cell value) /* a request */
|
||||
if (value < FIRST_REQUEST || !v4_manage_serve(&manage, hera_place, (unsigned)(value - FIRST_REQUEST))) refused++;
|
||||
return 1;
|
||||
}
|
||||
static const v4_device kernel = { kernel_take, kernel_give, 0, 0 };
|
||||
static const v4_device kernel = { kernel_take, kernel_give, 0 };
|
||||
|
||||
/* ---- the console: a device on Hera's port 1 ----
|
||||
* It speaks whole messages: it puts them on the wire to Hera and takes them
|
||||
@@ -209,7 +209,7 @@ static int ended;
|
||||
static v4_cell ended_from, ended_how;
|
||||
static unsigned post_passed[32]; /* by node number: POST tallies with no failures seen from it */
|
||||
static unsigned post_other; /* tallies with failures */
|
||||
static const v4_device console = { 0, 0, 0, 0 };
|
||||
static const v4_device console = { 0, 0, 0 };
|
||||
|
||||
static unsigned console_takes(void)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user