Files
LithosAnanake/v4/tests/test_host_depth.c
T
rajamesandClaude Opus 5.5 06da5700bc 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>
2026-10-13 17:47:05 -04:00

453 lines
26 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, AWAIT broken
* by a line from the console, refuse a message while its own text waits for
* room, send a GONE -- with its data stack holding 20 to 32 values, and from
* 20 to 33 calls deep.
*
* EVERY RUN ENDS ONE OF TWO WAYS, and which is checked: the path WORKS, all
* of it, as it does on a shallow stack; or node 11's text ends in "Stack
* overflow" or "Return stack overflow" and nothing else -- with nothing
* half done: no message lost that was owed, no line swallowed. Where the
* stack is shallow enough it must work. And after each, every node must
* answer. "It came back" is not enough: with the nucleus's tries of the
* stack taken out -- (ROOM-SERVE), (ROOM) -- this test fails (it was
* mutated to see).
*/
#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 }; /* 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, worked, refused_clean;
/* what the paths that wait for room watch 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++; }
}
/* A MESSAGE THAT HAS BEEN PASSED ON AS OFTEN AS IT MAY BE comes to node 11 from node 12, for a node 99: its
* fourth word is 1. No row of three can make one -- it takes sixteen nodes, or a ring -- so it is put on the
* wire here, once, the first time node 11 is asleep for room. Node 11 refuses it without looking for its way:
* the one refusal in which a message is dropped before the node's deepest use of its stacks. */
static int spent_put;
static void put_spent(void)
{
static const v4_cell hd[V4_WIRE_HEADER] = { 99, 12, V4_MSG_TEXT, 1, 0, 0, 4 }, text[1] = { 0x31 };
if (!spent_put && places[mid].wire[3].rx) spent_put = v4_wire_put(places[mid].wire[3].rx, hd, text) == V4_WIRE_DONE;
}
static void watch_mid_spent(void)
{
watch_mid();
if (saw_room_wait) put_spent();
}
/* 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; }
}
#define PATHS 10u
static const char *const path_name[PATHS] = { "a text that prints", "SEND", "SEND with no way", "AWAIT answered", "AWAIT ended by a GONE", "passing on while its text waits for room",
"AWAIT broken by a line from the console", "a refusal while its text waits for room", "GONE",
"a spent message refused while its text waits" };
static char numbers[8192]; /* what 1200 0 DO I . LOOP prints */
/* 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[PATHS] = {
": 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 ;",
": ACT 12 AWAIT DROP ;",
": ACT 1200 0 DO I . LOOP ;",
": ACT 44 12 GONE ;",
": 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;
if (path == 9 && !told(10, ": SLOW 500000 0 DO LOOP ; : BUSY SLOW SLOW ;")) return 0;
/* 320 characters: more than fits beside what node 11 itself is waiting to print */
if (path == 7 && !(told(10, ": SLOW 500000 0 DO LOOP ; : BUSY SLOW SLOW ;") && told(12, ": LATE 250000 0 DO LOOP PAD 320 BLANK PAD 320 99 SEND ;"))) return 0;
return 1;
}
/* 1 if `s` is `lead`, then some of `body` from its start, then exactly `end`. */
static int is_cut_short(const char *s, const char *body, const char *end)
{
size_t n = strlen(s), e = strlen(end);
return n >= e && strcmp(s + n - e, end) == 0 && strncmp(s, body, n - e) == 0 && n - e <= strlen(body);
}
/* Node 11 takes the path with `text`; `values` is how many values the text leaves on its stack if it completes;
* `must` says the stack is shallow enough that it must work. Returns 1 if it worked, 0 if it ended in a clean
* stack error, and counts a failure if it did neither. */
static int one(unsigned path, const char *what, unsigned depth, const char *text, unsigned values, int must)
{
static char sixhundred[601];
const char *why = 0, *over = 0;
v4_cell lost11 = pool[mid].n.mem[LOST], refused12 = pool[far].n.mem[REFUSED], ways12;
int how11, works = 0, clean_error = 0;
unsigned left;
v4_uheat_t clock12;
if (!sixhundred[0]) memset(sixhundred, 'A', 600);
runs++;
step_cap = 60000000;
if (path == 8 && !told(12, "44 2 ROUTE")) why = "node 12 did not take a way";
ways12 = pool[far].n.mem[ROUTE_COUNT];
forget();
saw_room_wait = 0; saw_passed = 0;
clock12 = pool[far].es.anticlock;
if (why) { }
else 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 */
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 (path == 6) { /* node 12 sends no answer; node 11 waits, and is typed to */
if (!put_text(11, text) || !run()) why = "it did not come to wait";
else if (!put_text(11, "77 .") || !run()) why = "the nodes did not come to rest";
} else if (path == 7) { /* node 10 is busy; node 12 sends a message for 99 by node 11, whose way for it is the full wire */
watch = watch_mid;
if (!put_text(11, text) || !put_text(10, "BUSY") || !put_text(12, "LATE") || !run()) why = "the nodes did not come to rest";
watch = 0;
} else if (path == 9) { /* node 10 is busy; a spent message comes while node 11 waits for room -- or, if it never does, afterwards */
spent_put = 0; watch = watch_mid_spent;
if (!put_text(11, text) || !put_text(10, "BUSY") || !run()) why = "the nodes did not come to rest";
watch = 0;
if (!why && !spent_put) { put_spent(); if (!run()) why = "the nodes did not come to rest"; }
} 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";
/* which of the two ways did it end? */
if (!why) {
if (strstr(printed, "Return stack overflow\n")) over = "Return stack overflow\n";
else if (strstr(printed, "Stack overflow\n")) over = "Stack overflow\n";
switch (path) {
case 0: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, sixhundred) == 0 && left == values;
clean_error = over && is_cut_short(printed, sixhundred, over); break;
case 1: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[far].es.anticlock != clock12 && pool[mid].n.mem[LOST] == lost11; /* node 12 did the text */
clean_error = over && strcmp(printed, over) == 0 && pool[far].es.anticlock == clock12; break; /* nothing was sent */
case 2: works = how11 == V4_TEXT_ERROR && strcmp(printed, "Argument out of range\n") == 0 && left == values;
clean_error = over && strcmp(printed, over) == 0; break;
case 3: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[mid].n.mem[LOST] == lost11;
clean_error = over && strcmp(printed, over) == 0; break;
case 4: works = how11 == V4_TEXT_ERROR && strcmp(printed, "Node gone\n") == 0 && left == values;
clean_error = over && strcmp(printed, over) == 0; break;
case 5: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, numbers) == 0 && left == values && saw_room_wait > 0 && saw_passed > 0;
clean_error = over && is_cut_short(printed, numbers, over); break;
case 6: works = strcmp(printed, "Interrupted\n77 ") == 0 && ended == 2;
if (over) { char want[64]; snprintf(want, sizeof want, "%s77 ", over); clean_error = strcmp(printed, want) == 0 && ended == 2; }
break;
case 7: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, numbers) == 0 && left == values && saw_room_wait > 0
&& pool[mid].n.mem[LOST] == lost11 + 1 && pool[far].n.mem[REFUSED] == refused12 + 1;
clean_error = over && is_cut_short(printed, numbers, over) && pool[far].n.mem[REFUSED] == refused12 + 1; break; /* whoever refused it, node 12 was told */
case 8: works = how11 == V4_TEXT_COMPLETED && printed_len == 0 && left == values && pool[far].n.mem[ROUTE_COUNT] == ways12 - 1;
clean_error = over && strcmp(printed, over) == 0; break;
case 9: works = how11 == V4_TEXT_COMPLETED && strcmp(printed, numbers) == 0 && left == values && saw_room_wait > 0
&& pool[mid].n.mem[LOST] == lost11 + 1 && pool[far].n.mem[REFUSED] == refused12 + 1;
clean_error = over && is_cut_short(printed, numbers, over) && pool[mid].n.mem[LOST] == lost11 + 1 && pool[far].n.mem[REFUSED] == refused12 + 1; break; /* it was dropped: its sender was told */
}
if (clean_error && !(path == 6 || (how11 == V4_TEXT_ERROR && left == 0))) clean_error = 0; /* a stack fault empties the stack */
if (works && over) works = 0;
if (!works && !clean_error) why = "it neither worked nor ended in a stack error and nothing else";
else if (!works && must) why = "it ended in a stack error where the stack is shallow enough for it to work";
}
/* every node comes back */
if (!why && !(strcmp(tell(11, "ABORT"), "") == 0 && ended == 1 && pool[mid].n.ds.depth == 0 && pool[mid].n.mem[AWAIT_FROM] == 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: how %d, %u left, lost %+ld, refused %+ld, waits %u/%u: \"%.50s\"\n", path_name[path], what, depth, why, how11, left,
(long)(pool[mid].n.mem[LOST] - lost11), (long)(pool[far].n.mem[REFUSED] - refused12), saw_room_wait, saw_passed, printed);
show_stuck(text);
(void)made(path); /* the next begins on a row that is whole */
return -1;
}
if (works) worked++; else refused_clean++;
return works;
}
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);
{ size_t at = 0; for (k = 0; k < 1200; k++) at += (size_t)snprintf(numbers + at, sizeof numbers - at, "%u ", k); }
for (path = 0; path < PATHS; path++) {
unsigned before = bad, w0 = worked, e0 = refused_clean, first_error = 0;
int r;
CHECK(made(path), "a row for path %u", path);
if (failures > (int)bad) break;
/* the data stack at every depth from 20 to full: with 24 or fewer every path must work */
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");
r = one(path, "values on the stack", k, text, k, k + 8u <= V4_DATA_DEPTH);
if (r == 0 && !first_error) first_error = k;
}
printf(" %-42s %2u..%u values: works to %u, then a stack error and nothing else;", path_name[path], (unsigned)(V4_DATA_DEPTH - 12u), (unsigned)V4_DATA_DEPTH, first_error ? first_error - 1u : (unsigned)V4_DATA_DEPTH);
/* and from every depth of calls from 14 to past the end of the return stack: 21 deep or less every path
* must work -- 16 or less for the two that print and are woken while they wait for room, which go
* deepest: the loop, the word that prints, EMIT, the wait, and (SERVE) under it */
first_error = 0;
for (k = V4_RET_DEPTH - 18u; k <= V4_RET_DEPTH + 1u; k++) {
snprintf(text, sizeof text, "N%u", k);
r = one(path, "calls deep", k, text, 0, k + ((path == 5 || path == 7 || path == 9) ? 16u : 11u) <= V4_RET_DEPTH);
if (r == 0 && !first_error) first_error = k;
}
printf(" calls: works to %u deep; %u worked, %u clean errors, %u failures\n", first_error ? first_error - 1u : (unsigned)(V4_RET_DEPTH + 1u), worked - w0, refused_clean - e0, bad - before);
}
/* ---- between texts: node 11 passes on, refuses, and holds what it has no room to pass on, 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 ; : PR 1200 0 DO I . LOOP ;") && told(10, ": SLOW 500000 0 DO LOOP ; : BUSY SLOW SLOW ;"), "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");
forget();
saw_room_wait = 0; watch = watch_mid;
CHECK(put_text(12, "PR") && put_text(10, "BUSY") && run() && strcmp(printed, numbers) == 0 && saw_room_wait > 0,
"and holds what node 12 prints while node 10 is busy, asleep for room, and passes all of it on: %u characters", printed_len);
watch = 0;
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 worked, %u ended in a stack error and nothing else, %u did neither\n", runs, worked, refused_clean, bad);
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}