Files
LithosAnanake/v4/tests/test_fabric.c
T
rajamesandClaude Opus 5.5 2cf37aab4f fix(v4.0.0): step 6d after its review -- no error in the middle of the messages waiting, no offer left standing
Ruled 2026-10-08: A, B, C, D and the guard for a half message.  Each with
a scene in test_host_mesh.c that failed first.

A. The words of a message after its first are written from the passes
   over the messages waiting by (!W): an offer and a wait for that offer
   only (a new engine address).  A reader removed in mid-message makes no
   error; the rest is let go and counted.  Before, the error left the
   messages waiting in pieces and the node going round for ever.
B. A store to the wait withdraws every offer; (GATE1) and (PAY-SET) do it
   first.  Before, text begun after a node went round without sleeping
   could go to the port of an older offer: text for one node was done by
   another.
C. What a finished text printed is put with the messages waiting, as how
   it ended is.  With no room either is let go and counted; neither is
   begun as a message from (FINISH) any more, where a stuck node on the
   way held the whole node.
D. Only what a sender is owed from an earlier text -- its output, how it
   ended -- holds that sender's next text back; a GONE does not; and what
   cannot be noted holds nothing.  A node's own messages keep the order
   they were made in.
-  A message half taken in when its writer is removed is let go
   ((MQ-MEND)), not kept as if whole.
-  Smaller: a refused line no longer ends a waiting text 'Interrupted';
   a node's own queued messages may be passed on by 16 nodes, not 15; no
   value is left on the stack when a port goes in mid-refusal.

make -C v4 test and sanitize at both widths, hosted-check.

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

551 lines
32 KiB
C

/* test_fabric.c -- nodes that talk: ports that block on a read as well as a
* write, a node born empty that executes what arrives at its port, and the
* fabric of nodes and wiring that changes while they run.
* docs/v4.0.0/MESH.md section 4 and step 1.
*
* At the level of opcodes: no word of any vocabulary is defined here.
*/
#include "v4/asm.h"
#include "v4/capsule.h"
#include "v4/fabric.h"
#include <stdio.h>
#include <string.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)
/* The memory map is open (D-4); the test chooses where the ports are. */
#define PB ((v4_cell)(V4_NODE_WORDS - 32u)) /* port 0: the ports and what follows them end below the top of memory */
#define ANY (PB + (v4_cell)V4_PORT_ANY)
#define ORG ((v4_cell)16) /* where a test's programme starts */
#define OUT ((v4_cell)900) /* where it leaves what it found */
#define NODES 8
static v4_fabric_node pool[NODES];
static v4_place places[4], more_places[NODES];
static v4_fabric f;
static v4_asm as;
/* the node with that number in the fabric */
#define ND(id) (v4_fabric_node_at(&f, (id)))
#define O(name) v4_asm_op(&as, V4_OP_##name)
#define LIT(v) v4_asm_lit(&as, (v4_cell)(v))
/* A node with a programme already in it, started at ORG: for the tests that
* are about talking and not about being born. */
static unsigned loaded(unsigned k)
{
int id = v4_fabric_add(&f, &pool[k], PB);
pool[k].n.p = ORG;
v4_asm_begin(&as, &pool[k].n, ORG);
return (unsigned)id;
}
/* the programme ends by waiting at a port nothing is wired to */
static void wait_for_ever(void) { LIT(PB + 7); O(BANG_B); O(FETCH_B); (void)v4_asm_label(&as); } /* and the last word is written out */
/* ---- the wait (MESH.md 7c) ---- */
static void fresh(void)
{
unsigned k;
v4_fabric_init(&f, places, 4);
for (k = 0; k < 4; k++) v4_node_reset(&pool[k].n);
}
/* offer `word` on port k */
static void offer(unsigned k, v4_cell word) { LIT(word); LIT(PB + (v4_cell)V4_PORT_OFFER + (v4_cell)k); O(BANG_A); O(STORE_A); }
/* the wait: what it gives goes to `at`, and which port to `at + 1` */
static void wait_into(v4_cell at)
{
LIT(PB + (v4_cell)V4_PORT_WAIT); O(BANG_B); O(FETCH_B); LIT(at); O(BANG_A); O(STORE_A);
LIT(PB + (v4_cell)V4_PORTS + 1); O(BANG_B); O(FETCH_B); LIT(at + 1); O(BANG_A); O(STORE_A);
}
/* the wait for an offer only */
static void wait1_into(v4_cell at)
{
LIT(PB + (v4_cell)V4_PORT_WAIT1); O(BANG_B); O(FETCH_B); LIT(at); O(BANG_A); O(STORE_A);
LIT(PB + (v4_cell)V4_PORTS + 1); O(BANG_B); O(FETCH_B); LIT(at + 1); O(BANG_A); O(STORE_A);
}
static void done_mark(void) { LIT(1); LIT(OUT + 2); O(BANG_A); O(STORE_A); }
static void count_for_ever(void)
{
v4_cell again;
LIT(OUT); O(BANG_A);
again = v4_asm_label(&as);
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
v4_asm_branch(&as, V4_OP_JUMP, again);
}
/* Let time pass until nothing more can happen, or `max` steps. */
static unsigned settle(unsigned max)
{
unsigned steps = 0;
while (steps < max && v4_fabric_step(&f) != 0) steps++;
return steps;
}
/* ---- a device: a neighbour that is not a node ---- */
typedef struct { const v4_cell *words; unsigned count, at; v4_cell took[16]; unsigned took_n; } stream;
static int stream_give(void *self, v4_cell *value)
{
stream *s = (stream *)self;
if (s->at >= s->count) return 0;
*value = s->words[s->at++];
return 1;
}
static int stream_take(void *self, v4_cell value)
{
stream *s = (stream *)self;
if (s->took_n >= 16) return 0;
s->took[s->took_n++] = value;
return 1;
}
/* for v4_capsule_write: each word of the capsule, into the stream's own list */
static void collect(void *self, v4_cell word)
{
stream *s = (stream *)self;
((v4_cell *)s->words)[s->count++] = word;
}
/* What a neighbour sends an empty node to put `count` words at `addr` and
* start there (MESH.md section 5): for the F18, and for this node, code a
* node executes straight from its port. */
static unsigned capsule(v4_cell *out, const v4_cell *words, unsigned count, v4_cell addr)
{
static const unsigned set_up[V4_SLOT_COUNT] = { V4_OP_FETCH_P, V4_OP_BANG_A, V4_OP_FETCH_P, V4_OP_PUSH, V4_OP_NOP, V4_OP_NOP };
static const unsigned pump[V4_SLOT_COUNT] = { V4_OP_FETCH_P, V4_OP_STORE_INC, V4_OP_UNEXT, V4_OP_NOP, V4_OP_NOP, V4_OP_NOP };
static v4_node scratch;
v4_asm j;
unsigned at = 0, i;
out[at++] = (v4_cell)v4_iword_assemble(set_up, 0); /* @p a! @p push */
out[at++] = addr;
out[at++] = (v4_cell)(count - 1);
out[at++] = (v4_cell)v4_iword_assemble(pump, 0); /* @p !+ unext */
for (i = 0; i < count; i++) out[at++] = words[i];
v4_node_reset(&scratch);
v4_asm_begin(&j, &scratch, 0);
v4_asm_branch(&j, V4_OP_JUMP, addr); /* jump addr */
(void)v4_asm_label(&j);
out[at++] = scratch.mem[0];
return at;
}
int main(void)
{
unsigned a, b, c, i, steps;
printf("v4 fabric tests: V4_CELL_BITS=%d, %u ports to a node\n", V4_CELL_BITS, (unsigned)V4_PORTS);
/* ---- a node newly born ---- */
v4_fabric_init(&f, places, 4);
CHECK(v4_fabric_node_at(&f, 0) == 0 && v4_fabric_step(&f) == 0, "an empty fabric: no nodes, and nothing happens");
a = (unsigned)v4_fabric_add(&f, &pool[0], PB);
CHECK(a == 0 && v4_fabric_node_at(&f, 0) == &pool[0], "a node is born and has a number");
CHECK(pool[0].n.p == ANY && pool[0].n.ds.depth == 0 && pool[0].n.rs.depth == 0, "its P is at \"any port\" and its stacks are empty");
for (i = 0; i < V4_NODE_WORDS && pool[0].n.mem[i] == 0; i++) { }
CHECK(i == V4_NODE_WORDS, "and there is nothing in its memory");
CHECK(v4_fabric_step(&f) == 0 && pool[0].n.reading && pool[0].n.read_port == V4_PORT_ANY, "it is blocked reading its ports");
CHECK(v4_fabric_step(&f) == 0 && pool[0].es.anticlock == 0, "and executes nothing while it waits: its anti-clock has not moved");
CHECK(v4_fabric_remove(&f, a) == &pool[0] && v4_fabric_node_at(&f, 0) == 0, "it is removed, and its storage comes back");
/* ---- two nodes exchange words ---- */
v4_fabric_init(&f, places, 4);
a = loaded(0); /* writes three words, then reads one */
LIT(PB + 2); O(BANG_B); LIT(10); O(STORE_B); LIT(20); O(STORE_B); LIT(12); O(STORE_B);
O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A);
wait_for_ever();
CHECK(v4_asm_ok(&as), "the writer assembles");
b = loaded(1); /* reads three, writes their sum back */
LIT(PB + 5); O(BANG_B); O(FETCH_B); O(FETCH_B); O(FETCH_B); O(ADD); O(ADD); O(STORE_B);
wait_for_ever();
CHECK(v4_asm_ok(&as), "the reader assembles");
steps = settle(1000);
CHECK(ND(a)->n.asking && ND(a)->n.ask_port == 2 && ND(a)->n.request == 10, "with no wire, the writer is blocked at its first write");
CHECK(ND(b)->n.reading && ND(b)->n.read_port == 5, "and the reader at its first read");
CHECK(v4_fabric_wire(&f, a, 2, b, 5), "port 2 of one is wired to port 5 of the other, while they wait");
steps = settle(1000);
CHECK(steps < 1000, "then everything that can happen happens, and stops: %u steps", steps);
CHECK(ND(a)->n.mem[OUT] == 42, "three words went one way and their sum came back: %ld", (long)ND(a)->n.mem[OUT]);
CHECK(ND(a)->n.reading && ND(b)->n.reading, "and both are waiting again");
{
v4_uheat_t ca = ND(a)->es.anticlock, cb = ND(b)->es.anticlock;
for (i = 0; i < 20; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->es.anticlock == ca && ND(b)->es.anticlock == cb, "a node that is waiting executes nothing, however long");
}
/* ---- an empty node is filled through its port, and runs what it was sent ---- */
{
static v4_node src;
static v4_cell prog[64], sent[96];
static stream s;
static v4_device dev;
unsigned len, count;
v4_node_reset(&src); /* the programme, assembled somewhere to be sent */
v4_asm_begin(&as, &src, ORG);
LIT(77); LIT(OUT); O(BANG_A); O(STORE_A);
LIT(PB + 1); O(BANG_B); LIT(99); O(STORE_B);
wait_for_ever();
count = (unsigned)(v4_asm_label(&as) - ORG);
CHECK(v4_asm_ok(&as) && count < 64, "a programme to send: %u words", count);
for (i = 0; i < count; i++) prog[i] = src.mem[ORG + i];
len = capsule(sent, prog, count, ORG);
/* sent by a device */
v4_fabric_init(&f, places, 4);
memset(&s, 0, sizeof s);
s.words = sent; s.count = len;
dev.give = stream_give; dev.take = stream_take; dev.self = &s;
b = (unsigned)v4_fabric_add(&f, &pool[2], PB);
CHECK(v4_fabric_wire_device(&f, b, 3, &dev), "a device is wired to port 3 of a node newly born");
steps = settle(5000);
CHECK(s.at == len, "the node took every word it was sent: %u of %u", s.at, len);
for (i = 0; i < count && ND(b)->n.mem[ORG + i] == prog[i]; i++) { }
CHECK(i == count, "the programme is in its memory, where it was sent to");
CHECK(ND(b)->n.mem[OUT] == 77, "and it ran: it left its mark");
CHECK(ND(b)->n.asking && ND(b)->n.ask_port == 1 && ND(b)->n.request == 99, "and is now writing to its port 1, which nothing is wired to");
CHECK(v4_fabric_wire_device(&f, b, 1, &dev) && settle(100) < 100 && s.took_n == 1 && s.took[0] == 99,
"a device wired there takes what it wrote");
/* sent by another node, which holds it in its own memory */
v4_fabric_init(&f, places, 4);
a = loaded(0);
for (i = 0; i < len; i++) ND(a)->n.mem[200 + i] = sent[i];
LIT(200); O(BANG_A); LIT(len - 1); O(PUSH); LIT(PB + 4); O(BANG_B);
(void)v4_asm_label(&as);
O(FETCH_INC); O(STORE_B); O(UNEXT); /* @+ !b unext: each write blocks in the middle of the word */
wait_for_ever();
CHECK(v4_asm_ok(&as), "a node that sends what is in its memory");
b = (unsigned)v4_fabric_add(&f, &pool[2], PB);
CHECK(v4_fabric_wire(&f, a, 4, b, 0), "is wired to a node newly born");
steps = settle(5000);
CHECK(ND(b)->n.mem[OUT] == 77 && ND(b)->n.asking && ND(b)->n.request == 99, "which takes it in and runs it, as from the device");
CHECK(ND(b)->n.last_from == 0, "it was listening to any port, and knows which it heard from: %u", ND(b)->n.last_from);
}
/* ---- a capsule of F18 code: any node's memory, sent to an empty node (capsule.h) ---- */
{
static v4_node src;
static v4_cell sent[400];
static stream s;
static v4_device dev;
unsigned k;
v4_node_reset(&src);
v4_asm_begin(&as, &src, 40); /* a programme, not at the start of memory */
LIT(123); LIT(OUT); O(BANG_A); O(STORE_A);
wait_for_ever();
CHECK(v4_asm_ok(&as), "a programme for the capsule");
src.mem[3] = 5; /* and words here and there: the first of memory, */
src.mem[200] = -1; src.mem[202] = 7; /* two with one zero between them, */
src.mem[300] = 9; src.mem[310] = 11; /* two with many, */
src.mem[PB - 1] = 13; /* and the last before the ports */
memset(&s, 0, sizeof s);
s.words = sent;
dev.give = stream_give; dev.take = stream_take; dev.self = &s;
s.count = 0;
s.count = v4_capsule_write(&src, 40, collect, &s);
CHECK(s.count > 0 && s.count < 400, "the capsule is %u words for a memory of %u", s.count, (unsigned)V4_NODE_WORDS);
s.at = 0;
v4_fabric_init(&f, places, 4);
b = (unsigned)v4_fabric_add(&f, &pool[0], PB);
CHECK(v4_fabric_wire_device(&f, b, 5, &dev), "it is sent to a node newly born");
steps = settle(5000);
CHECK(s.at == s.count, "which takes all of it: %u of %u", s.at, s.count);
for (k = 0; k < V4_NODE_WORDS && ND(b)->n.mem[k] == (k == (unsigned)OUT ? 123 : src.mem[k]); k++) { }
CHECK(k == V4_NODE_WORDS, "its memory is then the same, word for word (first difference at %u)", k);
CHECK(ND(b)->n.mem[OUT] == 123 && ND(b)->n.reading, "and it was started where the capsule says, and ran");
}
/* ---- a word is passed on by a node in between ---- */
v4_fabric_init(&f, places, 4);
a = loaded(0);
LIT(PB + 0); O(BANG_B); LIT(7); O(STORE_B); LIT(8); O(STORE_B); LIT(9); O(STORE_B);
wait_for_ever();
b = loaded(1); /* whatever comes on any port goes out on port 6 */
LIT(ANY); O(BANG_A); LIT(PB + 6); O(BANG_B);
{
v4_cell again = v4_asm_label(&as);
O(FETCH_A); O(STORE_B);
v4_asm_branch(&as, V4_OP_JUMP, again);
}
c = loaded(2);
LIT(PB + 1); O(BANG_B); LIT(OUT); O(BANG_A); O(FETCH_B); O(STORE_INC); O(FETCH_B); O(STORE_INC); O(FETCH_B); O(STORE_INC);
wait_for_ever();
CHECK(v4_asm_ok(&as), "three nodes in a row assemble");
CHECK(v4_fabric_wire(&f, a, 0, b, 2) && v4_fabric_wire(&f, b, 6, c, 1), "the first is wired to the second, the second to the third");
steps = settle(2000);
CHECK(ND(c)->n.mem[OUT] == 7 && ND(c)->n.mem[OUT + 1] == 8 && ND(c)->n.mem[OUT + 2] == 9,
"what the first wrote reached the third, in order, through the one in between");
CHECK(ND(b)->n.last_from == 2, "the one in between heard it on its port 2");
/* ---- the wiring is changed while they run ---- */
v4_fabric_init(&f, places, 4);
a = loaded(0); /* writes 1 2 3 4 ... to its port 0 for ever */
LIT(PB + 0); O(BANG_B); LIT(0);
{
v4_cell again = v4_asm_label(&as);
LIT(1); O(ADD); O(DUP); O(STORE_B);
v4_asm_branch(&as, V4_OP_JUMP, again);
}
b = loaded(1); LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
c = loaded(2); LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
CHECK(v4_asm_ok(&as), "a writer and two readers assemble");
CHECK(v4_fabric_wire(&f, a, 0, b, 3), "the writer is wired to the first reader");
(void)settle(500);
CHECK(ND(b)->n.mem[OUT] == 1 && ND(c)->n.mem[OUT] == 0, "who gets its first word");
CHECK(v4_fabric_wire(&f, a, 0, c, 3), "the same port is wired to the second reader instead");
(void)settle(500);
CHECK(ND(c)->n.mem[OUT] == 2, "who gets its next: %ld", (long)ND(c)->n.mem[OUT]);
CHECK(ND(a)->n.asking && ND(a)->n.request == 3, "and the writer waits with its third");
CHECK(v4_fabric_unwire(&f, a, 0) && settle(100) == 0 && ND(a)->n.asking, "with the wire cut it goes on waiting");
v4_fabric_gone_error(&f, 18);
CHECK(settle(100) == 0 && ND(a)->n.asking && ND(a)->n.faults == 0, "and still does when the fabric has an error for it: a bare node cannot take one");
v4_node_error_attach(&ND(a)->n, OUT + 20);
v4_node_fault_attach(&ND(a)->n, 900);
CHECK(v4_fabric_step(&f) == 1 && !ND(a)->n.asking && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 18
&& ND(a)->n.p == 900 + (v4_cell)V4_FAULT_RAISED && ND(a)->n.mem[OUT + 20] == 18,
"a node that can take an error is let go, and the error is raised on it: its P is its handler");
CHECK(!v4_node_port_gone(&ND(c)->n, 18), "a node that is not blocked at a port is not touched");
/* ---- asleep, awake, and removed, while looping; and a node born while others run ---- */
v4_fabric_init(&f, places, 4);
a = loaded(0); /* counts in OUT for ever */
LIT(OUT); O(BANG_A);
{
v4_cell again = v4_asm_label(&as);
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
v4_asm_branch(&as, V4_OP_JUMP, again);
}
b = loaded(1); /* and so does this one */
LIT(OUT); O(BANG_A);
{
v4_cell again = v4_asm_label(&as);
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
v4_asm_branch(&as, V4_OP_JUMP, again);
}
CHECK(v4_asm_ok(&as), "two nodes that never stop assemble");
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
{
v4_cell ca = ND(a)->n.mem[OUT], cb = ND(b)->n.mem[OUT];
v4_uheat_t clock = ND(a)->es.anticlock;
CHECK(ca > 0 && ca == cb, "each has had as much time as the other: %ld and %ld", (long)ca, (long)cb);
v4_fabric_sleep(&f, a);
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.mem[OUT] == ca && ND(a)->es.anticlock == clock, "put to sleep in the middle of its loop, one executes nothing");
CHECK(ND(b)->n.mem[OUT] == 2 * cb, "and the other goes on as before: %ld", (long)ND(b)->n.mem[OUT]);
v4_fabric_wake(&f, a);
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.mem[OUT] == 2 * ca, "woken, it goes on from where it was: %ld", (long)ND(a)->n.mem[OUT]);
CHECK(v4_fabric_remove(&f, a) != 0 && v4_fabric_node_at(&f, a) == 0, "it is removed while it loops");
cb = ND(b)->n.mem[OUT];
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(ND(b)->n.mem[OUT] > cb, "and the other goes on");
c = (unsigned)v4_fabric_add(&f, &pool[3], PB);
CHECK(c == a && pool[3].n.p == ANY, "a node is born while the other runs, into the place that was freed");
for (i = 0; i < 10; i++) (void)v4_fabric_step(&f);
CHECK(pool[3].n.reading && pool[3].es.anticlock == 0, "and waits, empty, while the other runs");
}
/* ---- more room, while they run ---- */
v4_fabric_init(&f, places, 4);
a = loaded(0); LIT(PB + 0); O(BANG_B); LIT(5); O(STORE_B); wait_for_ever();
b = loaded(1); LIT(PB + 0); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
(void)loaded(2); wait_for_ever();
(void)loaded(3); wait_for_ever();
CHECK(v4_fabric_add(&f, &pool[4], PB) == -1, "a fabric with no room left takes no more nodes");
CHECK(v4_fabric_wire(&f, a, 0, b, 0), "two of its nodes are wired");
v4_fabric_grow(&f, more_places, NODES);
CHECK(v4_fabric_add(&f, &pool[4], PB) == 4, "given more room, it takes another");
(void)settle(500);
CHECK(ND(b)->n.mem[OUT] == 5, "and the nodes that were there are wired as they were");
/* ---- what cannot be wired ---- */
CHECK(!v4_fabric_wire(&f, a, V4_PORTS, b, 0) && !v4_fabric_wire(&f, a, 0, 7, 0) && !v4_fabric_wire(&f, a, 1, a, 1),
"a port that is not there, a node that is not there, a port to itself: refused");
CHECK(v4_fabric_wire(&f, a, 1, a, 2), "but one port of a node may be wired to another of the same node");
/* ---- a node looks at its neighbours before it writes (MESH.md 7a) ---- */
v4_fabric_init(&f, places, 4);
a = loaded(0); LIT(PB + 0); O(BANG_B); LIT(5); O(STORE_B); wait_for_ever(); /* writes to its port 0 */
c = loaded(1); LIT(PB + 1); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever(); /* reads its port 1 */
for (i = 0; i < 10; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.asking && ND(c)->n.reading, "one node is waiting to write and another to read");
b = loaded(2);
LIT(PB + (v4_cell)V4_PORTS + 2); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A);
LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); LIT(OUT + 1); O(BANG_A); O(STORE_A);
wait_for_ever();
CHECK(v4_fabric_wire(&f, b, 2, a, 0) && v4_fabric_wire(&f, b, 4, c, 1), "a third is wired to both");
{
static stream none;
static v4_device sink;
sink.take = stream_take; sink.give = 0; sink.self = &none;
CHECK(v4_fabric_wire_device(&f, b, 6, &sink), "and to a device that takes what is written to it");
}
for (i = 0; i < 20; i++) (void)v4_fabric_step(&f);
CHECK(ND(b)->n.mem[OUT] == (1 << 2), "it sees which of its ports has a neighbour waiting to write to it: %ld", (long)ND(b)->n.mem[OUT]);
CHECK((ND(b)->n.mem[OUT + 1] & ((1 << V4_PORTS) - 1)) == ((1 << 4) | (1 << 6)), "and which have one waiting to read from it, or a device: %ld", (long)(ND(b)->n.mem[OUT + 1] & ((1 << V4_PORTS) - 1)));
CHECK((ND(b)->n.mem[OUT + 1] >> V4_PORTS) == ((1 << 2) | (1 << 4) | (1 << 6)), "and, in the bits above those, which of its ports have anything on them at all: %ld", (long)(ND(b)->n.mem[OUT + 1] >> V4_PORTS));
CHECK(ND(a)->n.asking && ND(a)->n.request == 5 && ND(c)->n.reading && ND(c)->n.mem[OUT] == 0, "looking takes nothing and gives nothing: both wait as they did");
CHECK(!ND(b)->n.stopped && ND(b)->n.reading, "and the one that looked did not wait to look");
/* ---- the wait (MESH.md 7c): a node offers a word and sleeps ---- */
{
static stream none;
static v4_device sink;
v4_uheat_t clock;
sink.take = stream_take; sink.give = 0; sink.self = &none;
/* taken by a reader */
fresh();
a = loaded(0); offer(0, 7); wait_into(OUT); done_mark(); wait_for_ever();
b = loaded(1); LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "a node that offers, wired to one that reads");
(void)settle(300);
CHECK(ND(b)->n.mem[OUT] == 7, "the reader has the word offered: %ld", (long)ND(b)->n.mem[OUT]);
CHECK(ND(a)->n.mem[OUT] == 0 && ND(a)->n.mem[OUT + 1] == (v4_cell)V4_PORTS + 0 && ND(a)->n.mem[OUT + 2] == 1 && ND(a)->n.offers == 0,
"the one that offered is told its offer on port 0 was taken, and has none left: %ld from %ld", (long)ND(a)->n.mem[OUT], (long)ND(a)->n.mem[OUT + 1]);
/* taken by a device */
fresh(); none.took_n = 0;
a = loaded(0); offer(0, 7); wait_into(OUT); done_mark(); wait_for_ever();
CHECK(v4_fabric_wire_device(&f, a, 0, &sink), "the same, wired to a device that takes what is written");
(void)settle(300);
CHECK(none.took_n == 1 && none.took[0] == 7 && ND(a)->n.mem[OUT + 1] == (v4_cell)V4_PORTS + 0 && ND(a)->n.mem[OUT + 2] == 1, "the device has the word, and the node is told");
/* a neighbour that never reads */
fresh();
a = loaded(0); offer(0, 7); wait_into(OUT); done_mark(); wait_for_ever();
b = loaded(1); count_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "a node that offers, wired to one that never reads");
for (i = 0; i < 200; i++) (void)v4_fabric_step(&f);
clock = ND(a)->es.anticlock;
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&ND(a)->n) && ND(a)->es.anticlock == clock && ND(a)->n.mem[OUT + 2] == 0, "it is in the wait, and executes nothing there");
CHECK((ND(b)->n.readers & (1u << 3)) != 0, "and shows to its neighbour as waiting to read");
/* withdrawn by a word that comes for it */
c = loaded(2); LIT(PB + 0); O(BANG_B); LIT(9); O(STORE_B); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, c, 0, a, 2), "a third node writes to it");
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.mem[OUT] == 9 && ND(a)->n.mem[OUT + 1] == 2 && ND(a)->n.mem[OUT + 2] == 1 && ND(a)->n.offers == 0,
"it gets that word, from that port, and its offer is withdrawn: %ld from %ld", (long)ND(a)->n.mem[OUT], (long)ND(a)->n.mem[OUT + 1]);
CHECK(!ND(b)->n.given && !ND(c)->n.asking, "the node offered to was given nothing, and the writer was served");
/* an offer that could be taken in the step a word comes: the word comes first */
fresh();
a = loaded(0); offer(0, 7); wait_into(OUT); offer(0, 7); wait_into(OUT + 3); done_mark(); wait_for_ever();
b = loaded(1); LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
c = loaded(2); LIT(PB + 0); O(BANG_B); LIT(9); O(STORE_B); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire(&f, c, 0, a, 2), "one offers to a reader while a third writes to it");
v4_fabric_sleep(&f, a);
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(ND(b)->n.reading && ND(c)->n.asking, "the reader and the writer are both blocked");
v4_fabric_sleep(&f, b); v4_fabric_sleep(&f, c); v4_fabric_wake(&f, a);
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&ND(a)->n), "and then the one that offers is in the wait");
v4_fabric_wake(&f, b); v4_fabric_wake(&f, c);
(void)v4_fabric_step(&f);
CHECK(!v4_node_in_wait(&ND(a)->n) && !ND(c)->n.asking && ND(b)->n.reading && !ND(b)->n.given, "in one step: the word for it comes first, and the reader is given nothing");
(void)settle(300);
CHECK(ND(a)->n.mem[OUT] == 9 && ND(a)->n.mem[OUT + 1] == 2 && ND(a)->n.mem[OUT + 3] == 0 && ND(a)->n.mem[OUT + 4] == (v4_cell)V4_PORTS + 0 && ND(b)->n.mem[OUT] == 7,
"it offers again, and the reader gets the word once");
/* two nodes each offering to the other */
fresh();
a = loaded(0); offer(0, 5); wait_into(OUT); done_mark(); wait_for_ever();
b = loaded(1); offer(3, 6); wait_into(OUT); done_mark(); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "two nodes each offer to the other");
(void)settle(300);
CHECK(ND(a)->n.mem[OUT] == 0 && ND(a)->n.mem[OUT + 1] == (v4_cell)V4_PORTS + 0, "the one at the lower place has its offer taken");
CHECK(ND(b)->n.mem[OUT] == 5 && ND(b)->n.mem[OUT + 1] == 3, "the other gets the word, from the port it came by");
CHECK(ND(a)->n.offers == 0 && ND(b)->n.offers == 0 && ND(a)->n.mem[OUT + 2] == 1 && ND(b)->n.mem[OUT + 2] == 1, "and neither has an offer left");
/* three in a ring, each offering to the next */
fresh();
a = loaded(0); offer(0, 11); wait_into(OUT); done_mark(); wait_for_ever();
b = loaded(1); offer(0, 12); wait_into(OUT); done_mark(); wait_for_ever();
c = loaded(2); offer(0, 13); wait_into(OUT); done_mark(); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 1) && v4_fabric_wire(&f, b, 0, c, 1) && v4_fabric_wire(&f, c, 0, a, 1), "three nodes in a ring, each offering to the next");
CHECK(settle(1000) < 1000, "they come to rest");
CHECK(ND(a)->n.mem[OUT + 1] == (v4_cell)V4_PORTS + 0 && ND(b)->n.mem[OUT] == 11 && ND(b)->n.mem[OUT + 1] == 1, "the first's offer was taken by the second");
CHECK(v4_node_in_wait(&ND(c)->n) && ND(c)->n.offers == 1u && ND(c)->n.mem[OUT + 2] == 0 && !ND(a)->n.given, "the third still offers to the first, which is not reading: nothing was handed over twice");
/* a neighbour that is asleep */
fresh();
a = loaded(0); offer(0, 7); wait_into(OUT); done_mark(); wait_for_ever();
b = loaded(1); LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "a node that offers, wired to a reader");
v4_fabric_sleep(&f, a);
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
v4_fabric_sleep(&f, b); v4_fabric_wake(&f, a);
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&ND(a)->n) && ND(b)->n.reading && !ND(b)->n.given, "while the reader is asleep the offer stands");
v4_fabric_wake(&f, b);
(void)settle(300);
CHECK(ND(b)->n.mem[OUT] == 7 && ND(a)->n.mem[OUT + 2] == 1, "woken, it takes it");
/* a port with nothing on it */
fresh();
a = loaded(0); offer(5, 7); wait_into(OUT); done_mark(); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire_device(&f, a, 7, &sink), "a node that offers on a port nothing is wired to (the port it ends by reading has a device that gives nothing)");
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
v4_fabric_gone_error(&f, 18);
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&ND(a)->n) && ND(a)->n.faults == 0, "a bare node waits, though the fabric has an error for it");
v4_node_error_attach(&ND(a)->n, OUT + 20);
v4_node_fault_attach(&ND(a)->n, 900);
for (i = 0; i < 100 && ND(a)->n.mem[OUT + 2] == 0; i++) (void)v4_fabric_step(&f); /* until it has noted what it was told */
CHECK(!v4_node_in_wait(&ND(a)->n) && ND(a)->n.faults == 0 && ND(a)->n.offers == 0 && ND(a)->n.mem[OUT] == 0 && ND(a)->n.mem[OUT + 1] == 2 * (v4_cell)V4_PORTS + 5 && ND(a)->n.mem[OUT + 2] == 1,
"one that can take an error is woken and told there is nothing on port 5: no error is raised, and its offer is withdrawn: %ld", (long)ND(a)->n.mem[OUT + 1]);
/* offers are withdrawn by a store to the wait (found by the review of step 6d) */
fresh();
a = loaded(0); offer(0, 7); LIT(0); LIT(PB + (v4_cell)V4_PORT_WAIT); O(BANG_A); O(STORE_A); done_mark(); wait_for_ever();
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(v4_asm_ok(&as) && ND(a)->n.mem[OUT + 2] == 1 && ND(a)->n.offers == 0 && ND(a)->n.faults == 0, "a node that offers and then stores to the wait has no offer left");
/* the wait for an offer only: a word that comes for the node does not end it */
fresh();
a = loaded(0); offer(0, 7); wait1_into(OUT); done_mark(); wait_for_ever();
b = loaded(1); count_for_ever();
c = loaded(2); LIT(PB + 0); O(BANG_B); LIT(9); O(STORE_B); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire(&f, c, 0, a, 2), "a node waits for its offer only, to a node that never reads, while a third writes to it");
for (i = 0; i < 200; i++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&ND(a)->n) && ND(a)->n.mem[OUT + 2] == 0 && ND(c)->n.asking && (ND(c)->n.readers & 1u) == 0, "it goes on waiting; the writer is not served, and does not see it as reading");
CHECK(v4_fabric_remove(&f, b) != 0, "the node offered to is removed");
v4_fabric_gone_error(&f, 18);
v4_node_error_attach(&ND(a)->n, OUT + 20);
v4_node_fault_attach(&ND(a)->n, 900);
CHECK(v4_fabric_wire_device(&f, a, 7, &sink), "(the port it ends by reading has a device that gives nothing)");
for (i = 0; i < 100 && ND(a)->n.mem[OUT + 2] == 0; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.mem[OUT + 1] == 2 * (v4_cell)V4_PORTS + 0 && ND(a)->n.faults == 0 && ND(a)->n.offers == 0 && ND(c)->n.asking, "it is told there is nothing on that port; no error is raised; the writer still waits");
fresh();
a = loaded(0); offer(0, 7); wait1_into(OUT); done_mark(); wait_for_ever();
b = loaded(1); LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever();
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "the same, to a node that reads");
(void)settle(300);
CHECK(ND(b)->n.mem[OUT] == 7 && ND(a)->n.mem[OUT + 1] == (v4_cell)V4_PORTS + 0 && ND(a)->n.mem[OUT + 2] == 1, "its offer is taken, and it is told");
/* a host with one node, whose devices always take */
fresh();
a = loaded(0); offer(1, 21); offer(3, 23); wait_into(OUT); done_mark(); wait_for_ever();
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&ND(a)->n) && ND(a)->n.offers == ((1u << 1) | (1u << 3)), "a node offers on two ports and is in the wait");
CHECK(v4_node_offer_as_write(&ND(a)->n) && ND(a)->n.asking && ND(a)->n.ask_port == 1 && ND(a)->n.request == 21, "its host turns that into a write of the lower port's word");
v4_node_port_served(&ND(a)->n);
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(!ND(a)->n.asking && ND(a)->n.mem[OUT] == 0 && ND(a)->n.mem[OUT + 1] == (v4_cell)V4_PORTS + 1 && ND(a)->n.mem[OUT + 2] == 1 && ND(a)->n.offers == 0,
"and when it is served the node is told that offer was taken");
CHECK(!v4_node_offer_as_write(&ND(a)->n), "a node that is not in the wait is left alone");
}
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}