Files
LithosAnanake/v4/tests/test_host_depth.c
T
rajamesandClaude Opus 5.5 b1d09af043 feat(v4.0.0): messages are whole and wait on the wire -- a node keeps none but the one it is doing
Step 6e, tasks 2 and 3 (docs/v4.0.0/MESH.md 7d).  The nucleus puts, looks
at, takes, moves and drops whole messages through the six addresses after
its ports; its own ring of messages waiting, the refusals owed, (GATE) and
the looking words' use are gone.  SEND, an answer, a NACK and a GONE go or
are refused at once; what a text prints waits for room; a text is not
begun until the wire its answer goes by has room for it, and that room is
kept.  The lone node's host (v4/system/boot.c) keeps its two wires and
does its operations; the test hosts' consoles put and take whole messages
through the fabric.  New: tests/test_host_depth.c, every message path of
a mesh node at every depth of both stacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:59:37 -04:00

342 lines
18 KiB
C

/* test_host_depth.c -- every message path of a node on a mesh, at every
* depth of both stacks. docs/v4.0.0/MESH.md 7d.7, acceptance 7; 7c.6.
*
* console --1 [10] 2--2 [11] 3--2 [12]
*
* A node's message code runs on the stacks its text is using. The first
* attempt at step 6d was tested on a shallow stack with every node there,
* and hung on a deep one. Here node 11 does each thing a node does with a
* message -- print, SEND, SEND with no way, AWAIT answered, AWAIT ended by a
* GONE, pass a message on while its own text waits for room -- with its data
* stack holding 20 to 32 values, and from 20 to 32 calls deep. After each,
* every node must answer, and node 11's stack must be as deep as the text
* made it, or its text must have ended in an error it reported. It may
* refuse; it may not hang, and it may not swallow a line.
*/
#include "v4/image.h"
#include "v4/fabric.h"
#include "v4/message.h"
#include "v4/text.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "host_map.h"
static int failures = 0, checks = 0;
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
static v4_text tx;
static v4_node nucleus; /* one node with the nucleus in it: what every node here starts as */
static v4_cell w_idle, w_fault;
static v4_fabric_node pool[3];
static v4_place places[3];
static v4_fabric f;
#define QUEUES 12u /* two for each wire there may be: the console's, 10-11, 11-12, 10-12, and two wires to spare */
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 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;
static v4_cell printed_from; /* which node the last of it came from */
static int ended; /* how many messages of how text ended have come, */
static v4_cell ended_from, ended_how; /* and the last of them */
static unsigned ended_at; /* and how much had been printed when it came */
static int nacks; /* how many NACKs have come to the console, */
static v4_cell nack_from, nack_about; /* and the last of them: who refused, and the node the refused message was for */
/* Everything the node at the console has put on the console's wire is taken, in order. How many messages. */
static unsigned console_takes(void)
{
static v4_cell hd[V4_WIRE_HEADER], text[V4_MSG_MAX_CHARS / 4u];
unsigned took = 0, i;
while (v4_fabric_device_take(&f, hera, 1, hd, text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) {
took++;
if (hd[2] == V4_MSG_OUTPUT) {
for (i = 0; i < (unsigned)hd[6] && printed_len + 1 < sizeof printed; i++) printed[printed_len++] = (char)(((v4_ucell)text[i / 4u] >> (8u * (i % 4u))) & 0xFFu);
printed[printed_len] = 0;
printed_from = hd[1];
} else if (hd[2] == V4_MSG_DONE) {
ended++; ended_from = hd[1]; ended_how = text[0]; ended_at = printed_len;
} else if (hd[2] == V4_MSG_NACK) {
nacks++; nack_from = hd[1]; nack_about = text[0];
}
}
return took;
}
static int is_empty(const char *s) { return s[0] == 0; }
/* 1 if the node is asleep (operation 8): it executes nothing until a message comes. */
static int asleep(unsigned id)
{
const v4_fabric_node *x = v4_fabric_node_at(&f, id);
return x && x->n.doing && x->n.do_op == 8;
}
static unsigned step_cap = 20000000; /* how long a line may take before it is given up on */
static unsigned stuck; /* bit k: the node at place k is known to be going round for ever, and is not waited for */
static void (*watch)(void); /* called after every step, if set */
static unsigned long steps_run;
static void show_stuck(const char *text)
{
unsigned k, q;
if (!getenv("V4_MESH_STUCK")) return;
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) { const v4_node *n = &pool[k].n;
printf(" stuck after \"%.40s\": node %ld p=%ld doing=%d op=%ld how=%ld wire=%u asking=%d d=%u r=%u await=%ld keep=%ld lost=%ld refused=%ld fault=%u@%ld err=%ld\n", text, (long)n->mem[ME], (long)n->p, n->doing, (long)n->do_op, (long)n->how, n->wire,
n->asking, (unsigned)n->ds.depth, (unsigned)n->rs.depth, (long)n->mem[AWAIT_FROM], (long)n->mem[KEEP], (long)n->mem[LOST], (long)n->mem[REFUSED], n->fault_kind, (long)n->fault_addr, (long)n->mem[NODE_ERROR]);
for (q = 0; q < V4_PORTS; q++) if (places[k].wire[q].rx) printf(" port %u: %u cells coming, %u going\n", q, places[k].wire[q].rx->used, places[k].wire[q].tx->used); }
}
/* Let everything that follows from what has been put happen: until nothing can, or -- with a node known to be
* stuck -- until every other node has been asleep, and the console has been sent nothing, for a while. */
static int run(void)
{
unsigned steps = 0, quiet = 0, k;
while (steps < step_cap) {
unsigned did = v4_fabric_step(&f), took = console_takes(), awake = 0, running = 0;
steps++; steps_run++;
if (watch) watch();
/* a step in which a node only faulted counts nothing done (exec.h): it goes on from its handler at the next */
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k) && !pool[k].n.doing && !pool[k].n.asking && !pool[k].n.stopped && !(pool[k].n.reading && !pool[k].n.given)) running = 1;
if (did == 0 && took == 0 && !running) return 1;
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k) && !(stuck & 1u << k) && !(pool[k].n.doing && (pool[k].n.do_op == 8 || pool[k].n.do_op == 9))) awake = 1;
quiet = (stuck && !awake && took == 0) ? quiet + 1 : 0;
if (quiet >= 400000) return 1;
}
return 0;
}
/* The console sends text to a node: the message is put on the wire to the node the console is on. */
static int put_text(v4_cell node, const char *text)
{
static v4_message m;
if (!v4_message_text(&m, node, CONSOLE_ID, V4_MSG_TEXT, text, (unsigned)strlen(text))) return 0;
return v4_fabric_device_put(&f, hera, 1, m.word, m.word + V4_MSG_HEADER) == V4_WIRE_DONE;
}
static void forget(void)
{
printed_len = 0; printed[0] = 0; printed_from = -1;
ended = 0; ended_from = -1; ended_how = -1; ended_at = 0;
}
/* Send text to a node and let everything that follows from it happen. */
static const char *tell(v4_cell node, const char *text)
{
forget();
if (!put_text(node, text)) return "(not put)";
if (!run()) { show_stuck(text); return "(still running)"; }
return printed;
}
/* A StarForth node in the fabric: born, then given the nucleus and the
* registers the nucleus expects, waiting for a message. */
static unsigned starforth_node(unsigned k)
{
int id = v4_fabric_add(&f, &pool[k], PORT);
v4_node *n = &pool[k].n;
memcpy(n->mem, nucleus.mem, sizeof n->mem);
v4_node_stack_regs_attach(n, DSTACK_REG, RSTACK_REG);
v4_node_error_attach(n, NODE_ERROR);
v4_node_fault_attach(n, w_fault);
n->p = w_idle;
return (unsigned)id;
}
/* Three nodes that know nothing, in a row, the console on the first. */
static int fresh(void)
{
unsigned i;
int ok;
v4_fabric_init(&f, places, 3);
v4_fabric_queues(&f, queues, QUEUES);
v4_fabric_gone_error(&f, V4_ERROR_NO_ONE);
hera = starforth_node(0);
mid = starforth_node(1);
far = starforth_node(2);
ok = v4_fabric_wire_device(&f, hera, 1, &console) && v4_fabric_wire(&f, hera, 2, mid, 2) && v4_fabric_wire(&f, mid, 3, far, 2);
stuck = 0; watch = 0; nacks = 0; step_cap = 20000000;
forget();
for (i = 0; i < 200; i++) (void)v4_fabric_step(&f);
return ok && asleep(hera) && asleep(mid) && asleep(far);
}
static int told(v4_cell node, const char *text) { return is_empty(tell(node, text)) && ended == 1 && ended_how == V4_TEXT_COMPLETED; }
/* The same, told who they are, the way to each other, and who is on each port:
* console --1 [10] 2--2 [11] 3--2 [12] */
static int row(void)
{
return fresh()
&& told(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE 0 2 ROUTE")
&& told(0, "11 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE 0 3 ROUTE")
&& told(0, "12 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE")
&& told(10, "0 NO-ROUTE 11 2 ROUTE 12 2 ROUTE 11 2 NEIGHBOUR") && told(11, "0 NO-ROUTE 12 3 ROUTE 10 2 NEIGHBOUR 12 3 NEIGHBOUR") && told(12, "11 2 NEIGHBOUR");
}
/* ---- the sweep ---- */
static unsigned runs, bad;
/* what the last path watches for: node 11 asleep for room, and, while it is, a message it has passed on to
* the wire to node 12 */
static unsigned saw_room_wait, saw_passed;
static void watch_mid(void)
{
if (pool[mid].n.doing && pool[mid].n.do_op == 9) { saw_room_wait++; if (places[mid].wire[3].tx && places[mid].wire[3].tx->used > 0) saw_passed++; }
}
/* words that push 1, 2, 4, 8 and 16 values and call nothing while they do */
static void fill(char *text, size_t cap, unsigned count)
{
size_t at = 0;
unsigned bit;
text[0] = 0;
for (; count >= 16; count -= 16) at += (size_t)snprintf(text + at, cap - at, "P16 ");
for (bit = 8; bit; bit >>= 1) if (count >= bit) { at += (size_t)snprintf(text + at, cap - at, "P%u ", bit); count -= bit; }
}
/* A row made afresh for one path; node 11 is given the word ACT that takes it, and the words that fill the
* stack and that call each other. */
static int made(unsigned path)
{
static const char *const act[] = {
": ACT 600 0 DO 65 EMIT LOOP ;",
": ACT S\" 1 DROP\" 12 SEND ;",
": ACT S\" 1 DROP\" 99 SEND ;",
": ACT S\" 1 DROP\" 12 SEND 12 AWAIT DROP ;",
": ACT 12 AWAIT DROP ;",
": ACT 1200 0 DO I . LOOP ;",
};
char line[64];
unsigned k;
if (!row() || !told(11, act[path]) || !told(11, ": P1 7 ; : P2 7 7 ; : P4 7 7 7 7 ; : P8 7 7 7 7 7 7 7 7 ;") || !told(11, ": P16 P8 P8 ;")) return 0;
if (!told(11, ": N1 ACT ;")) return 0;
for (k = 2; k <= V4_RET_DEPTH + 1u; k++) {
snprintf(line, sizeof line, ": N%u N%u ;", k, k - 1u);
if (!told(11, line)) return 0;
}
if (path == 2 && !told(11, "NO-ROUTES 1 2 ROUTE 10 2 ROUTE 12 3 ROUTE")) return 0; /* no way for any node it has not been told of */
if (path == 4 && !told(12, ": LL 400000 0 DO LOOP ;")) return 0;
if (path == 5 && !told(10, ": SLOW 500000 0 DO LOOP ; : BUSY SLOW S\" 1 DROP\" 12 SEND SLOW ;")) return 0;
return 1;
}
/* Node 11 takes the path with `text`; `values` is how many values the text leaves on its stack if it completes. */
static int one(unsigned path, const char *what, unsigned depth, const char *text, unsigned values)
{
static const char *const name[] = { "a text that prints", "SEND", "SEND with no way", "AWAIT answered", "AWAIT ended by a GONE", "passing on while its text waits for room" };
const char *why = 0;
int how11;
unsigned left;
runs++;
forget();
step_cap = 60000000;
if (path == 4) { /* node 12 is busy for a long while and sends no answer; node 11 waits; its centre says 12 is gone */
stuck = 1u << far;
if (!put_text(12, "LL") || !put_text(11, text) || !run()) why = "it did not come to wait";
else if (!put_text(10, "12 11 GONE") || !run()) why = "the GONE was not done";
stuck = 0;
if (!why && !run()) why = "the nodes did not come to rest";
} else if (path == 5) { /* node 10, by which node 11 prints, is busy, and sends node 12 a message by node 11 meanwhile */
saw_room_wait = 0; saw_passed = 0; watch = watch_mid;
if (!put_text(11, text) || !put_text(10, "BUSY") || !run()) why = "the nodes did not come to rest";
watch = 0;
} else {
if (!put_text(11, text) || !run()) why = "the nodes did not come to rest";
}
how11 = (int)pool[mid].n.mem[LINE_STATUS];
left = (unsigned)pool[mid].n.ds.depth;
if (!why && !(asleep(hera) && asleep(mid) && asleep(far))) why = "a node is not asleep";
if (!why && ended < 1) why = "node 11's text was not answered";
if (!why && how11 == V4_TEXT_COMPLETED && left != values) why = "its stack is not as deep as the text made it";
if (!why && how11 == V4_TEXT_ERROR && !(strstr(printed, "overflow") || strstr(printed, "Argument out of range") || strstr(printed, "Node gone") || strstr(printed, "Message refused")))
why = "its text ended in an error it did not report";
if (!why && how11 != V4_TEXT_COMPLETED && how11 != V4_TEXT_ERROR) why = "its text ended neither way";
if (!why && path == 0 && how11 == V4_TEXT_COMPLETED && printed_len != 600) why = "not all it printed arrived";
if (!why && path == 5 && how11 == V4_TEXT_COMPLETED && !strstr(printed, " 1199 ")) why = "not all it printed arrived";
if (!why && path == 5 && how11 == V4_TEXT_COMPLETED && !(saw_room_wait > 0 && saw_passed > 0)) why = "it did not wait for room, or passed nothing on while it did";
/* every node comes back */
if (!why && !(strcmp(tell(11, "ABORT"), "") == 0 && ended == 1 && pool[mid].n.ds.depth == 0)) why = "node 11 did not answer ABORT";
if (!why && path == 4 && !told(11, "12 3 ROUTE")) why = "node 11 did not take the way to 12 again";
if (!why && strcmp(tell(11, "1 2 + ."), "3 ") != 0) why = "node 11 did not answer";
if (!why && strcmp(tell(10, "1 2 + ."), "3 ") != 0) why = "node 10 did not answer";
if (!why && strcmp(tell(12, "1 2 + ."), "3 ") != 0) why = "node 12 did not answer";
if (!why && !(asleep(hera) && asleep(mid) && asleep(far))) why = "a node is not asleep afterwards";
checks++;
if (why) {
failures++; bad++;
printf("FAIL %s, %s %u: %s: \"%.60s\"\n", name[path], what, depth, why, printed);
show_stuck(text);
(void)made(path); /* the next begins on a row that is whole */
}
return how11;
}
int main(void)
{
unsigned path, k;
char text[160];
printf("v4 mesh depth tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
{
static const char *const files[] = { "core.v4", "input.v4", "dict.v4", "codegen.v4", "compile.v4", "quit.v4", "forth.v4",
"numout.v4", "words.v4", "system.v4", "qmath.v4", "blocks.v4", "log.v4", "acl.v4" };
CHECK(host_load(&tx, &nucleus, files, 14), "the nucleus assembles");
}
CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx));
if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; }
w_idle = v4_text_word(&tx, "(IDLE)");
w_fault = v4_text_word(&tx, "(FAULTS)");
nucleus.mem[DP] = DICT_W * 4; /* the variables, as at switch-on (tools/mkimage.c) */
nucleus.mem[LATEST] = v4_text_latest(&tx);
nucleus.mem[CFP] = CFS_W;
nucleus.mem[BASE] = 10;
nucleus.mem[FENCE] = DICT_W;
nucleus.mem[BOOT_CELLS] = nucleus.mem[DP];
nucleus.mem[BOOT_CELLS + 1] = nucleus.mem[LATEST];
nucleus.mem[LOG_LEVEL] = 2;
nucleus.mem[CONTEXT] = LATEST;
nucleus.mem[CURRENT] = LATEST;
nucleus.mem[SRC] = TIB;
nucleus.mem[LINE_STATUS] = 1;
HOST_MESSAGES_EMPTY(&nucleus);
for (path = 0; path < 6; path++) {
unsigned before = bad, done = 0, errors = 0;
CHECK(made(path), "a row for path %u", path);
if (failures > (int)bad) break;
/* the data stack at every depth from 20 to full */
for (k = V4_DATA_DEPTH - 12u; k <= V4_DATA_DEPTH; k++) {
size_t at;
fill(text, sizeof text, k);
at = strlen(text);
snprintf(text + at, sizeof text - at, "ACT");
if (one(path, "values on the stack", k, text, k) == V4_TEXT_COMPLETED) done++; else errors++;
}
/* and from every depth of calls from 20 to past the end of the return stack */
for (k = V4_RET_DEPTH - 12u; k <= V4_RET_DEPTH + 1u; k++) {
snprintf(text, sizeof text, "N%u", k);
(void)one(path, "calls deep", k, text, 0);
}
printf(" path %u: with %u to %u values on the stack, %u completed and %u ended in an error reported; %u failures\n", path, (unsigned)(V4_DATA_DEPTH - 12u), (unsigned)V4_DATA_DEPTH, done, errors, bad - before);
}
/* ---- between texts: node 11 passes on, and refuses, with as many values waiting on its stack as a text
* may leave (MESH.md 7d.4: the rule of 28). Not one fault. ---- */
CHECK(row() && told(11, ": P1 7 ; : P2 7 7 ; : P4 7 7 7 7 ; : P8 7 7 7 7 7 7 7 7 ;") && told(11, ": P16 P8 P8 ;") && told(11, "77 5 ROUTE")
&& told(12, ": S77 S\" 1 DROP\" 77 SEND ;"), "a row; node 11's way to 77 is a port with no one on it");
for (k = V4_DATA_DEPTH - 12u; k + 4u <= V4_DATA_DEPTH; k++) {
unsigned faults;
v4_cell lost = pool[mid].n.mem[LOST], refused = pool[far].n.mem[REFUSED];
fill(text, sizeof text, k);
CHECK(told(11, text) && pool[mid].n.ds.depth == k, "node 11's text leaves %u values on its stack", k);
faults = pool[mid].n.faults;
CHECK(strcmp(tell(12, "1 2 + ."), "3 ") == 0 && ended == 1, "with %u values waiting it passes text on to node 12, and what comes back", k);
CHECK(told(12, "S77") && pool[mid].n.mem[LOST] == lost + 1 && pool[far].n.mem[REFUSED] == refused + 1, "and refuses a message it has no way for, and tells node 12");
CHECK(pool[mid].n.faults == faults && pool[mid].n.ds.depth == k && asleep(mid), "without a fault, its stack as it was: %u values", (unsigned)pool[mid].n.ds.depth);
CHECK(strcmp(tell(11, "ABORT"), "") == 0 && strcmp(tell(11, "1 2 + ."), "3 ") == 0, "and it goes on");
}
printf(" %u runs; %u in which a node did not come back\n", runs, bad);
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}