- The result of every take is looked at: a wire cut between the look and the take leaves nothing acted on (the text before run again, a refusal counted, a wait ended by an old answer, a way forgotten). - A node with no room to pass on what another printed, or its word of how text ended, leaves it on the wire it came by and moves it when there is room; what is behind it for the same port waits too. Sleep for room (9) is woken by room on any of the node's wires (engine; MESH.md 7d.3). - The lone node's host keeps no queue for the kernel's port: a message to it is error 18. The step limit is counted while the node waits for room. - A text waiting for room to print is not interrupted by console text; AWAIT is, as before. (BREAK?) is gone. - A GONE is believed only by the centre's port; unawaited answers let go are counted; nothing past a message's last character goes on a wire; AWAIT sets (AWAIT-FROM) only once the stack has been tried. - test_host_depth.c: ten paths, each run works or ends in one named stack error; it fails with (ROOM) or (ROOM-SERVE) taken out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
451 lines
26 KiB
C
451 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, 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;
|
|
|
|
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;
|
|
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[mid].n.mem[LOST] == lost11 + 1; /* node 12's answer, which it did not wait for */
|
|
clean_error = over && strcmp(printed, over) == 0 && pool[mid].n.mem[LOST] == lost11; break;
|
|
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;
|
|
}
|