/* 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 #include 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); } 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); } /* a device that says whether it has something pending */ static int *dev_has; static int device_has(void *self) { (void)self; return *dev_has; } /* 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"); /* ---- a node blocked on the first word of a message, while a device of its own has something for it ---- */ { static int has; /* what the device says it has pending */ static stream none; static v4_device dev; v4_fabric_init(&f, places, 4); for (i = 0; i < 3; i++) v4_node_reset(&pool[i].n); a = loaded(0); /* looks about, then writes two words to its port 0 */ LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); O(DROP); LIT(PB + 0); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); wait_for_ever(); b = loaded(1); /* never reads */ 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) && v4_fabric_wire(&f, a, 0, b, 3), "a writer that looks first, wired to a node that never reads"); dev.take = stream_take; dev.give = 0; dev.self = &none; dev.pending = device_has; has = 1; dev_has = &has; CHECK(v4_fabric_wire_device(&f, a, 5, &dev), "and to a device, which has something for it"); for (i = 0; i < 40; i++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.asking && ND(a)->n.request == 1 && v4_node_words_since_look(&ND(a)->n) == 0, "the writer is blocked on its first word, and waits: the fabric has no error for it"); v4_fabric_interrupt_error(&f, 21); for (i = 0; i < 40; i++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.asking && ND(a)->n.faults == 0, "nor is a bare node let go when it has: it cannot take an error"); v4_node_error_attach(&ND(a)->n, OUT + 20); v4_node_fault_attach(&ND(a)->n, 900); has = 0; for (i = 0; i < 40; i++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.asking && ND(a)->n.faults == 0, "a node that can take one waits all the same while the device has nothing for it"); has = 1; (void)v4_fabric_step(&f); CHECK(!ND(a)->n.asking && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 21 && ND(a)->n.mem[OUT + 20] == 21, "when it has, the writer is let go and the error raised on it"); /* once the first word has gone, it is not let go: the message must be finished */ v4_fabric_init(&f, places, 4); for (i = 0; i < 3; i++) v4_node_reset(&pool[i].n); a = loaded(0); LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); O(DROP); LIT(PB + 0); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); wait_for_ever(); b = loaded(1); /* reads one word, and no more */ LIT(PB + 3); O(BANG_B); O(FETCH_B); 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) && v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire_device(&f, a, 5, &dev), "the same, to a node that reads one word and no more"); v4_node_error_attach(&ND(a)->n, OUT + 20); v4_node_fault_attach(&ND(a)->n, 900); v4_fabric_interrupt_error(&f, 21); for (i = 0; i < 60; i++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.asking && ND(a)->n.request == 2 && ND(a)->n.faults == 0, "blocked on its second word with the device still pending, the writer waits"); } /* ---- 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), "a node that offers on a port nothing is wired to"); 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); (void)v4_fabric_step(&f); CHECK(!v4_node_in_wait(&ND(a)->n) && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 18 && ND(a)->n.offers == 0, "one that can take an error has it raised, and its offer is withdrawn"); /* 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; }