/* test_host_mesh.c -- StarForth nodes that talk: a console wired to one * node, and two more in a row behind it. docs/v4.0.0/MESH.md sections 6 * and 7, steps 3 and 4. * * console --1 [10] 2--2 [11] 3--2 [12] * * Each node is the nucleus (v4/capsule/ *.v4) and nothing else. They start * alike, numbered 0 and knowing no way anywhere; everything they come to * know they are told by text sent from the console, as whoever wires a node * tells it. The console is a device on a port and speaks whole messages * (v4/include/v4/message.h): it puts them on the wire to its node and * takes them off the wire from it, through the fabric (MESH.md 7d.2). * * Each scene after the first is played on a row made afresh, row(), so * that none depends on what an earlier one left. */ #include "v4/image.h" #include "v4/fabric.h" #include "v4/message.h" #include "v4/text.h" #include #include #include #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 */ /* 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 }; 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 unsigned stale_out; /* output messages whose last word had anything in it past the last character */ 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]; if (hd[6] % 4 != 0 && ((v4_ucell)text[hd[6] / 4] >> (8u * (unsigned)(hd[6] % 4))) != 0) stale_out++; } 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; } /* 1 if it is asleep waiting for room on a wire (operation 9). */ static int waits_for_room(unsigned id) { const v4_fabric_node *x = v4_fabric_node_at(&f, id); return x && x->n.doing && x->n.do_op == 9; } /* the queue of messages going from a node's port, and coming to it */ static const v4_wire_queue *going_from(unsigned id, unsigned port) { return places[id].wire[port].tx; } static const v4_wire_queue *coming_to(unsigned id, unsigned port) { return places[id].wire[port].rx; } 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 unsigned between_data, between_ret; /* the most of each stack any node has used while doing nothing that is a text's (QUIET), */ static int was_quiet[3], clean[3]; /* after a text that left nothing on the data stack */ 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(); for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) { int quiet_now = pool[k].n.mem[QUIET] == 1; if (quiet_now && !was_quiet[k]) clean[k] = pool[k].n.ds.depth <= 1; /* its text has ended: did it leave anything? (how it ended may still be there) */ was_quiet[k] = quiet_now; if (!quiet_now || !clean[k]) continue; if (pool[k].n.ds.depth > between_data) between_data = (unsigned)pool[k].n.ds.depth; if (pool[k].n.rs.depth > between_ret) between_ret = (unsigned)pool[k].n.rs.depth; } /* 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; } /* Let time pass until `when` says so; 0 if it never does. */ static int run_until(int (*when)(void), unsigned cap) { unsigned steps; for (steps = 0; steps < cap; steps++) { if (when()) return 1; (void)v4_fabric_step(&f); (void)console_takes(); steps_run++; if (watch) watch(); } return when(); } /* 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; was_quiet[0] = was_quiet[1] = was_quiet[2] = 0; clean[0] = clean[1] = clean[2] = 0; 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"); } /* And with the far node wired straight to the one at the console as well, port 3 to port 3; node 10 reaches * 12 by that wire, and 12 reaches everything by it but 11. */ static int triangle(void) { return row() && v4_fabric_wire(&f, hera, 3, far, 3) && told(10, "12 NO-ROUTE 12 3 ROUTE 12 3 NEIGHBOUR") && told(12, "NO-ROUTES 3 DEFAULT-ROUTE 11 2 ROUTE 10 3 NEIGHBOUR"); } static v4_uheat_t clock_of(unsigned id) { return pool[id].es.anticlock; } /* The whole of the mesh as it stands, kept and put back: so that one moment can be gone on from many times. */ static v4_fabric_node kept_pool[3]; static v4_place kept_places[3]; static v4_wire_queue kept_queues[QUEUES]; static v4_fabric kept_f; static void keep(void) { memcpy(kept_pool, pool, sizeof pool); memcpy(kept_places, places, sizeof places); memcpy(kept_queues, queues, sizeof queues); kept_f = f; } static void put_back(void) { memcpy(pool, kept_pool, sizeof pool); memcpy(places, kept_places, sizeof places); memcpy(queues, kept_queues, sizeof queues); f = kept_f; forget(); } /* what a scene waits for */ static int far_has_one(void) { return coming_to(far, 2) && coming_to(far, 2)->used > 0; } static int far_has_nack(void) { return far_has_one() && coming_to(far, 2)->cell[(coming_to(far, 2)->head + 2u) % V4_WIRE_CELLS] == V4_MSG_NACK; } static int far_awaits_and_has_one(void) { return pool[far].n.mem[AWAIT_FROM] == 11 && far_has_one(); } static v4_cell mid_for; /* whom the message waited for at node 11's wire from node 12 is for */ static int mid_has_one(void) { return coming_to(mid, 3) && coming_to(mid, 3)->used > 0 && coming_to(mid, 3)->cell[coming_to(mid, 3)->head] == mid_for; } static v4_cell far_lost_wanted; static int far_has_lost(void) { return pool[far].n.mem[LOST] >= far_lost_wanted && asleep(far); } static int mid_holds(void) { return pool[mid].n.doing && pool[mid].n.do_op == 9 && pool[mid].n.mem[H_PORT] >= 0; } static unsigned saw_mid_hold; static void watch_mid_holds(void) { if (pool[mid].n.doing && pool[mid].n.do_op == 9 && pool[mid].n.mem[H_PORT] >= 0) saw_mid_hold++; } /* what a scene watches for while it runs */ static unsigned saw_room_wait, saw_full; 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); } /* AN EXCHANGE, DISTURBED AT EVERY STEP. The exchange that start() begins is played from the mesh as kept: * once whole, to see how many steps it takes, and then once for every one of those steps with the mesh * disturbed at that step -- * 1 node 11 removed, and node 12 then wired to node 10, as whoever removed it would, so that it can be asked; * 2 node 12 removed; * 3 the wire between nodes 11 and 12 cut and made again at once: what waited on it is let go. * After each, every node left must come to rest, asleep and blocked at no port, and answer. With `moments` * only the steps at which something the fabric keeps had changed are tried -- what is on a wire, or what a * node asks of one: for an exchange too long to disturb at every step. A removal between two such steps * finds the mesh as the one before it did. * How many steps the exchange takes; how many disturbances were tried; how many went wrong, and the first. */ 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 v4_cell start_node; static const char *start_text; static int start_plain(void) { return put_text(start_node, start_text); } static int hera_busy(void) { return pool[hera].n.mem[QUIET] == 0; } /* Text for node 12 that prints, with node 10 made busy and the wire from node 11 to node 10 full but for 7 * cells: what node 12 prints, and how its text ended, are held by node 11, asleep for room (MESH.md 7d.4). */ static int start_held(void) { static v4_cell big[V4_WIRE_HEADER + V4_MSG_MAX_CHARS / 4u]; v4_wire_queue *q = places[mid].wire[2].tx; unsigned k; int ok = put_text(12, "A") && put_text(10, "SLOW") && run_until(hera_busy, 5000); /* node 10 has passed the one on and begun the other */ memset(big, 0, sizeof big); big[0] = CONSOLE_ID; big[1] = 12; big[2] = V4_MSG_OUTPUT; big[3] = 16; big[6] = 1024; for (k = 0; k < 3; k++) ok = ok && v4_wire_put(q, big, big + V4_WIRE_HEADER) == V4_WIRE_DONE; big[6] = 884; return ok && v4_wire_put(q, big, big + V4_WIRE_HEADER) == V4_WIRE_DONE && V4_WIRE_CELLS - q->used == 7; } /* what the fabric keeps, in a number: what every wire holds and what every node asks of one */ static unsigned long fabric_mark(void) { unsigned long h = 1469598103UL; unsigned k; for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) h = (h ^ (unsigned long)(pool[k].n.doing * 16 + (int)pool[k].n.do_op)) * 16777619UL; for (k = 0; k < QUEUES; k++) h = ((h ^ queues[k].used) * 16777619UL ^ queues[k].head) * 16777619UL; return h; } static unsigned disturbed_at_every_step(int (*start)(void), unsigned how, int moments, unsigned *tried, unsigned *wrong, unsigned *first_wrong) { unsigned long before, mark, last = 0; unsigned whole, s; *tried = 0; *wrong = 0; *first_wrong = 0; put_back(); before = steps_run; saw_mid_hold = 0; watch = watch_mid_holds; if (!start() || !run()) { watch = 0; *wrong = 1; return 0; } watch = 0; whole = (unsigned)(steps_run - before); put_back(); if (!start()) { *wrong = 1; return 0; } for (s = 0; s <= whole; s++) { mark = fabric_mark(); if (!moments || s == 0 || mark != last) { 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 */ if (how == 1) ok = v4_fabric_remove(&f, mid) == &pool[mid] && run() && at_rest(hera) && at_rest(far) && v4_fabric_wire(&f, hera, 2, far, 2) && answers(12, far) && answers(10, hera); else if (how == 2) ok = v4_fabric_remove(&f, far) == &pool[far] && run() && at_rest(hera) && at_rest(mid) && answers(11, mid) && answers(10, hera); else ok = v4_fabric_unwire(&f, mid, 3) && v4_fabric_wire(&f, mid, 3, far, 2) && run() && at_rest(hera) && at_rest(mid) && at_rest(far) && answers(12, far) && answers(11, mid) && answers(10, hera); (*tried)++; 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; } last = mark; (void)v4_fabric_step(&f); (void)console_takes(); } return whole; } int main(void) { unsigned i; printf("v4 mesh 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); CHECK(fresh(), "the console is wired to one node's port 1, the three nodes are in a row, and all three sleep"); /* ---- they are told who they are and the way to each other, by text ---- */ CHECK(is_empty(tell(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE 0 2 ROUTE")) && ended == 1 && ended_how == V4_TEXT_COMPLETED && ended_from == 10, "the node at the console takes text sent to 0, and is 10 from then on: %ld", (long)ended_from); CHECK(is_empty(tell(0, "11 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE 0 3 ROUTE")) && ended == 1 && ended_from == 11 && ended_how == V4_TEXT_COMPLETED, "text sent to 0 now passes through 10 to the next, which is 11 from then on: %ld", (long)ended_from); CHECK(is_empty(tell(0, "12 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE")) && ended == 1 && ended_from == 12 && ended_how == V4_TEXT_COMPLETED, "and then through both to the last, 12: %ld", (long)ended_from); CHECK(is_empty(tell(10, "11 2 ROUTE 12 2 ROUTE")) && ended_from == 10 && is_empty(tell(11, "12 3 ROUTE")) && ended_from == 11, "each is told the way to the others"); /* ---- they talk ---- */ CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && printed_from == 10 && ended_how == V4_TEXT_COMPLETED, "text for the node at the console is done there"); CHECK(strcmp(tell(11, "20 22 + ."), "42 ") == 0 && printed_from == 11 && ended_from == 11, "text for the next node is done there, and what it prints comes back"); CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && ended_from == 12 && ended_how == V4_TEXT_COMPLETED, "text for the last passes through two nodes, and what it prints comes back through two: \"%s\" from %ld", printed, (long)printed_from); CHECK(strcmp(tell(12, "NOSUCH"), "UNKNOWN WORD: 'NOSUCH'\n") == 0 && ended_from == 12 && ended_how == V4_TEXT_ERROR, "an error there is reported from there"); CHECK(strcmp(tell(12, ": SQ DUP * ;"), "") == 0 && strcmp(tell(12, "9 SQ ."), "81 ") == 0 && strstr(tell(11, "9 SQ ."), "UNKNOWN WORD") != NULL, "each node has its own dictionary: a word defined on one is not on another"); { const char *all = tell(12, "WORDS"); CHECK(strlen(all) > 1000 && printed_from == 12 && ended == 1 && ended_at == printed_len, "more than a message holds comes back in several, and then how it ended: %u characters", (unsigned)strlen(all)); } { static char line[1100]; memset(line, ' ', 1024); memcpy(line, "66 EMIT", 7); memcpy(line + 1017, "67 EMIT", 7); line[1024] = 0; CHECK(strcmp(tell(12, line), "BC") == 0, "a message as long as a message may be is passed on whole: 1024 characters"); } /* ---- one node sends to another ---- */ CHECK(strcmp(tell(10, ": TELL S\" 65 EMIT 12 . \" 12 SEND ; TELL"), "A12 ") == 0 && printed_from == 12, "text one node SENDs another is done there, and what that prints goes to its console: \"%s\" from %ld", printed, (long)printed_from); CHECK(strstr(tell(10, ": NOWAY S\" 1\" 99 SEND ; NOWAY"), "Argument out of range") != NULL && ended_how == V4_TEXT_ERROR, "SEND to a node there is no way to is an error"); CHECK(strstr(tell(10, ": LONG PAD 1025 12 SEND ; LONG"), "Argument out of range") != NULL && strstr(tell(10, ": SHORT PAD -1 12 SEND ; SHORT"), "Argument out of range") != NULL && strcmp(tell(10, "DEPTH ."), "0 ") == 0, "and so is more text than a message carries, or less than none; nothing is left on the stack"); CHECK(strstr(tell(10, ": P5 S\" 1\" 0 5 SEND-ON ; P5"), "No one on that port") != NULL && ended_how == V4_TEXT_ERROR, "SEND-ON by a port with no one on it is an error that says so"); /* ---- no way ---- */ nacks = 0; i = (unsigned)pool[hera].n.mem[LOST]; CHECK(is_empty(tell(99, "1 2 + .")) && ended == 0, "text for a node there is no way to is not done by anyone"); CHECK(nacks == 1 && nack_from == 10 && nack_about == 99, "the console, which sent it, is told it was refused, by the node that could not pass it on: %d from %ld about %ld", nacks, (long)nack_from, (long)nack_about); CHECK(pool[hera].n.mem[LOST] == (v4_cell)i + 1, "and that node has counted it"); CHECK(strcmp(tell(11, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "the others have lost nothing"); i = (unsigned)pool[hera].n.mem[LOST]; CHECK(told(10, ": TELL2 S\" 1 DROP\" 12 SEND ; TELL2") && pool[hera].n.mem[LOST] == (v4_cell)i && coming_to(hera, 2)->used == 0, "an answer that comes to a node that is not waiting for it is let go, and is not counted: a SEND that is not waited on is ordinary"); /* ---- waiting ---- */ { v4_uheat_t c0 = clock_of(0), c1 = clock_of(1), c2 = clock_of(2); for (i = 0; i < 100; i++) (void)v4_fabric_step(&f); CHECK(asleep(hera) && asleep(mid) && asleep(far), "with nothing to do, all three sleep"); CHECK(clock_of(0) == c0 && clock_of(1) == c1 && clock_of(2) == c2, "and execute nothing"); } /* ---- the ways are forgotten and told again: the wiring has changed ---- */ CHECK(v4_fabric_wire(&f, hera, 3, far, 3), "the far node is wired straight to the one at the console as well"); CHECK(is_empty(tell(10, "NO-ROUTES 1 1 ROUTE 11 2 ROUTE 12 3 ROUTE")) && is_empty(tell(12, "NO-ROUTES 3 DEFAULT-ROUTE")), "and both are told"); { v4_uheat_t before = clock_of(1); CHECK(strcmp(tell(12, "5 5 + ."), "10 ") == 0 && clock_of(1) == before, "text for it no longer passes through the middle node, which executes nothing"); } /* ---- two neighbours with something for each other at the same moment ---- * Each puts its message on the wire to the other, whole, and neither waits for the other (MESH.md 7d.2). * Each node is told who is on each of its ports: it is how it knows its centre. */ CHECK(is_empty(tell(10, "NO-ROUTES 1 1 ROUTE 11 2 ROUTE 12 2 ROUTE")) && is_empty(tell(12, "NO-ROUTES 2 DEFAULT-ROUTE")), "the row of three again"); CHECK(is_empty(tell(10, "11 2 NEIGHBOUR 0 3 NEIGHBOUR")) && is_empty(tell(11, "10 2 NEIGHBOUR 12 3 NEIGHBOUR")) && is_empty(tell(12, "11 2 NEIGHBOUR 0 3 NEIGHBOUR")), "each is told who is on the other end of its ports"); CHECK(strstr(tell(10, "11 8 NEIGHBOUR"), "Argument out of range") != NULL && strstr(tell(10, "11 -1 NEIGHBOUR"), "Argument out of range") != NULL, "a port a node has not got is an error"); /* the far node sends the middle one text, and then word of how its own * text ended, which goes by the middle one; the middle one does the * text and answers the far one: the two messages meet on one wire */ CHECK(strcmp(tell(12, ": HI S\" 72 EMIT\" 11 SEND ; HI"), "H") == 0 && printed_from == 11 && ended == 1 && ended_from == 12, "from the far end to the middle, with the answers crossing: \"%s\" from %ld", printed, (long)printed_from); CHECK(asleep(hera) && asleep(mid) && asleep(far), "and all three are asleep again"); /* every node sends every other a number of messages, all at once: each * counts what it is sent */ #define COUNTING "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT VARIABLE TRIED : BURST WHO ! 0 DO 1 TRIED +! S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;" CHECK(is_empty(tell(10, COUNTING)) && is_empty(tell(11, COUNTING)) && is_empty(tell(12, COUNTING)), "each node is given a word to count with and one to send with"); { const char *r = tell(10, ": GO S\" 6 10 BURST 6 12 BURST\" 11 SEND S\" 6 11 BURST 6 10 BURST\" 12 SEND 6 11 BURST 6 12 BURST ; GO"); CHECK(is_empty(r), "six messages from each node to each other, all at once: \"%s\"", r); CHECK(asleep(hera) && asleep(mid) && asleep(far), "all three come to rest"); CHECK(strcmp(tell(10, "GOT @ . (LOST) @ ."), "12 1 ") == 0 && strcmp(tell(11, "GOT @ . (LOST) @ ."), "12 0 ") == 0 && strcmp(tell(12, "GOT @ . (LOST) @ ."), "12 0 ") == 0, "every one of them arrived, and none was let go: \"%s\"", printed); } /* ---- THE FLOOD: more than the wires hold (MESH.md 7d.6, acceptance 2) ---- * 800 messages among three nodes, all at once. A SEND the wire has no room for is refused at once: the * word that sends ends there, error 19, and the refusal is counted. A message a middle node has no room * to pass on is let go there, counted, and its sender told. Nothing waits, and it comes to rest. */ { long got = 0, lost = 0, refused = 0, sent = 0, tried = 0, v, let_go, orders = 0; unsigned k, q, left = 0; static const v4_cell who[3] = { 10, 11, 12 }; const char *r; CHECK(is_empty(tell(10, "0 GOT ! 0 SENT ! 0 TRIED !")) && is_empty(tell(11, "0 GOT ! 0 SENT ! 0 TRIED !")) && is_empty(tell(12, "0 GOT ! 0 SENT ! 0 TRIED !")), "the counts begin again"); for (k = 0; k < 3; k++) { lost -= pool[k].n.mem[LOST]; refused -= pool[k].n.mem[REFUSED]; } r = tell(10, ": GO2 S\" 200 12 BURST 200 10 BURST\" 11 SEND S\" 200 10 BURST 200 11 BURST\" 12 SEND 200 12 BURST 200 11 BURST ; GO2"); CHECK(strcmp(r, "(still running)") != 0, "two hundred from each to each: it ends"); CHECK(asleep(hera) && asleep(mid) && asleep(far), "and all three come to rest, asleep"); for (k = 0; k < 3; k++) for (q = 0; q < V4_PORTS; q++) if (coming_to(k, q)) left += coming_to(k, q)->used; CHECK(left == 0, "with nothing left on any wire: %u cells", left); for (k = 0; k < 3; k++) { v = strtol(tell(who[k], "GOT @ ."), NULL, 10); got += v; CHECK(v >= 0 && v <= 400, "node %ld did %ld of the messages for it", (long)who[k], v); sent += strtol(tell(who[k], "SENT @ ."), NULL, 10); v = strtol(tell(who[k], "TRIED @ ."), NULL, 10); tried += v; if (v == 0) orders++; /* it never began to send: the text that told it to was itself let go on the way */ lost += pool[k].n.mem[LOST]; refused += pool[k].n.mem[REFUSED]; } let_go = sent - got; printf(" the flood: %ld sends tried: %ld refused at once, %ld let go on the way, %ld done; %ld of the two texts that set a node sending let go; (LOST) %ld, (REFUSED) %ld\n", tried, tried - sent, let_go, got, orders, lost, refused); /* THE ACCOUNT IS EXACT. Every send tried was refused at once (counted once, in (REFUSED), and the word * that sent ended there); or got on a wire and was done; or got on a wire and was let go on the way by * a node with no room to pass it on -- counted there in (LOST), and once more where its NACK ended: in * (REFUSED) at the node that sent it, or in (LOST) if the NACK could not go either. That is so too of the * text that sets a node sending, which is one more message on those wires. Nothing else is * counted: an answer waits for room and is not let go, and one that nobody waits for is not a loss. */ CHECK(tried > sent && got > 0 && let_go >= 0, "there was not room for all of them: some were refused at once"); CHECK(lost + refused == (tried - sent) + 2 * (let_go + orders), "every message is accounted for, exactly: (LOST) + (REFUSED) is %ld; %ld refused at once and twice %ld let go is %ld", lost + refused, tried - sent, let_go + orders, (tried - sent) + 2 * (let_go + orders)); CHECK(let_go > 0, "and some were let go on the way, so that is not an account of nothing"); CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12, "and the nodes go on as before"); } /* ---- acceptance 2 and 3: REFUSED AT ONCE, and a stuck node holds up no one ---- */ CHECK(row(), "a row made afresh"); { v4_cell r0; char text[64]; CHECK(told(10, ": F 0 DO S\" HIT\" 11 SEND LOOP ;"), "node 10 is given a word that sends node 11 short texts"); stuck = 1u << mid; (void)tell(11, ": C BEGIN 0 UNTIL ; C"); CHECK(ended == 0 && !asleep(mid) && coming_to(mid, 2)->used == 0, "node 11 is stuck in a loop that never ends; it took the text that stuck it"); r0 = pool[hera].n.mem[REFUSED]; snprintf(text, sizeof text, "%u F", (unsigned)(V4_WIRE_CELLS / 8u)); CHECK(is_empty(tell(10, text)) && ended == 1 && ended_how == V4_TEXT_COMPLETED && pool[hera].n.mem[REFUSED] == r0 && going_from(hera, 2)->used == V4_WIRE_CELLS, "as many as the wire holds are taken, and none is refused: the wire is full, %u cells", going_from(hera, 2)->used); for (i = 1; i <= 5; i++) { const char *r = tell(10, "1 F"); CHECK(strstr(r, "Message refused") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && pool[hera].n.mem[REFUSED] == r0 + (v4_cell)i, "one more is refused at once, and counted: the line ends, having waited for nothing: \"%s\", %ld", r, (long)(pool[hera].n.mem[REFUSED] - r0)); CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers"); } CHECK(asleep(hera) && asleep(far) && pool[hera].n.mem[LOST] == 0, "nothing waits: nodes 10 and 12 are asleep, and nothing was let go"); /* text for node 12, beyond the stuck one */ { static char line[1100]; v4_cell lost = pool[hera].n.mem[LOST]; memset(line, ' ', 1024); memcpy(line, "1 DROP", 6); line[1024] = 0; nacks = 0; (void)tell(12, line); CHECK(nacks == 1 && nack_from == 10 && nack_about == 12 && pool[hera].n.mem[LOST] == lost + 1 && ended == 0, "text for node 12, beyond the stuck node, is refused at node 10, whose wire to 11 is full, and the console is sent the NACK: %d from %ld about %ld", nacks, (long)nack_from, (long)nack_about); CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera), "node 10 answers, and sleeps"); } } /* ---- a refusal owed to a stuck node ---- */ CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other"); { v4_cell lost = pool[far].n.mem[LOST]; v4_uheat_t c; CHECK(told(12, "NO-ROUTES 1 3 ROUTE 10 3 ROUTE 11 2 ROUTE") && told(11, "55 3 ROUTE"), "node 12 has no way for 55; node 11 is told the way to 55 is by node 12"); stuck = 1u << mid; (void)tell(11, ": C1 S\" 1 DROP\" 55 SEND BEGIN 0 UNTIL ; C1"); CHECK(ended == 0 && pool[far].n.mem[LOST] == lost + 1, "node 11 sends 55 a message by node 12 and is then stuck in a loop: node 12 has let the message go"); CHECK(going_from(far, 2)->used == 8 && coming_to(far, 2)->used == 0, "its NACK is on the wire to node 11, which does not take it: %u cells", going_from(far, 2)->used); c = clock_of(far); for (i = 0; i < 500; i++) (void)v4_fabric_step(&f); CHECK(asleep(far) && clock_of(far) == c, "node 12 is asleep, not going round: it owes nothing"); /* nothing but the text goes on the wire: not what lies after it in the node's memory */ (void)tell(12, ": S5 S\" ABCDEXYZ\" DROP 5 11 SEND ; S5"); CHECK(coming_to(mid, 3)->used == 8 + 9 && coming_to(mid, 3)->cell[(coming_to(mid, 3)->head + 16u) % V4_WIRE_CELLS] == 'E', "five characters sent of eight: the last word on the wire holds the fifth and nothing else: %lx", (unsigned long)coming_to(mid, 3)->cell[(coming_to(mid, 3)->head + 16u) % V4_WIRE_CELLS]); CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and it and node 10 answer: \"%s\"", printed); } /* the same with the wire to the stuck node full: the NACK cannot go, and is counted */ CHECK(triangle(), "the same made afresh"); { v4_cell lost; v4_uheat_t c; char text[64]; CHECK(told(12, "NO-ROUTES 1 3 ROUTE 10 3 ROUTE 11 2 ROUTE : F 0 DO S\" HIT\" 11 SEND LOOP ;") && told(11, "55 3 ROUTE"), "node 12 has no way for 55, and a word that sends node 11 short texts"); stuck = 1u << mid; (void)tell(11, ": C2 400000 0 DO LOOP S\" 1 DROP\" 55 SEND BEGIN 0 UNTIL ; C2"); snprintf(text, sizeof text, "%u F", (unsigned)(V4_WIRE_CELLS / 8u)); CHECK(told(12, text) && going_from(far, 2)->used == V4_WIRE_CELLS, "node 11 is busy; node 12 fills the wire to it"); lost = pool[far].n.mem[LOST]; far_lost_wanted = lost + 1; CHECK(run_until(far_has_lost, 30000000) && run() && pool[far].n.mem[LOST] == lost + 2 && going_from(far, 2)->used == V4_WIRE_CELLS && coming_to(far, 2)->used == 0, "node 11 then sends 55 a message by node 12 and sticks: node 12 lets it go, and the NACK it owes has no room on the wire: both are counted: %ld", (long)(pool[far].n.mem[LOST] - lost)); c = clock_of(far); for (i = 0; i < 500; i++) (void)v4_fabric_step(&f); CHECK(asleep(far) && clock_of(far) == c && strcmp(tell(12, "7 8 * ."), "56 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 12 is asleep, owing nothing, and it and node 10 answer"); } /* ---- THE KERNEL ON A NODE'S PORT TAKES NO MESSAGES (MESH.md 7d.5, ruled 2026-10-08) ---- */ CHECK(triangle() && v4_fabric_wire_kernel(&f, far, 0, &kernel_dev), "the same made afresh, with a kernel on node 12's port 0"); { kernel_words = 0; CHECK(strcmp(tell(12, "7 0 PORT! 1 2 + ."), "3 ") == 0 && kernel_words == 1, "a word written to the kernel's port is taken, as ever"); CHECK(strstr(tell(12, ": S0 S\" 1 DROP\" 0 0 SEND-ON ; S0"), "No one on that port") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && kernel_words == 1, "a message sent by that port is refused at once: \"%s\"", printed); CHECK(told(12, "1 0 ROUTE"), "node 12 is told the way to its console is the kernel's port"); CHECK(is_empty(tell(12, "65 EMIT")) && ended == 1 && ended_from == 12 && ended_how == V4_TEXT_ERROR && asleep(far), "a text that prints cannot send what it printed: it ends in an error, said by the wire the text came on, and node 12 sleeps"); CHECK(is_empty(tell(12, ": P 3000 0 DO I . LOOP ; P")) && ended == 1 && ended_how == V4_TEXT_ERROR && asleep(far) && pool[far].n.ds.depth == 0, "one that prints more than the buffer holds ends in error 18 and does not wait: %ld", (long)pool[far].n.mem[NODE_ERROR]); CHECK(told(12, "1 NO-ROUTE") && strcmp(tell(12, "7 8 * ."), "56 ") == 0 && kernel_words == 1, "told to forget that way, it prints again; the kernel was sent nothing"); } /* ---- acceptance 5: PRINTING IS NOT LOST ---- */ CHECK(triangle(), "the same made afresh"); { static char want[32768]; size_t at = 0; for (i = 0; i < 3000; i++) at += (size_t)snprintf(want + at, sizeof want - at, "%u ", i); CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "node 12 has a word that prints 3000 numbers, and node 10 one that takes a while"); forget(); saw_room_wait = 0; watch = watch_far_waits; CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run(), "node 12 prints them while node 10, by which they go to the console, is busy with one line after another"); watch = 0; CHECK(saw_room_wait > 0, "the wire to node 10 filled, and node 12 slept for room on it"); CHECK(strcmp(printed, want) == 0, "every number arrived, in order: %u characters of %u", printed_len, (unsigned)strlen(want)); CHECK(ended == 5 && pool[far].n.mem[LOST] == 0 && pool[hera].n.mem[LOST] == 0 && pool[far].n.mem[LINE_STATUS] == 1, "and then how the text ended; nothing was let go"); CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && asleep(hera) && asleep(mid) && asleep(far), "and all three go on"); } /* ---- printing held by a stuck node (MESH.md 7d.6) ---- */ CHECK(triangle(), "the same made afresh"); { v4_cell lost, refused; v4_uheat_t c; CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(12, "NO-ROUTES 2 DEFAULT-ROUTE 10 3 ROUTE") && told(10, "77 3 ROUTE : X S\" 1 DROP\" 77 SEND ;"), "node 12's way to the console is by node 11; node 10 is told the way to 77 is by node 12"); stuck = 1u << mid; (void)tell(11, ": C BEGIN 0 UNTIL ; C"); (void)tell(12, "P"); CHECK(ended == 0 && waits_for_room(far) && going_from(far, 2)->used > V4_WIRE_CELLS - 8u - 71u && going_from(far, 2)->used <= V4_WIRE_CELLS - 8u, "node 12 prints until the wire to the stuck node is full but for the room kept for its answer, and sleeps for room: %u cells", going_from(far, 2)->used); c = clock_of(far); for (i = 0; i < 500; i++) (void)v4_fabric_step(&f); CHECK(waits_for_room(far) && clock_of(far) == c, "executing nothing"); lost = pool[far].n.mem[LOST]; refused = pool[hera].n.mem[REFUSED]; /* its way for 77 is the full wire: while one more fits beside the room kept it is passed on, and * after that it is refused and node 10 told. It is never kept. */ for (i = 0; i < 12 && pool[far].n.mem[LOST] == lost; i++) { unsigned before = going_from(far, 2)->used; CHECK(is_empty(tell(10, "X")) && ended == 1 && ended_from == 10, "node 10 sends a message for node 77 by node 12"); CHECK(coming_to(far, 3)->used == 0 && waits_for_room(far), "node 12, woken, does not keep it, and sleeps for room again"); CHECK((going_from(far, 2)->used == before + 9u && pool[far].n.mem[LOST] == lost) || (going_from(far, 2)->used == before && pool[far].n.mem[LOST] == lost + 1), "it is passed on, or it is refused: %u cells then, %u now", before, going_from(far, 2)->used); } CHECK(pool[far].n.mem[LOST] == lost + 1 && pool[hera].n.mem[REFUSED] == refused + 1 && going_from(far, 2)->used <= V4_WIRE_CELLS - 8u, "when one more would take the room kept for the answer it is refused, and node 10 told: the 8 cells are still there (%u used)", going_from(far, 2)->used); CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers meanwhile"); lost = pool[far].n.mem[LOST]; CHECK(v4_fabric_remove(&f, mid) == &pool[1], "node 11 is removed"); stuck = 0; CHECK(run() && asleep(far) && pool[far].n.mem[NODE_ERROR] == 18 && pool[far].n.mem[LINE_STATUS] == 2 && pool[far].n.mem[OUT_PTR] == OUT_W, "node 12's text ends in error 18, No one on that port, and it sleeps: error %ld, ended %ld", (long)pool[far].n.mem[NODE_ERROR], (long)pool[far].n.mem[LINE_STATUS]); CHECK(pool[far].n.mem[LOST] == lost + 2, "the error's message and the word of how the text ended had no one to go to, and are counted: %ld", (long)(pool[far].n.mem[LOST] - lost)); (void)tell(12, "NO-ROUTES 3 DEFAULT-ROUTE"); CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && asleep(far) && asleep(hera), "told another way to the console, it goes on: \"%s\"", printed); } /* ---- NOTHING BREAKS A WAIT FOR ROOM TO PRINT (MESH.md 7d.4, ruled 2026-10-08) ---- * A line typed ahead while a text prints more than the wire holds does not cut the printing short. */ CHECK(triangle(), "the same made afresh"); { static char want[32768]; size_t at = 0; for (i = 0; i < 3000; i++) at += (size_t)snprintf(want + at, sizeof want - at, "%u ", i); snprintf(want + at, sizeof want - at, "56 "); CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "node 12 has a word that prints 3000 numbers, and node 10 one that takes a while"); forget(); saw_room_wait = 0; watch = watch_far_waits; CHECK(put_text(10, "SLOW") && put_text(12, "P") && put_text(12, "7 8 * .") && put_text(10, "SLOW") && put_text(10, "SLOW") && run(), "node 12 is typed two lines, the first of which prints more than the wire holds, while node 10 is busy"); watch = 0; CHECK(saw_room_wait > 0 && strstr(printed, "Interrupted") == NULL, "node 12 slept for room to print with the second line waiting on its wire, and was not interrupted"); CHECK(strcmp(printed, want) == 0 && ended == 5 && pool[far].n.mem[LOST] == 0, "the first text printed everything, and then the second line was done: %u characters of %u, %d ended", printed_len, (unsigned)strlen(want), ended); } /* and with a stuck node on the way, where the room never comes, text for the node is left on its wire */ CHECK(triangle(), "the same made afresh"); { CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(12, "NO-ROUTES 2 DEFAULT-ROUTE 10 3 ROUTE"), "node 12's way to the console is by node 11 again"); stuck = 1u << mid; (void)tell(11, ": C BEGIN 0 UNTIL ; C"); (void)tell(12, "P"); CHECK(ended == 0 && waits_for_room(far) && pool[far].n.mem[NODE_ERROR] == 0, "node 11 is stuck, and node 12 sleeps for room to print"); (void)tell(10, ": T12 S\" 1 DROP\" 12 SEND ; T12"); (void)tell(12, "65 EMIT"); CHECK(waits_for_room(far) && pool[far].n.mem[NODE_ERROR] == 0 && pool[far].n.mem[QUIET] == 0 && coming_to(far, 3)->used == 18, "text for node 12, from another node and from its console, is left on the wire: its text is not ended, and it sleeps on: error %ld, %u cells", (long)pool[far].n.mem[NODE_ERROR], coming_to(far, 3)->used); CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers meanwhile"); } /* ---- PRINTING THROUGH A MIDDLE NODE IS NOT LOST (ruled 2026-10-08) ---- * In the row, what node 12 prints goes by node 11 to node 10, which is busy. The wire from 11 to 10 * fills; node 11 does not let go what it has no room to pass on: it leaves it on the wire from 12, which * fills in its turn, and node 12 waits. */ CHECK(row(), "a row made afresh"); { static char want[32768]; size_t at = 0; for (i = 0; i < 3000; i++) at += (size_t)snprintf(want + at, sizeof want - at, "%u ", i); CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "node 12 has a word that prints 3000 numbers, and node 10 one that takes a while"); forget(); saw_room_wait = 0; saw_mid_hold = 0; watch = watch_mid_holds; CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run(), "node 12 prints them while node 10 is busy with one line after another"); watch = 0; CHECK(saw_mid_hold > 0, "node 11 left output on the wire it came by, for want of room, and slept for room"); CHECK(strcmp(printed, want) == 0, "every number arrived, in order: %u characters of %u", printed_len, (unsigned)strlen(want)); CHECK(ended == 5 && ended_at <= printed_len && pool[far].n.mem[LINE_STATUS] == 1 && pool[hera].n.mem[LOST] == 0 && pool[mid].n.mem[LOST] == 0 && pool[far].n.mem[LOST] == 0 && pool[far].n.mem[REFUSED] == 0, "and then how the text ended; nothing was let go anywhere: %ld %ld %ld", (long)pool[hera].n.mem[LOST], (long)pool[mid].n.mem[LOST], (long)pool[far].n.mem[LOST]); CHECK(asleep(hera) && asleep(mid) && asleep(far) && pool[mid].n.mem[H_PORT] == -1, "all three sleep, node 11 for a message and not for room"); /* the middle node removed while it holds output: node 12's text ends in an error; node 10 goes on */ CHECK(row() && told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "the same again"); forget(); CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run_until(mid_holds, 20000000), "node 11 is holding output, asleep for room"); CHECK(v4_fabric_remove(&f, mid) == &pool[1] && run(), "node 11 is removed, and the two that are left come to rest"); CHECK(asleep(far) && pool[far].n.mem[NODE_ERROR] == 18 && pool[far].n.mem[LINE_STATUS] == 2 && pool[far].n.mem[KEEP] == -1, "node 12's text has ended in error 18, No one on that port, and it sleeps"); CHECK(pool[far].n.ds.depth == 0, "with nothing left on its data stack of what the word that was printing had there: %u", (unsigned)pool[far].n.ds.depth); CHECK(strncmp(printed, want, printed_len) == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera), "what had arrived was in order, and node 10 goes on"); /* the node that prints removed while the middle node holds its output: what is on the wires arrives */ CHECK(row() && told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "and again"); forget(); CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run_until(mid_holds, 20000000), "node 11 is holding output again"); CHECK(v4_fabric_remove(&f, far) == &pool[2] && run(), "node 12 is removed, and the two that are left come to rest"); CHECK(asleep(mid) && asleep(hera) && pool[mid].n.mem[H_PORT] == -1 && coming_to(hera, 2)->used == 0, "node 11 sleeps, holding nothing; nothing is left on the wire to node 10"); CHECK(printed_len > 0 && strncmp(printed, want, printed_len) == 0 && ended >= 4, "what node 12 had printed before it went arrived, in order: %u characters", printed_len); CHECK(strcmp(tell(11, "20 22 + ."), "42 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and nodes 10 and 11 go on"); } /* ---- A TAKE THAT DOES NOT TAKE (fix round 1, F1) ---- * The wire is cut at every step from a message's coming to its being taken. If it was still on the wire * when the wire was cut it was not taken, and nothing may be done for it: not the text before it run * again, not a refusal counted, not a wait ended with an old answer, not a way forgotten. */ { unsigned k, cut_before = 0, cut_after = 0, wrong = 0; CHECK(row() && told(12, "VARIABLE N : T1 1 N +! ; : T2 10 N +! ;") && told(12, "T1") && put_text(12, "T2") && run_until(far_has_one, 1000000), "text has come to node 12's wire"); keep(); for (k = 0; k < 200; k++) { /* text, taken by (IDLE) */ int there; v4_cell lost; put_back(); lost = pool[far].n.mem[LOST]; for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } there = far_has_one(); (void)v4_fabric_unwire(&f, mid, 3); if (!run() || !asleep(far) || !v4_fabric_wire(&f, mid, 3, far, 2)) { wrong += 1000; break; } if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; } if (strcmp(tell(12, "N @ ."), there ? "1 " : "11 ") != 0 || pool[far].n.mem[LOST] != lost + (there ? 0 : 1)) { if (!wrong) printf(" cut %u steps after the text came, it %s on the wire: N is %s, %ld counted lost\n", k, there ? "still" : "no longer", printed, (long)(pool[far].n.mem[LOST] - lost)); wrong++; } } CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "text: cut before it was taken (%u times) nothing was done; cut after (%u), the text was done once, and its answer counted: %u wrong", cut_before, cut_after, wrong); cut_before = cut_after = wrong = 0; CHECK(row() && told(12, ": N77 S\" 1 DROP\" 77 SEND ;") && put_text(12, "N77") && run_until(far_has_nack, 1000000), "a NACK has come to node 12's wire"); keep(); for (k = 0; k < 200; k++) { /* a NACK, taken by (IDLE) */ int there; v4_cell refused; put_back(); refused = pool[far].n.mem[REFUSED]; for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } there = far_has_nack(); (void)v4_fabric_unwire(&f, mid, 3); if (!run() || !asleep(far)) { wrong += 1000; break; } if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; } if (pool[far].n.mem[REFUSED] != refused + (there ? 0 : 1)) wrong++; } CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a NACK: cut before it was taken (%u times) no refusal was counted; cut after (%u), one: %u wrong", cut_before, cut_after, wrong); cut_before = cut_after = wrong = 0; CHECK(row() && told(12, ": Q S\" 1 DROP\" 11 SEND 11 AWAIT 7 + . ;") && put_text(12, "Q") && run_until(far_awaits_and_has_one, 1000000), "an answer has come to the wire of node 12, which waits for it"); keep(); for (k = 0; k < 200; k++) { /* an answer, taken by AWAIT */ int there; put_back(); for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } there = far_has_one(); (void)v4_fabric_unwire(&f, mid, 3); if (!run() || !asleep(far)) { wrong += 1000; break; } if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; } /* not taken: it waits on. Taken: the wait ended with the answer, 1, and "8 " was printed -- to a wire with no one on it. */ if (there ? !(pool[far].n.mem[AWAIT_FROM] == 11 && pool[far].n.mem[QUIET] == 0) : !(pool[far].n.mem[AWAIT_FROM] == 0 && pool[far].n.mem[QUIET] == 1)) wrong++; } CHECK(wrong == 0 && cut_before > 5 && cut_after > 0, "an answer: cut before it was taken (%u times) the wait went on; cut after (%u), it ended: %u wrong", cut_before, cut_after, wrong); cut_before = cut_after = wrong = 0; CHECK(row() && told(12, "55 2 ROUTE 44 2 ROUTE") && told(11, "55 12 GONE") && told(12, "55 2 ROUTE") && told(11, ": G44 44 12 GONE ;") && pool[far].n.mem[ROUTE_COUNT] == 2 && put_text(11, "G44") && run_until(far_has_one, 1000000), "a GONE from its centre has come to node 12's wire"); keep(); for (k = 0; k < 200; k++) { /* a GONE, taken by (IDLE): node 11 is node 12's centre */ int there; v4_cell ways; put_back(); ways = pool[far].n.mem[ROUTE_COUNT]; for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } there = far_has_one(); (void)v4_fabric_unwire(&f, mid, 3); if (!run() || !asleep(far)) { wrong += 1000; break; } if (there) cut_before++; else { if (!cut_after && getenv("V4_MESH_STUCK")) printf(" first cut after the take: %u steps\n", k); cut_after++; } if (pool[far].n.mem[ROUTE_COUNT] != ways - (there ? 0 : 1)) wrong++; } CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a GONE: cut before it was taken (%u times) no way was forgotten; cut after (%u), one: %u wrong", cut_before, cut_after, wrong); } /* ---- ONLY WHAT WAS LOOKED AT IS TAKEN (fix round 2, NP1; MESH.md 7d.3) ---- * Between the look and the take the wire is cut, made again, and a different message put on it. */ { unsigned k, tried = 0, wrong = 0, hung = 0; static v4_message big; static char blanks[1025]; /* text was looked at; a NACK is there now. The NACK must not be done as text: it is a refusal, and counted as one. */ CHECK(row() && told(12, "VARIABLE N : T1 1 N +! ; : T2 10 N +! ;") && told(12, "T1") && put_text(12, "T2") && run_until(far_has_one, 1000000), "text has come to node 12's wire"); keep(); for (k = 0; k < 200; k++) { static const v4_cell hd[V4_WIRE_HEADER] = { 12, 11, V4_MSG_NACK, 16, 0, 0, 4 }, t[1] = { 'T' | ('2' << 8) }; v4_cell refused; put_back(); refused = pool[far].n.mem[REFUSED]; for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } if (!far_has_one()) break; if (!v4_fabric_unwire(&f, mid, 3) || !v4_fabric_wire(&f, mid, 3, far, 2) || v4_wire_put(places[far].wire[2].rx, hd, t) != V4_WIRE_DONE) { wrong += 1000; break; } tried++; if (!run()) { hung++; continue; } if (strcmp(tell(12, "N @ ."), "1 ") != 0 || pool[far].n.mem[REFUSED] != refused + 1 || pool[far].n.mem[ME] != 12) wrong++; } CHECK(tried > 100 && wrong == 0 && hung == 0, "text looked at, and a NACK there when it comes to take: at each of %u steps the NACK was counted as a refusal and nothing was done as text: %u wrong, %u hung", tried, wrong, hung); /* a NACK was looked at; a text of 1,024 characters is there now. Its 256 cells must not be taken where one was looked for. */ memset(blanks, ' ', 1024); CHECK(row() && told(12, ": N77 S\" 1 DROP\" 77 SEND ;") && put_text(12, "N77") && run_until(far_has_nack, 1000000) && v4_message_text(&big, 12, 11, V4_MSG_TEXT, blanks, 1024), "a NACK has come to node 12's wire"); keep(); tried = wrong = hung = 0; for (k = 0; k < 200; k++) { static v4_cell before[V4_NODE_WORDS]; v4_cell at; unsigned changed = 0; put_back(); for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } if (!far_has_nack()) break; memcpy(before, pool[far].n.mem, sizeof before); if (!v4_fabric_unwire(&f, mid, 3) || !v4_fabric_wire(&f, mid, 3, far, 2) || v4_wire_put(places[far].wire[2].rx, big.word, big.word + V4_MSG_HEADER) != V4_WIRE_DONE) { wrong += 1000; break; } tried++; if (!run()) { hung++; continue; } /* the cells after (ONE), as far as the variables go, are as they were: the text went to TIB, where text goes */ for (at = ONE + 1; at < BUF0_W; at++) if (at != LINE_STATUS && at != QUIET && at != DONE_PORT && at != KEEP && at != REPLY && !(at >= MSG && at < MSG + 7) && at != PRINT_TO && at != PRINT_PORT && !(at >= S_ON && at <= S_HAVE) && at != DEFER && pool[far].n.mem[at] != before[at]) changed++; if (changed || pool[far].n.mem[ME] != 12 || strcmp(tell(12, "1 2 + ."), "3 ") != 0) wrong++; } CHECK(tried > 100 && wrong == 0 && hung == 0, "a NACK looked at, and a text of 1,024 characters there when it comes to take: at each of %u steps nothing was overwritten, and node 12 answers: %u wrong, %u hung", tried, wrong, hung); } /* ---- A DROP THAT DOES NOT DROP (fix round 2, NP3) ---- * The wire is cut between the look and the drop: the cut let the message go, and nothing is counted or told for it. */ { unsigned k, cut_before = 0, cut_after = 0, wrong = 0; /* (REFUSE): node 11 has no way for what node 12 sends 99 */ mid_for = 99; CHECK(row() && told(11, "NO-ROUTES 1 2 ROUTE 10 2 ROUTE 12 3 ROUTE") && told(12, ": S99 S\" 1 DROP\" 99 SEND ;") && put_text(12, "S99") && run_until(mid_has_one, 1000000), "a message node 11 must refuse has come to its wire"); keep(); for (k = 0; k < 320; k++) { int there; v4_cell lost; put_back(); lost = pool[mid].n.mem[LOST]; for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } there = mid_has_one(); (void)v4_fabric_unwire(&f, mid, 3); if (!run() || !asleep(mid)) { wrong += 1000; break; } if (there) cut_before++; else cut_after++; /* cut after the drop the message is counted; and its NACK too, if the cut came before that was put */ if (there ? pool[mid].n.mem[LOST] != lost : !(pool[mid].n.mem[LOST] == lost + 1 || pool[mid].n.mem[LOST] == lost + 2)) wrong++; } CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "a refusal: cut before the drop (%u times) nothing was counted; cut after (%u), the message, and its NACK if it could not go: %u wrong", cut_before, cut_after, wrong); /* (IDLE): what node 12 prints comes to node 11, which is not a console: it is let go, and counted */ cut_before = cut_after = wrong = 0; mid_for = 11; CHECK(row() && told(12, "11 (CONSOLE) !") && put_text(12, "65 EMIT") && run_until(mid_has_one, 1000000), "what a node printed has come to the wire of a node that is no console"); keep(); for (k = 0; k < 320; k++) { int there; v4_cell lost; put_back(); lost = pool[mid].n.mem[LOST]; for (i = 0; i < k; i++) { (void)v4_fabric_step(&f); (void)console_takes(); } there = mid_has_one(); (void)v4_fabric_unwire(&f, mid, 3); if (!run() || !asleep(mid)) { wrong += 1000; break; } if (there) cut_before++; else cut_after++; if (pool[mid].n.mem[LOST] != lost + (there ? 0 : 1)) wrong++; } CHECK(wrong == 0 && cut_before > 20 && cut_after > 0, "what should not have come: cut before the drop (%u times) nothing was counted; cut after (%u), one: %u wrong", cut_before, cut_after, wrong); } /* ---- Review Focus 4: THE ANSWER HAS ROOM KEPT ---- * Node 11 sends the console 127 short messages by way of node 12, which passes them on to the wire to * node 10 while node 10 is busy: that wire is then full but for 8 cells. Node 10 has sent node 12 text * that prints. Node 12 begins it -- there is room for its answer -- and its printing waits for room, since * it may not take the 8 cells kept; when node 10 takes what is on the wire, the printing goes and then * the answer, which node 10 is waiting for. */ CHECK(triangle(), "the same made afresh"); { CHECK(told(11, "1 3 ROUTE : F1 0 DO S\" HIT\" 1 SEND LOOP ;") && told(10, ": WAIT 60000 0 DO LOOP ;") && told(10, ": G S\" 127 F1\" 11 SEND WAIT S\" 65 EMIT\" 12 SEND WAIT WAIT 12 AWAIT . ;"), "nodes 11 and 10 are given their words"); saw_room_wait = 0; saw_full = 0; watch = watch_far_waits; (void)tell(10, "G"); watch = 0; CHECK(saw_full > 0, "node 12 slept for room to print with the wire to node 10 holding exactly the 8 cells kept for its answer (%u, %u)", saw_room_wait, saw_full); CHECK(strcmp(printed, "A1 ") == 0 && pool[far].n.mem[LOST] == 0, "what it printed arrived, and then how the text ended, which node 10 waited for: the answer was not refused: \"%s\"", printed); CHECK(asleep(hera) && asleep(mid) && asleep(far) && pool[far].n.mem[KEEP] == -1, "all three sleep, and node 12 keeps no room"); } /* ---- Review Focus 2 and 3: LEFT FOR ITSELF ---- * Node 10 waits for node 12's answer. On the one wire, from node 11, there then come: text for node 10 * itself; a message for the console; and node 12's answer. */ CHECK(row(), "a row made afresh"); { v4_uheat_t c; CHECK(told(12, ": LL 400000 0 DO LOOP ;") && told(11, ": S1 S\" 66 EMIT\" 10 SEND ;"), "node 12 has a word that takes a long while, node 11 one that sends node 10 text"); forget(); stuck = 1u << far; /* it is busy, not stuck: it is not waited for */ CHECK(put_text(10, ": Q S\" LL\" 12 SEND 12 AWAIT . ; Q") && run() && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera) && ended == 0, "node 10 sends node 12 text that takes a long while, and waits for its answer, asleep"); CHECK(is_empty(tell(11, "S1")) && ended == 1 && ended_from == 11, "node 11 sends node 10 text, and then word of how its own text ended, for the console: that is passed on by node 10"); CHECK(coming_to(hera, 2)->used == 9 && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera), "the text for node 10 itself is left on the wire, in front, and node 10 waits on, asleep: %u cells", coming_to(hera, 2)->used); c = clock_of(hera); for (i = 0; i < 2000; i++) (void)v4_fabric_step(&f); CHECK(clock_of(hera) == c && asleep(hera), "with a message for itself on a wire it executes nothing until another comes: it does not go round"); stuck = 0; forget(); CHECK(run() && strcmp(printed, "1 B") == 0 && ended == 1 && ended_at == 2 && pool[hera].n.mem[AWAIT_FROM] == 0, "node 12's answer comes behind it and ends the wait; and then the text that was left is done: \"%s\"", printed); CHECK(asleep(hera) && asleep(mid) && asleep(far) && coming_to(hera, 2)->used == 0, "all three sleep, with nothing left on the wire"); } /* ---- acceptance 8: ORDER ---- */ CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other"); CHECK(told(11, "12 2 ROUTE") && told(12, ": SLOW 30000 0 DO LOOP ;"), "node 11 is told the way to node 12 is by node 10; node 12 is given a word that takes a while"); CHECK(strcmp(tell(11, ": TRIO S\" SLOW 68 EMIT\" 12 SEND S\" 69 EMIT\" 12 SEND S\" 70 EMIT\" 12 SEND ; TRIO"), "DEF") == 0, "it sends node 12 three texts, the first slow to do: the other two wait on the wire, and they are done in the order they were sent: \"%s\"", printed); CHECK(strcmp(tell(12, ": TWO 65 EMIT 66 EMIT NOSUCHWORD ; 67 EMIT"), "UNKNOWN WORD: 'NOSUCHWORD'\n") == 0 && ended == 1 && ended_at == printed_len, "what a text prints, and then how it ended, reach the console in that order"); /* ---- AWAIT's endings (MESH.md 7b) ---- */ CHECK(row(), "a row made afresh"); /* refused: a NACK about the node waited for */ { v4_cell refused = pool[far].n.mem[REFUSED], lost = pool[hera].n.mem[LOST]; CHECK(is_empty(tell(12, ": N77 S\" 1 DROP\" 77 SEND ; N77")) && ended_how == V4_TEXT_COMPLETED, "node 12 sends text to node 77: it has a way for everything, toward 11"); CHECK(pool[hera].n.mem[LOST] == lost + 1 && pool[far].n.mem[REFUSED] == refused + 1, "node 10, two away, has no way to 77 and counts the message; node 12 is told, and counts the refusal"); CHECK(strstr(tell(12, ": W77 S\" 1 DROP\" 77 SEND 77 AWAIT . ; W77"), "Message refused") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && ended_from == 12, "a node that sends to 77 and waits for its answer: the wait ends, with an error that says the message was refused: \"%s\"", printed); CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && pool[far].n.mem[AWAIT_FROM] == 0 && pool[far].n.mem[REFUSED] == refused + 1, "and it goes on to its next line, waiting for no one"); /* the refusal came before the wait began: it is on the wire, and is found there */ CHECK(strstr(tell(12, ": QF S\" 1 DROP\" 77 SEND 3000 0 DO LOOP 77 AWAIT . ; QF"), "Message refused") != NULL && ended_how == V4_TEXT_ERROR && pool[far].n.mem[AWAIT_FROM] == 0 && asleep(far), "node 12 sends to 77, is busy a while, and then waits: the refusal is on its wire already, and the wait ends at once: \"%s\"", printed); /* a NACK about another node does not end it, and is counted */ refused = pool[far].n.mem[REFUSED]; (void)tell(12, ": QO S\" 1 DROP\" 77 SEND 11 AWAIT . ; QO"); CHECK(ended == 0 && pool[far].n.mem[AWAIT_FROM] == 11 && pool[far].n.mem[REFUSED] == refused + 1 && asleep(far), "waiting on node 11, it is told its message for 77 was refused: that is counted, and it waits on"); } /* gone: a GONE from its centre, and not from another */ { v4_cell ways = pool[far].n.mem[ROUTE_COUNT]; CHECK(told(11, "44 3 ROUTE"), "node 11 is given a way"); ways = pool[mid].n.mem[ROUTE_COUNT]; CHECK(is_empty(tell(11, "44 AWAIT .")) && ended == 0 && pool[mid].n.mem[AWAIT_FROM] == 44, "node 11 waits for an answer from a node 44"); CHECK(is_empty(tell(10, ": G12 44 11 GONE ;")) && told(10, "12 2 ROUTE"), "(node 10 is given a word)"); /* node 12 is still waiting on 11: a line from the console ends that */ CHECK(strstr(tell(12, "1 DROP"), "Interrupted") != NULL, "(node 12's wait is ended from the console)"); CHECK(told(12, "44 11 GONE") && pool[mid].n.mem[AWAIT_FROM] == 44 && pool[mid].n.mem[ROUTE_COUNT] == ways, "node 12 tells it that 44 is gone: node 12 is not its centre, and it goes on waiting, its ways as they were"); CHECK(strstr(tell(10, "G12"), "Node gone") != NULL && printed_from == 11 && pool[mid].n.mem[AWAIT_FROM] == 0 && pool[mid].n.mem[ROUTE_COUNT] == ways - 1, "node 10, its centre, tells it: the wait ends with an error that says so, and the way is forgotten: \"%s\"", printed); CHECK(asleep(mid) && coming_to(mid, 3)->used == 0 && coming_to(mid, 2)->used == 0, "it sleeps; the GONE it did not believe was not kept"); (void)tell(11, "33 AWAIT ."); CHECK(ended == 0 && told(10, "44 11 GONE") && pool[mid].n.mem[AWAIT_FROM] == 33 && asleep(mid), "a GONE from its centre about another node does not end a wait"); } /* a GONE that says it is from the centre but comes by another port is not believed (MESH.md 7b.4) */ CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other"); { v4_cell ways; CHECK(told(11, "44 3 ROUTE") && told(12, ": FORGE 10 (ME) ! 44 11 GONE 12 (ME) ! ;"), "node 11 has a way to 44; node 12 has a word that sends a GONE saying it is from node 10"); ways = pool[mid].n.mem[ROUTE_COUNT]; CHECK(told(12, "FORGE") && asleep(mid) && coming_to(mid, 3)->used == 0 && pool[mid].n.mem[ROUTE_COUNT] == ways, "it comes to node 11 by the wire from node 12, not by its centre's: it is not believed, and the way stays: %ld ways of %ld", (long)pool[mid].n.mem[ROUTE_COUNT], (long)ways); CHECK(told(10, "44 11 GONE") && pool[mid].n.mem[ROUTE_COUNT] == ways - 1, "from node 10, by its centre's port, it is"); } /* interrupted: text from the console */ CHECK(row(), "a row made afresh"); { const char *r; CHECK(is_empty(tell(10, "12 AWAIT .")) && ended == 0 && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera), "node 10 waits for an answer from 12, which is not going to send one"); CHECK(strcmp(tell(11, "20 22 + ."), "42 ") == 0 && ended == 1 && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera), "text from the console for node 11 comes to node 10 on its way: node 10 passes it on, and what comes back, and goes on waiting"); r = tell(10, "65 EMIT"); CHECK(strcmp(r, "Interrupted\nA") == 0 && ended == 2 && pool[hera].n.mem[AWAIT_FROM] == 0, "text from the console for node 10 itself ends its wait, with an error that says so, and is then done: \"%s\"", r); CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera) && asleep(mid) && asleep(far), "and all three go on as before"); CHECK(strstr(tell(10, "5 NO-ROUTE"), "ERROR") == NULL && strcmp(tell(11, "20 22 + ."), "42 ") == 0, "forgetting the way to a node there was no way to changes nothing"); } /* ---- an answer refused on its way back; a message that would go round for ever ---- */ CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other"); { v4_cell refused, l10, l11, r12; /* a message that would go round for ever is refused after 16 nodes have passed it on */ refused = pool[hera].n.mem[REFUSED]; l10 = pool[hera].n.mem[LOST]; l11 = pool[mid].n.mem[LOST]; CHECK(told(10, "66 2 ROUTE"), "node 10 is told the way to 66 is by node 11, whose way for everything it has not been told of is back by node 10"); CHECK(is_empty(tell(10, ": H66 S\" 1 DROP\" 66 SEND ; H66")) && ended == 1 && ended_how == V4_TEXT_COMPLETED, "node 10 sends 66 a message: it comes to rest"); CHECK(pool[hera].n.mem[LOST] + pool[mid].n.mem[LOST] == l10 + l11 + 1 && pool[hera].n.mem[REFUSED] == refused + 1, "one of the two let it go when it had been passed on as often as a message may be, and node 10 was told it was refused"); nacks = 0; CHECK(is_empty(tell(66, "1 DROP")) && nacks == 1 && nack_about == 66, "the same from the console, which sends it with no count: it too is let go, and the console told"); CHECK(strstr(tell(10, ": W66 S\" 1 DROP\" 66 SEND 66 AWAIT . ; W66"), "Message refused") != NULL && ended_how == V4_TEXT_ERROR && pool[hera].n.mem[AWAIT_FROM] == 0, "and waiting for 66's answer, node 10 is the one that lets its own message go when it comes back the last time: its wait ends, the message refused: \"%s\"", printed); CHECK(told(10, "66 NO-ROUTE") && asleep(hera) && asleep(mid) && asleep(far), "the way is forgotten, and all three are at rest"); /* An answer that cannot be passed on: the NACK is for the node the answer was for, not the node that * answered. Here it cannot go either -- it has the answer's own way to go -- and whoever waits is * not told: MESH.md 7d.6. That wait is ended from the console. */ r12 = pool[far].n.mem[REFUSED]; l11 = pool[mid].n.mem[LOST]; CHECK(told(12, "10 2 ROUTE") && told(11, "10 4 ROUTE"), "node 12 is told the way to node 10 is by node 11, which is told it is by a port with no one on it"); (void)tell(10, ": RA S\" 1 DROP\" 12 SEND 12 AWAIT . ; RA"); CHECK(ended == 0 && pool[hera].n.mem[AWAIT_FROM] == 12 && pool[mid].n.mem[LOST] == l11 + 2, "node 10 sends node 12 text and waits; the answer comes back by node 11, which cannot pass it on, nor the NACK for it: both are counted: %ld", (long)(pool[mid].n.mem[LOST] - l11)); CHECK(pool[far].n.mem[REFUSED] == r12 && asleep(far), "node 12, which answered, is told nothing: the NACK was not for it"); CHECK(strcmp(tell(10, "1 2 + ."), "Interrupted\n3 ") == 0 && asleep(hera), "node 10's wait is ended from the console: \"%s\"", printed); } /* ---- the stacks, between texts (MESH.md 7c.6, 7d.4) ---- * After a text that left nothing on the data stack, what a node's stacks hold while it does nothing that * is a text's is what the nucleus uses: passing on, refusing, taking in, and sending what a text printed and * how it ended. It is looked at once an instruction word, so it is the least that was used; that 28 * values may wait is measured in test_host_quit.c and, for passing on and refusing, in test_host_depth.c. * The return stack is empty when a text ends; (FINISH)'s wait for room goes seven deep on it. */ CHECK(stale_out == 0, "no message of what a node printed carried anything past its last character: %u did", stale_out); 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 WORD WRITTEN TO A PORT, PORT!, HOLDS NO ONE (found by the final review of step 6e) ---- * A node's neighbours speak whole messages and are asleep at their wires; so is its console. A word * written to one of them will never be read. The line that wrote it ends in an error and the node goes * on; before, it was blocked for ever, and every node beyond it was cut off. */ CHECK(row(), "a row made afresh"); step_cap = 2000000; CHECK(strstr(tell(10, "5 2 PORT! 65 EMIT"), "No one on that port") != NULL && strchr(printed, 'A') == NULL && ended == 1 && ended_how == V4_TEXT_ERROR && asleep(hera), "node 10 writes a word to node 11's port: its line ends No one on that port: \"%s\"", printed); CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && strcmp(tell(11, "20 22 + ."), "42 ") == 0 && strcmp(tell(12, "7 8 * ."), "56 ") == 0, "and all three answer"); CHECK(strstr(tell(10, "5 1 PORT!"), "No one on that port") != NULL && ended_how == V4_TEXT_ERROR && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "the same for a word written to the console's port: \"%s\"", printed); CHECK(strstr(tell(11, "5 3 PORT!"), "No one on that port") != NULL && ended_from == 11 && strcmp(tell(12, "7 8 * ."), "56 ") == 0 && strcmp(tell(11, "20 22 + ."), "42 ") == 0, "and for node 11 writing to node 12's: node 12 is not cut off"); stuck = 1u << mid; (void)tell(11, ": C BEGIN 0 UNTIL ; C"); CHECK(ended == 0 && !asleep(mid), "node 11 is stuck in a loop: it does not sleep"); CHECK(strstr(tell(10, "5 2 PORT!"), "No one on that port") != NULL && ended_how == V4_TEXT_ERROR && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "a word node 10 writes to it is refused all the same, and node 10 can be typed to: \"%s\"", printed); stuck = 0; step_cap = 20000000; /* ---- TWO TEXTS FROM ONE NODE ARE BEGUN IN THE ORDER THEY CAME (acceptance 8; found by the final review) ---- * Node 12 has two texts from node 10 on its wire, and the wire their answers go by has 7 cells free: * neither can be begun. Node 11, which was not taking, then takes a message off that wire -- at every * step in turn, so that the room comes before node 12 looks at the first text, between its look at the * first and at the second, and after both. Whenever it comes, the first is begun first. */ { static v4_cell big[V4_WIRE_HEADER + V4_MSG_MAX_CHARS / 4u], hd[V4_WIRE_HEADER]; static v4_message m; unsigned k, wrong = 0, first_wrong = 0, undone = 0; v4_wire_queue *out, *in; CHECK(row() && told(12, ": X1 65 EMIT ; : X2 66 EMIT ;"), "a row made afresh; node 12 has two words that print"); v4_fabric_sleep(&f, mid); /* node 11 takes nothing for now */ out = places[far].wire[2].tx; in = places[far].wire[2].rx; memset(big, 0, sizeof big); big[0] = 1; big[1] = 12; big[2] = V4_MSG_OUTPUT; big[3] = 16; big[6] = 1024; for (i = 0; i < 3; i++) (void)v4_wire_put(out, big, big + V4_WIRE_HEADER); big[6] = 884; (void)v4_wire_put(out, big, big + V4_WIRE_HEADER); CHECK(V4_WIRE_CELLS - out->used == 7, "the wire from node 12 to node 11 has 7 cells free: %u", V4_WIRE_CELLS - out->used); CHECK(v4_message_text(&m, 12, 10, V4_MSG_TEXT, "X1", 2) && (m.word[3] = 16, m.word[5] = 1, v4_wire_put(in, m.word, m.word + V4_MSG_HEADER) == V4_WIRE_DONE) && v4_message_text(&m, 12, 10, V4_MSG_TEXT, "X2", 2) && (m.word[3] = 16, m.word[5] = 2, v4_wire_put(in, m.word, m.word + V4_MSG_HEADER) == V4_WIRE_DONE), "two texts from node 10 are on the wire to node 12, numbered 1 and 2"); keep(); for (k = 0; k < 700; k++) { unsigned began = 0, seq[2] = { 0, 0 }; put_back(); for (i = 0; i < 100000 && began < 2; i++) { const v4_node *n = &pool[far].n; int kept = n->mem[KEEP] >= 0; if (i == k) { v4_wire_first(out); (void)v4_wire_look(out, hd); (void)v4_wire_drop(out); } (void)v4_fabric_step(&f); (void)console_takes(); if (!kept && n->mem[KEEP] >= 0) seq[began++] = (unsigned)n->mem[MSG + 5]; } if (began < 2) undone++; else if (seq[0] != 1 || seq[1] != 2) { if (!wrong) first_wrong = k; wrong++; } } CHECK(undone == 0 && wrong == 0, "whenever the room came, both texts were done, the first first: %u not done, %u in the wrong order, the first when it came at step %u", undone, wrong, first_wrong); } /* ---- A NODE REMOVED, OR A WIRE CUT, 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, again with node 12 removed, and again with the wire between them cut and made again: the * nodes left come to rest, blocked at 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. * WHAT "ANSWERS" MEANS HERE. A node that was waiting for an answer from a node now removed, or for one * that was on a wire now cut, is still waiting when the mesh comes to rest: removing a node tells no one * (Hera's KILL does: test_host_unit.c). Its wait is ended by the line it is then typed, "Interrupted", * and it answers. */ { 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" } }; static const char *const how_name[4] = { "", "node 11 removed", "node 12 removed", "the wire between nodes 11 and 12 cut and made again" }; unsigned e, g, whole, tried, wrong, first, all = 0; 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(); start_node = ex[e].node; start_text = ex[e].text; for (g = 1; g <= 3; g++) { whole = disturbed_at_every_step(start_plain, g, 0, &tried, &wrong, &first); all += tried; CHECK(whole > 0 && wrong == 0, "%s, in %u steps: with %s at each of them the rest come to rest and answer: %u did not, the first at step %u", ex[e].what, whole, how_name[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"); /* and with what node 12 prints held at node 11, which sleeps for room: at every step at which * anything on a wire, or what a node asks of one, has changed */ CHECK(row() && told(12, ": A 7 8 * . ;") && told(10, ": SLOW 3000 0 DO LOOP ;") && told(10, ASK) && told(11, ASK) && told(12, ASK), "a row made afresh for output held at a full wire"); step_cap = 2000000; keep(); for (g = 1; g <= 3; g++) { whole = disturbed_at_every_step(start_held, g, 1, &tried, &wrong, &first); all += tried; CHECK(whole > 0 && saw_mid_hold > 0, "text for node 12 that prints, node 10 busy and the wire to it full: node 11 held what was printed, asleep for room, for %u of %u steps", saw_mid_hold, whole); CHECK(tried > 20 && wrong == 0, "with %s at each of the %u steps at which a wire or a node's asking changed, the rest come to rest and answer: %u did not, the first at step %u", how_name[g], tried, wrong, first); } printf(" a node removed or a wire cut: %u times in all\n", all); step_cap = 20000000; } printf(" %d checks, %d failures\n", checks, failures); return failures != 0; }