/* 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 - 16u)) /* port 0 */ #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 */ /* 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"); /* ---- 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"); printf(" %d checks, %d failures\n", checks, failures); return failures != 0; }