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>
551 lines
32 KiB
C
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;
|
|
}
|