/* 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 - 24u)) /* port 0: the ports, and the addresses after them (node.h), end inside memory */ #define P_WIRE (PB + (v4_cell)V4_PORT_WIRE) #define P_A (PB + (v4_cell)V4_PORT_A) #define P_B (PB + (v4_cell)V4_PORT_B) #define P_DO (PB + (v4_cell)V4_PORT_DO) #define P_HOW (PB + (v4_cell)V4_PORT_HOW) #define P_HAVE (PB + (v4_cell)V4_PORT_HAVE) #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; } /* ---- messages on a wire: what the tests below drive a node with ---- */ /* A message in a node's memory: seven words at `hdr` and its text at `text`. */ static void put_msg(v4_node *n, v4_cell hdr, v4_cell text, v4_cell len, v4_cell seed) { unsigned i; for (i = 0; i < 7; i++) n->mem[hdr + (v4_cell)i] = seed * 100 + (v4_cell)i; n->mem[hdr + 6] = len; for (i = 0; len > 0 && len <= 1024 && i < ((unsigned)len + 3u) / 4u; i++) n->mem[text + (v4_cell)i] = seed * 1000 + (v4_cell)i; } /* 1 if the message in the node's memory is the one put_msg makes, but for the word `except` of the header */ static int msg_is_but(const v4_node *n, v4_cell hdr, v4_cell text, v4_cell len, v4_cell seed, unsigned except) { unsigned i; for (i = 0; i < 7; i++) { v4_cell want = i == 6 ? len : seed * 100 + (v4_cell)i; if (i != except && n->mem[hdr + (v4_cell)i] != want) return 0; } for (i = 0; i < ((unsigned)len + 3u) / 4u; i++) if (n->mem[text + (v4_cell)i] != seed * 1000 + (v4_cell)i) return 0; return 1; } static int msg_is(const v4_node *n, v4_cell hdr, v4_cell text, v4_cell len, v4_cell seed) { return msg_is_but(n, hdr, text, len, seed, 7); } /* A queue filled until `leave` cells are free. */ static void fill(v4_wire_queue *q, unsigned leave) { v4_cell h[7] = { 1, 2, 3, 4, 5, 6, 1024 }; static v4_cell t[V4_WIRE_CELLS]; while (V4_WIRE_CELLS - q->used >= 263u + leave) (void)v4_wire_put(q, h, t); if (V4_WIRE_CELLS - q->used > leave) { h[6] = 4 * (v4_cell)(V4_WIRE_CELLS - q->used - leave - 7u); (void)v4_wire_put(q, h, t); } } /* Ask node `id` for an operation from outside, as its own programme would by * storing to its wire, A, B and Do; let one step of the engine pass; and give * how it went, or -99 if the node is still blocked at it. */ static v4_cell ask(unsigned id, v4_cell wire, v4_cell a, v4_cell b, v4_cell op) { v4_node *n = &ND(id)->n; v4_node_store(n, P_WIRE, wire); v4_node_store(n, P_A, a); v4_node_store(n, P_B, b); v4_node_store(n, P_DO, op); (void)v4_fabric_step(&f); return n->doing ? -99 : v4_node_fetch(n, P_HOW); } /* The same for a take, a move or a drop, which are done only on the message * last looked at: the node looks first, as a node does. */ static v4_cell seen(unsigned id, v4_cell wire, v4_cell a, v4_cell b, v4_cell op) { (void)ask(id, wire, 500, 0, 1); return ask(id, wire, a, b, op); } 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"); /* ---- A WORD WRITTEN TO WHAT WILL NOT READ ONE (found by the final review of step 6e) ---- * A node that writes a word to a port is blocked until it is taken. Two things will never take it: a * device that takes no words -- one that speaks whole messages only -- and a node that is asleep at its * wires, which executes nothing until a message comes. The writer is let go, as from a port with no one * on it. A neighbour that is doing something else, or that the fabric has put to sleep, may yet read: * the writer waits. */ { static v4_wire_queue wq[4]; static const v4_device mute = { 0, 0, 0 }; /* it takes and gives no words */ static const v4_cell one[1] = { 5 }; static stream s; static v4_device giver; v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, wq, 4); a = loaded(0); /* writes a word to its port 0, and then counts in OUT */ LIT(PB + 0); O(BANG_B); LIT(7); O(STORE_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_device(&f, a, 0, &mute), "a writer, wired to a device that takes no words"); (void)settle(100); CHECK(ND(a)->n.asking && ND(a)->n.mem[OUT] == 0, "it is blocked writing"); v4_fabric_gone_error(&f, 18); (void)settle(100); CHECK(ND(a)->n.asking && ND(a)->n.faults == 0, "a bare node, which cannot take an error, waits there"); v4_node_error_attach(&ND(a)->n, OUT + 20); v4_node_fault_attach(&ND(a)->n, 900); (void)v4_fabric_step(&f); CHECK(!ND(a)->n.asking && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 18 && ND(a)->n.mem[OUT + 20] == 18, "a node that can take one is let go: no one on that port"); v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, wq, 4); v4_fabric_gone_error(&f, 18); a = loaded(0); LIT(PB + 0); O(BANG_B); LIT(7); O(STORE_B); wait_for_ever(); b = loaded(1); /* reads a word from its port 5, and then sleeps at its wires */ LIT(PB + 5); O(BANG_B); O(FETCH_B); O(DROP); LIT(P_DO); O(BANG_B); LIT(8); O(STORE_B); wait_for_ever(); CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "a writer wired to a node that is waiting to read another port"); v4_node_error_attach(&ND(a)->n, OUT + 20); v4_node_fault_attach(&ND(a)->n, 900); (void)settle(200); CHECK(ND(a)->n.asking && ND(a)->n.faults == 0 && ND(b)->n.reading, "the neighbour is not asleep at its wires and may yet read: the writer waits"); v4_fabric_sleep(&f, b); memset(&s, 0, sizeof s); s.words = one; s.count = 1; giver.give = stream_give; giver.take = 0; giver.self = &s; CHECK(v4_fabric_wire_device(&f, b, 5, &giver), "the neighbour is put to sleep by the fabric, and a device with a word for it is wired to its port 5"); (void)settle(200); CHECK(ND(a)->n.asking && ND(a)->n.faults == 0 && !v4_node_doing(&ND(b)->n), "a neighbour the fabric has put to sleep takes nothing and does nothing: the writer waits"); v4_fabric_wake(&f, b); for (i = 0; i < 200 && ND(a)->n.asking; i++) (void)v4_fabric_step(&f); CHECK(v4_node_doing(&ND(b)->n) && ND(b)->n.do_op == 8, "woken, the neighbour takes its word and sleeps at its wires"); CHECK(!ND(a)->n.asking && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 18, "and the writer is let go: no one on that port will read a word"); /* nor does it matter that such a node is awake and running: once a node has asked for a whole message * it is not one that words are written to. A stuck node does not hold whoever writes it a word. */ v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, wq, 4); v4_fabric_gone_error(&f, 18); a = loaded(0); LIT(PB + 0); O(BANG_B); LIT(7); O(STORE_B); wait_for_ever(); b = loaded(1); /* reads a word from its port 5, asks whether its wire 3 has room, and then counts for ever */ LIT(PB + 5); O(BANG_B); O(FETCH_B); O(DROP); LIT(P_WIRE); O(BANG_B); LIT(3); O(STORE_B); LIT(P_A); O(BANG_B); LIT(8); O(STORE_B); LIT(P_DO); O(BANG_B); LIT(10); O(STORE_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), "a writer wired to a node that has not yet asked anything of a wire"); v4_node_error_attach(&ND(a)->n, OUT + 20); v4_node_fault_attach(&ND(a)->n, 900); (void)settle(200); CHECK(ND(a)->n.asking && ND(a)->n.faults == 0, "the writer waits"); memset(&s, 0, sizeof s); s.words = one; s.count = 1; CHECK(v4_fabric_wire_device(&f, b, 5, &giver), "the neighbour is given its word"); for (i = 0; i < 200 && ND(a)->n.asking; i++) (void)v4_fabric_step(&f); CHECK(!ND(a)->n.asking && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 18, "it asks for room, and the writer is let go"); for (i = 0; i < 20; i++) (void)v4_fabric_step(&f); CHECK(!v4_node_doing(&ND(b)->n) && ND(b)->n.mem[OUT] > 0, "though that node is not asleep: it goes on counting"); } /* ---- 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"); /* ---- messages on the wire (MESH.md 7d): the engine only ---- */ { static v4_wire_queue qpool[8]; v4_cell h[7], t[V4_WIRE_CELLS], snap[V4_NODE_WORDS]; v4_wire_queue copy; v4_device nothing; unsigned w, k; memset(¬hing, 0, sizeof nothing); /* a node puts a message on its wire 0 and a neighbour takes it from its wire 3 */ v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, qpool, 8); a = loaded(0); LIT(P_WIRE); O(BANG_B); LIT(0); O(STORE_B); LIT(P_A); O(BANG_B); LIT(100); O(STORE_B); LIT(P_B); O(BANG_B); LIT(110); O(STORE_B); LIT(P_DO); O(BANG_B); LIT(5); O(STORE_B); LIT(P_HOW); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever(); b = loaded(1); LIT(P_WIRE); O(BANG_B); LIT(3); O(STORE_B); LIT(P_DO); O(BANG_B); LIT(8); O(STORE_B); LIT(P_HAVE); O(BANG_B); O(FETCH_B); LIT(OUT + 1); O(BANG_A); O(STORE_A); LIT(P_A); O(BANG_B); LIT(200); O(STORE_B); LIT(P_DO); O(BANG_B); LIT(1); O(STORE_B); /* it looks, and then takes what it looked at */ LIT(P_B); O(BANG_B); LIT(220); O(STORE_B); LIT(P_DO); O(BANG_B); LIT(2); O(STORE_B); LIT(P_HOW); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever(); CHECK(v4_asm_ok(&as), "a node that puts and a node that sleeps and takes assemble"); CHECK(v4_fabric_wire(&f, a, 0, b, 3), "wire 0 of the first is wired to wire 3 of the second"); ND(a)->n.mem[OUT] = ND(b)->n.mem[OUT] = -7; put_msg(&ND(a)->n, 100, 110, 13, 5); for (steps = 0; steps < 300 && ND(a)->n.do_op != 5; steps++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.do_op == 5 && !ND(a)->n.doing && ND(a)->n.how == 0 && f.place[a].wire[0].tx->used == 7 + 4, "the put was done in the engine step that asked for it: the message is on the wire and the node is not blocked"); steps = settle(2000); CHECK(steps < 2000 && ND(b)->n.do_op == 2 && ND(a)->n.mem[OUT] == 0 && ND(b)->n.mem[OUT] == 0, "how it went is 0 on both: %ld and %ld", (long)ND(a)->n.mem[OUT], (long)ND(b)->n.mem[OUT]); CHECK(msg_is(&ND(b)->n, 200, 220, 13, 5), "the seven words and the text taken are what was put"); CHECK(ND(b)->n.mem[OUT + 1] == (1 << 3), "and while it waited it saw the message on its wire 3: %ld", (long)ND(b)->n.mem[OUT + 1]); CHECK(f.place[a].wire[0].tx->used == 0 && f.place[b].wire[3].rx->used == 0, "the wire is empty again"); /* a put with nothing on the port is 2, to a wire with no room is 1; neither changes the wire */ v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, qpool, 8); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); CHECK(v4_fabric_wire(&f, a, 0, b, 3), "a wire for the refusals"); put_msg(&ND(a)->n, 100, 110, 20, 3); CHECK(ask(a, 5, 100, 110, 5) == 2, "a put to a port with nothing on it answers 2"); CHECK(ask(a, (v4_cell)V4_PORTS, 100, 110, 5) == 2 && ask(a, -1, 100, 110, 5) == 2, "and so does one to a port that is not there"); fill(f.place[a].wire[0].tx, 0); memcpy(©, f.place[a].wire[0].tx, sizeof copy); CHECK(ask(a, 0, 100, 110, 5) == 1, "a put to a wire with no room answers 1"); CHECK(memcmp(©, f.place[a].wire[0].tx, sizeof copy) == 0, "and the wire is exactly as it was"); v4_wire_reset(f.place[a].wire[0].tx); fill(f.place[a].wire[0].tx, 200); put_msg(&ND(a)->n, 100, 110, 4 * (201 - 7), 3); memcpy(©, f.place[a].wire[0].tx, sizeof copy); CHECK(ask(a, 0, 100, 110, 5) == 1 && memcmp(©, f.place[a].wire[0].tx, sizeof copy) == 0, "a message of 201 cells to a wire with 200 free: 1, and nothing put"); put_msg(&ND(a)->n, 100, 110, 4 * (200 - 7), 3); CHECK(ask(a, 0, 100, 110, 5) == 0 && f.place[a].wire[0].tx->used == V4_WIRE_CELLS, "one of 200 cells goes"); put_msg(&ND(a)->n, 100, 110, 5000, 3); v4_wire_reset(f.place[a].wire[0].tx); CHECK(ask(a, 0, 100, 110, 5) == 4 && f.place[a].wire[0].tx->used == 0, "a message whose length is not 0 to 1,024 answers 4"); put_msg(&ND(a)->n, 100, 110, -1, 3); CHECK(ask(a, 0, 100, 110, 5) == 4 && f.place[a].wire[0].tx->used == 0, "so does a length of -1"); CHECK(ask(a, 0, 100, 110, 99) == 4 && ask(a, 0, 100, 110, 0) == 4 && ask(a, 0, 100, 110, 11) == 4, "and an operation that is not one of the ten"); /* addresses off the end of memory, or inside the ports: 4, and nothing copied */ { v4_cell bad[7]; v4_wire_queue before_rx, before_tx; unsigned op, i; const unsigned ops[3] = { 1, 2, 5 }; bad[0] = (v4_cell)V4_NODE_WORDS - 3; /* the seven words run off the end */ bad[1] = (v4_cell)V4_NODE_WORDS; bad[2] = -1; bad[3] = PB + 2; /* inside the ports */ bad[4] = PB - 3; /* runs into them */ bad[5] = PB + (v4_cell)V4_PORT_HAVE; /* the last of them */ bad[6] = PB + (v4_cell)V4_PORT_HAVE - 6; /* the seven end on it */ for (op = 0; op < 3; op++) for (k = 0; k < 7; k++) { unsigned faults = ND(a)->n.faults; v4_wire_reset(f.place[a].wire[0].tx); v4_wire_reset(f.place[a].wire[0].rx); put_msg(&ND(a)->n, 100, 110, 30, 6); /* a message waiting for look and take to find */ for (i = 0; i < 7; i++) t[i] = ND(a)->n.mem[100 + i]; for (i = 0; i < 8; i++) t[8 + i] = ND(a)->n.mem[110 + i]; CHECK(v4_wire_put(f.place[a].wire[0].rx, t, t + 8) == V4_WIRE_DONE, "(a message waits)"); f.place[a].wire[0].rx->looked = 1; /* and has been looked at */ memcpy(snap, ND(a)->n.mem, sizeof snap); memcpy(&before_rx, f.place[a].wire[0].rx, sizeof before_rx); memcpy(&before_tx, f.place[a].wire[0].tx, sizeof before_tx); CHECK(ask(a, 0, bad[k], 110, ops[op]) == 4, "op %u with its seven words at %ld: 4", ops[op], (long)bad[k]); CHECK(memcmp(snap, ND(a)->n.mem, sizeof snap) == 0 && ND(a)->n.faults == faults, "op %u at %ld: nothing was copied, and the node did not fault", ops[op], (long)bad[k]); CHECK(memcmp(&before_rx, f.place[a].wire[0].rx, sizeof before_rx) == 0 && memcmp(&before_tx, f.place[a].wire[0].tx, sizeof before_tx) == 0, "op %u at %ld: neither wire has changed", ops[op], (long)bad[k]); } /* a good header and a text address off the end or inside the ports: 4, and the header is not copied either */ bad[0] = (v4_cell)V4_NODE_WORDS - 10; bad[1] = PB + 3; bad[2] = -5; for (k = 0; k < 3; k++) { v4_wire_reset(f.place[a].wire[0].tx); v4_wire_reset(f.place[a].wire[0].rx); put_msg(&ND(a)->n, 100, 110, 30, 6); for (i = 0; i < 7; i++) t[i] = ND(a)->n.mem[100 + i]; for (i = 0; i < 8; i++) t[8 + i] = ND(a)->n.mem[110 + i]; (void)v4_wire_put(f.place[a].wire[0].rx, t, t + 8); f.place[a].wire[0].rx->looked = 1; memcpy(snap, ND(a)->n.mem, sizeof snap); memcpy(&before_rx, f.place[a].wire[0].rx, sizeof before_rx); CHECK(ask(a, 0, 300, bad[k], 2) == 4 && memcmp(snap, ND(a)->n.mem, sizeof snap) == 0 && memcmp(&before_rx, f.place[a].wire[0].rx, sizeof before_rx) == 0, "take with its text at %ld: 4, nothing copied (not the seven words either), the message still there", (long)bad[k]); CHECK(ask(a, 0, 100, bad[k], 5) == 4 && f.place[a].wire[0].tx->used == 0, "put with its text at %ld: 4, nothing put", (long)bad[k]); } /* a message with no text needs no address for it */ put_msg(&ND(a)->n, 100, 110, 0, 6); v4_wire_reset(f.place[a].wire[0].tx); CHECK(ask(a, 0, 100, -1, 5) == 0 && f.place[a].wire[0].tx->used == 7, "a message of no text is put with any address for its text"); v4_wire_reset(f.place[a].wire[0].tx); } /* the wire, look, take, first, next, drop and the mark */ v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, qpool, 8); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); CHECK(v4_fabric_wire(&f, a, 0, b, 3), "a wire for looking"); CHECK(ask(a, 0, 200, 220, 1) == 3 && ask(a, 0, 200, 220, 2) == 3 && ask(a, 0, 200, 220, 4) == 3 && ask(a, 0, 200, 220, 3) == 3 && ask(a, 5, 200, 220, 1) == 2, "look, take, drop and move on a wire with no message answer 3, on a port with nothing 2"); put_msg(&ND(b)->n, 100, 110, 9, 1); CHECK(ask(b, 3, 100, 110, 5) == 0, "three messages are put"); put_msg(&ND(b)->n, 100, 110, 21, 2); CHECK(ask(b, 3, 100, 110, 5) == 0, "the second"); put_msg(&ND(b)->n, 100, 110, 4, 3); CHECK(ask(b, 3, 100, 110, 5) == 0, "and the third"); CHECK(ask(a, 0, 200, 220, 6) == 0 && ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 100 && ND(a)->n.mem[206] == 9, "first, and look: the first message's seven words"); CHECK(f.place[a].wire[0].rx->used == 10 + 13 + 8, "look took nothing"); CHECK(ask(a, 0, 200, 220, 7) == 0 && ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 200, "next: the second"); CHECK(ask(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 21, 2), "take of the second"); CHECK(ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 300, "the mark is at the third"); CHECK(ask(a, 0, 200, 220, 6) == 0 && seen(a, 0, 200, 220, 4) == 0 && ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 300, "first and drop: the first is gone, the third is left"); CHECK(ask(a, 0, 200, 220, 7) == 3 && ask(a, 0, 200, 220, 1) == 3, "next at the end answers 3 and the mark is at no message"); CHECK(ask(a, 0, 200, 220, 6) == 0 && seen(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 4, 3) && f.place[a].wire[0].rx->used == 0, "and the third is taken"); /* "which wires have a message": a bit to a wire; fetching it clears arrived */ v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, qpool, 8); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); c = (unsigned)v4_fabric_add(&f, &pool[2], PB); CHECK(v4_fabric_wire(&f, a, 2, b, 0) && v4_fabric_wire(&f, a, 6, c, 1), "a node with two neighbours"); (void)v4_fabric_step(&f); CHECK(v4_node_fetch(&ND(a)->n, P_HAVE) == 0, "with nothing on any wire, no bit"); put_msg(&ND(b)->n, 100, 110, 3, 1); CHECK(ask(b, 0, 100, 110, 5) == 0, "one neighbour puts"); put_msg(&ND(c)->n, 100, 110, 3, 1); CHECK(ask(c, 1, 100, 110, 5) == 0, "the other puts"); CHECK(f.place[a].wire[2].rx->arrived && f.place[a].wire[6].rx->arrived, "a message has arrived on each"); (void)v4_fabric_step(&f); CHECK(v4_node_fetch(&ND(a)->n, P_HAVE) == ((1 << 2) | (1 << 6)), "the wires with a message have each their bit"); (void)v4_fabric_step(&f); CHECK(!f.place[a].wire[2].rx->arrived && !f.place[a].wire[6].rx->arrived, "and fetching it clears what has arrived"); CHECK(v4_node_fetch(&ND(a)->n, P_HAVE) == ((1 << 2) | (1 << 6)), "though the messages are still there"); /* sleep: blocked, and the engine's clock does not move; a put to one of its wires wakes it */ v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, qpool, 8); a = loaded(0); LIT(P_DO); O(BANG_B); LIT(8); O(STORE_B); LIT(1); LIT(OUT); O(BANG_A); O(STORE_A); wait_for_ever(); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 4, b, 2), "a node that sleeps and then leaves its mark"); (void)settle(100); CHECK(ND(a)->n.doing && ND(a)->n.do_op == 8 && ND(a)->n.mem[OUT] == 0, "it is blocked at its sleep"); { v4_uheat_t clock = ND(a)->es.anticlock; unsigned moved = 0; for (i = 0; i < 500; i++) moved += v4_fabric_step(&f); CHECK(moved == 0 && ND(a)->es.anticlock == clock && ND(a)->n.doing && ND(a)->n.mem[OUT] == 0, "for 500 steps nothing happens and its clock does not move"); put_msg(&ND(b)->n, 100, 110, 3, 1); CHECK(ask(b, 2, 100, 110, 5) == 0, "a put to one of its wires"); (void)settle(100); CHECK(!ND(a)->n.doing && ND(a)->n.how == 0 && ND(a)->n.mem[OUT] == 1, "wakes it, and it goes on"); } /* a node that looks, leaves the message there, fetches "which wires" and sleeps again stays asleep until a second comes */ v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, qpool, 8); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); CHECK(v4_fabric_wire(&f, a, 1, b, 4), "two nodes"); put_msg(&ND(b)->n, 100, 110, 3, 1); CHECK(ask(b, 4, 100, 110, 5) == 0, "a message comes"); CHECK(ask(a, 0, 200, 220, 8) == 0, "a sleep with a message come is not a sleep"); CHECK(ask(a, 1, 200, 220, 6) == 0 && ask(a, 1, 200, 220, 1) == 0 && ND(a)->n.mem[200] == 100, "it looks, and leaves it there"); (void)v4_node_fetch(&ND(a)->n, P_HAVE); v4_node_store(&ND(a)->n, P_DO, 8); for (i = 0; i < 500; i++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.doing && f.place[a].wire[1].rx->used == 8, "it sleeps again, with the message still on the wire, and stays asleep for 500 steps"); put_msg(&ND(b)->n, 100, 110, 3, 2); CHECK(ask(b, 4, 100, 110, 5) == 0, "a second message comes"); (void)v4_fabric_step(&f); CHECK(!ND(a)->n.doing && ND(a)->n.how == 0, "and wakes it"); /* a message put in the same engine step as the node's fetch of "which wires" is not lost: the next sleep does not block */ v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, qpool, 8); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); CHECK(v4_fabric_wire(&f, a, 0, b, 3), "two nodes for the same-step put"); (void)v4_node_fetch(&ND(a)->n, P_HAVE); /* the fetch is seen in the step's execute phase ... */ put_msg(&ND(b)->n, 100, 110, 3, 1); v4_node_store(&ND(b)->n, P_WIRE, 3); v4_node_store(&ND(b)->n, P_A, 100); v4_node_store(&ND(b)->n, P_B, 110); v4_node_store(&ND(b)->n, P_DO, 5); /* ... and the put is done in the same step's operations */ (void)v4_fabric_step(&f); CHECK(!ND(b)->n.doing && ND(b)->n.how == 0 && f.place[a].wire[0].rx->used == 8 && f.place[a].wire[0].rx->arrived != 0, "the put and the fetch were in one step: the message is on the wire and counts as arrived"); CHECK(ask(a, 0, 0, 0, 8) == 0, "so the sleep that follows does not block"); /* sleep for room */ v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, qpool, 8); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); CHECK(v4_fabric_wire(&f, a, 0, b, 3), "a writer and a reader"); fill(f.place[a].wire[0].tx, 100); CHECK(ask(a, 0, 50, 0, 10) == 0 && ask(a, 0, 100, 0, 10) == 0 && ask(a, 0, 101, 0, 10) == 1 && ask(a, 0, 5000, 0, 10) == 1, "room, with 100 cells free: for 50 and for 100, not for 101"); CHECK(ask(a, 5, 10, 0, 10) == 2 && ask(a, 0, -1, 0, 10) == 4, "room on a port with nothing is 2, for -1 cells 4"); CHECK(f.place[a].wire[0].tx->used == V4_WIRE_CELLS - 100 && !ND(a)->n.doing, "and it changes nothing"); CHECK(ask(a, 0, V4_WIRE_CELLS + 1, 0, 9) == 1 && ask(a, 0, 100000, 0, 9) == 1 && ask(a, 0, -1, 0, 9) == 1 && !ND(a)->n.doing, "sleep for more room than the wire can ever have answers 1 at once, not blocked"); (void)v4_node_fetch(&ND(a)->n, P_HAVE); (void)v4_fabric_step(&f); CHECK(ask(a, 0, 200, 0, 9) == -99 && ND(a)->n.doing, "sleep for 200 cells with 100 free: blocked"); for (i = 0; i < 100; i++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.doing, "and still blocked"); v4_wire_first(f.place[b].wire[3].rx); CHECK(seen(b, 3, 400, 420, 4) == 0, "the reader drops the front message (which is 263 cells)"); while (ND(a)->n.doing && f.place[b].wire[3].rx->used > 0) { (void)seen(b, 3, 400, 420, 4); } CHECK(!ND(a)->n.doing && ND(a)->n.how == 0 && f.place[a].wire[0].tx->used + 200 <= V4_WIRE_CELLS, "woken, with how it went 0"); /* a small message in front: dropping it frees little, and the sleeper wants more than that leaves */ v4_wire_reset(f.place[a].wire[0].tx); v4_wire_reset(f.place[b].wire[3].rx); { v4_cell hh[7] = { 1, 2, 3, 4, 5, 6, 12 }; static v4_cell tt[V4_WIRE_CELLS]; (void)v4_wire_put(f.place[a].wire[0].tx, hh, tt); /* 10 cells */ hh[6] = 1024; for (i = 0; i < 3; i++) (void)v4_wire_put(f.place[a].wire[0].tx, hh, tt); hh[6] = 4 * (35 - 7); (void)v4_wire_put(f.place[a].wire[0].tx, hh, tt); /* 35 cells: 190 are free */ } CHECK(V4_WIRE_CELLS - f.place[a].wire[0].tx->used == 190, "a wire with 190 free and a small message in front"); (void)v4_node_fetch(&ND(a)->n, P_HAVE); (void)v4_fabric_step(&f); CHECK(ask(a, 0, 250, 0, 9) == -99, "asleep for 250"); v4_wire_first(f.place[b].wire[3].rx); CHECK(seen(b, 3, 400, 420, 4) == 0 && V4_WIRE_CELLS - f.place[a].wire[0].tx->used == 200, "the reader drops the small one: 200 are free"); CHECK(!ND(a)->n.doing && ND(a)->n.how == 1, "the sleeper, which wants 250, is woken, and told 1: a wire of its own has gained room, and not enough"); CHECK(ask(a, 0, 250, 0, 9) == -99, "it asks again, and sleeps"); for (i = 0; i < 50; i++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.doing, "and sleeps on while nothing changes"); CHECK(seen(b, 3, 400, 420, 4) == 0 && !ND(a)->n.doing && ND(a)->n.how == 0, "the next take gives it the room: 0"); /* woken by a message coming instead */ v4_wire_reset(f.place[a].wire[0].tx); fill(f.place[a].wire[0].tx, 100); (void)v4_node_fetch(&ND(a)->n, P_HAVE); (void)v4_fabric_step(&f); CHECK(ask(a, 0, 200, 0, 9) == -99, "asleep for room again"); put_msg(&ND(b)->n, 100, 110, 3, 1); CHECK(ask(b, 3, 100, 110, 5) == 0, "a message comes on the other wire"); (void)v4_fabric_step(&f); CHECK(!ND(a)->n.doing && ND(a)->n.how == 1 && f.place[a].wire[0].tx->used + 200 > V4_WIRE_CELLS, "it wakes, and how it went says 1: no room, but a message"); /* A node asleep for room on one wire is woken when another of its wires is cut, or the node on it * removed: it may be keeping something to put there, and must find out that it cannot (found by the * final review of step 6e: before, it slept on until something else happened). */ v4_fabric_init(&f, more_places, 8); v4_fabric_queues(&f, qpool, 8); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); c = (unsigned)v4_fabric_add(&f, &pool[2], PB); CHECK(v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire(&f, a, 1, c, 2), "a node with two wires"); fill(f.place[a].wire[0].tx, 0); fill(f.place[a].wire[1].tx, 0); (void)v4_node_fetch(&ND(a)->n, P_HAVE); (void)v4_fabric_step(&f); CHECK(ask(a, 0, 100, 0, 9) == -99, "both full: asleep for room on wire 0"); CHECK(v4_fabric_unwire(&f, a, 1), "its wire 1 is cut"); (void)v4_fabric_step(&f); CHECK(!ND(a)->n.doing && ND(a)->n.how == 1, "it wakes, told 1: the wire it named has no room, but something has changed"); CHECK(ask(a, 1, 100, 0, 10) == 2, "it asks, and finds no one on port 1"); CHECK(ask(a, 0, 100, 0, 9) == -99, "asleep for room on wire 0 again"); for (i = 0; i < 100; i++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.doing, "and it is not woken twice for the one cut"); CHECK(v4_fabric_wire(&f, a, 1, c, 2), "the wire is made again"); fill(f.place[a].wire[1].tx, 0); for (i = 0; i < 100; i++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.doing, "a wire being made does not wake it"); CHECK(v4_fabric_remove(&f, c) == &pool[2], "the node on wire 1 is removed"); (void)v4_fabric_step(&f); CHECK(!ND(a)->n.doing && ND(a)->n.how == 1, "it wakes, told 1"); ND(a)->n.doing = 0; v4_node_store(&ND(a)->n, P_DO, 8); CHECK(v4_fabric_wire(&f, a, 1, b, 4) && v4_fabric_unwire(&f, a, 1), "asleep for a message (8), with a wire made and cut"); for (i = 0; i < 100; i++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.doing, "a node asleep for a message is not woken by a cut: it keeps nothing"); /* SLEEP FOR ROOM IS WOKEN BY ROOM ON ANY OF THE NODE'S WIRES (MESH.md 7d.3): a node may be waiting to * put on more than one, and names only one */ v4_fabric_init(&f, more_places, 8); v4_fabric_queues(&f, qpool, 8); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); c = (unsigned)v4_fabric_add(&f, &pool[2], PB); CHECK(v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire(&f, a, 1, c, 2), "a node with two wires"); fill(f.place[a].wire[0].tx, 0); fill(f.place[a].wire[1].tx, 0); (void)v4_node_fetch(&ND(a)->n, P_HAVE); (void)v4_fabric_step(&f); CHECK(ask(a, 0, 100, 0, 9) == -99, "both full: asleep for room on wire 0"); for (i = 0; i < 100; i++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.doing, "and it sleeps on while nothing changes"); v4_wire_first(f.place[c].wire[2].rx); CHECK(seen(c, 2, 400, 420, 4) == 0, "the reader of wire 1 drops a message"); CHECK(!ND(a)->n.doing && ND(a)->n.how == 1 && f.place[a].wire[0].tx->used == V4_WIRE_CELLS, "it wakes, told 1: the wire it named has no room, but something has changed"); CHECK(ask(a, 0, 100, 0, 10) == 1 && ask(a, 1, 100, 0, 10) == 0, "it asks which: wire 1 has the room"); CHECK(ask(a, 0, 100, 0, 9) == -99, "asleep for room on wire 0 again: that room on wire 1 was seen"); for (i = 0; i < 100; i++) (void)v4_fabric_step(&f); CHECK(ND(a)->n.doing, "and it is not woken twice for it"); /* the room comes in the very step the node goes to sleep: the reader's take is done before the sleep is looked at */ (void)ask(c, 2, 500, 0, 1); v4_node_store(&ND(c)->n, P_WIRE, 2); v4_node_store(&ND(c)->n, P_DO, 4); ND(a)->n.doing = 0; v4_node_store(&ND(a)->n, P_WIRE, 0); v4_node_store(&ND(a)->n, P_A, 100); v4_node_store(&ND(a)->n, P_DO, 9); (void)v4_fabric_step(&f); CHECK(!ND(c)->n.doing && !ND(a)->n.doing && ND(a)->n.how == 1, "room that comes in the step the node goes to sleep wakes it: no wake-up is lost"); /* a device's take is a take */ { static const v4_device dev = { 0, 0, 0 }; v4_cell hh[7], tt[V4_WIRE_CELLS / 4]; CHECK(v4_fabric_wire_device(&f, a, 4, &dev), "a device on a third wire"); fill(f.place[a].wire[4].tx, 0); CHECK(ask(a, 0, 100, 0, 9) == -99, "asleep for room on wire 0"); CHECK(v4_fabric_device_take(&f, a, 4, hh, tt, V4_WIRE_CELLS / 4) == V4_WIRE_DONE, "the device takes a message between steps"); (void)v4_fabric_step(&f); CHECK(!ND(a)->n.doing && ND(a)->n.how == 1, "and that wakes it"); } /* a plain sleep is not woken by room */ (void)v4_node_fetch(&ND(a)->n, P_HAVE); (void)v4_fabric_step(&f); CHECK(ask(a, 0, 0, 0, 8) == -99, "asleep, for a message"); CHECK(seen(c, 2, 400, 420, 4) == 0 && ND(a)->n.doing, "room on a wire does not wake a node that sleeps for a message only"); /* TAKE, MOVE AND DROP ARE ABOUT THE MESSAGE LAST LOOKED AT (MESH.md 7d.3). A node looks, decides, and * then takes: if the wire was cut and made again in between, and another message put, what is at the * mark is not what it looked at. Then each answers 3, no such message, and nothing is written or moved. */ v4_fabric_init(&f, more_places, 8); v4_fabric_queues(&f, qpool, 8); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); c = (unsigned)v4_fabric_add(&f, &pool[2], PB); CHECK(v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire(&f, a, 1, c, 2), "a node between two"); put_msg(&ND(b)->n, 100, 110, 4, 1); CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 0, 0, 6) == 0, "a message of four characters comes, and the mark is at it"); memcpy(snap, ND(a)->n.mem, sizeof snap); CHECK(ask(a, 0, 200, 220, 2) == 3 && ask(a, 0, 1, 0, 3) == 3 && ask(a, 0, 0, 0, 4) == 3 && f.place[a].wire[0].rx->used == 8 && memcmp(snap, ND(a)->n.mem, sizeof snap) == 0, "not looked at: take, move and drop each answer 3, and nothing is written or taken"); CHECK(ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[206] == 4, "it is looked at: four characters"); /* the wire is cut and made again, and a message of 1,024 characters put where the short one was */ CHECK(v4_fabric_unwire(&f, a, 0) && v4_fabric_wire(&f, a, 0, b, 3), "the wire is cut, and made again"); put_msg(&ND(b)->n, 100, 110, 1024, 2); CHECK(ask(b, 3, 100, 110, 5) == 0 && f.place[a].wire[0].rx->used == 263, "and a message of 1,024 characters is put on it"); memcpy(snap, ND(a)->n.mem, sizeof snap); CHECK(ask(a, 0, 200, 220, 2) == 3 && memcmp(snap, ND(a)->n.mem, sizeof snap) == 0 && f.place[a].wire[0].rx->used == 263, "a take answers 3: it is not the message that was looked at. Nothing is written: not one cell, where 256 would have been"); CHECK(ask(a, 0, 1, 0, 3) == 3 && ask(a, 0, 0, 0, 4) == 3 && f.place[a].wire[0].rx->used == 263 && f.place[a].wire[1].tx->used == 0, "nor is it moved, or dropped"); CHECK(ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[206] == 1024 && ask(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 1024, 2), "looked at, it is taken"); /* a look is good for one take, move or drop, and not after the mark has been moved */ put_msg(&ND(b)->n, 100, 110, 4, 3); CHECK(ask(b, 3, 100, 110, 5) == 0, "another comes"); put_msg(&ND(b)->n, 100, 110, 8, 4); CHECK(ask(b, 3, 100, 110, 5) == 0, "and another behind it"); CHECK(ask(a, 0, 200, 220, 1) == 0 && ask(a, 0, 0, 0, 7) == 0 && ask(a, 0, 200, 220, 2) == 3, "looked at, then the mark moved on with next: a take answers 3"); CHECK(ask(a, 0, 0, 0, 6) == 0 && ask(a, 0, 200, 220, 1) == 0 && ask(a, 0, 0, 0, 6) == 0 && ask(a, 0, 0, 0, 4) == 3, "looked at, then first: a drop answers 3"); CHECK(ask(a, 0, 200, 220, 1) == 0 && ask(a, 0, 0, 0, 4) == 0 && ask(a, 0, 0, 0, 4) == 3 && f.place[a].wire[0].rx->used == 9, "looked at and dropped; the one that followed has not been looked at, and is not dropped"); CHECK(ask(a, 0, 200, 220, 1) == 0 && ND(a)->n.mem[206] == 8 && ask(a, 0, 1, 0, 3) == 0 && f.place[a].wire[1].tx->used == 9, "looked at, it is moved"); /* a message put behind the one looked at does not undo the look */ put_msg(&ND(b)->n, 100, 110, 4, 5); CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 200, 220, 1) == 0, "one comes and is looked at"); put_msg(&ND(b)->n, 100, 110, 4, 6); CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 4, 5), "another comes behind it: the first is still the one looked at, and is taken"); /* THE KERNEL ON A PORT has no queue: it is asked by words, and takes no messages */ { static stream ks; static v4_device kern; unsigned free_before = f.queue_free; ks.took_n = 0; kern.take = stream_take; kern.give = 0; kern.self = &ks; CHECK(v4_fabric_wire_kernel(&f, a, 5, &kern) && f.queue_free == free_before && !f.place[a].wire[5].rx && !f.place[a].wire[5].tx, "a kernel is wired to a port, and is given no queues"); put_msg(&ND(a)->n, 100, 110, 4, 7); CHECK(ask(a, 5, 100, 110, 5) == 2 && ask(a, 5, 8, 0, 10) == 2 && ask(a, 5, 8, 0, 9) == 2 && ask(a, 5, 200, 220, 1) == 2 && ask(a, 5, 0, 0, 6) == 2 && !ND(a)->n.doing, "a message put to it, room asked of it, a sleep for room on it, a look: each answers 2 at once -- no one takes messages there"); v4_node_store(&ND(a)->n, PB + 5, 77); (void)v4_fabric_step(&f); CHECK(!ND(a)->n.asking && ks.took_n == 1 && ks.took[0] == 77, "a word written to it is handed over as before"); CHECK(v4_fabric_unwire(&f, a, 5) && f.queue_free == free_before, "and cutting it gives back no queue that it never had"); } /* move from one of a node's wires to another */ v4_fabric_init(&f, more_places, 8); v4_fabric_queues(&f, qpool, 8); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); c = (unsigned)v4_fabric_add(&f, &pool[2], PB); CHECK(v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire(&f, a, 1, c, 2), "a node between two"); put_msg(&ND(b)->n, 100, 110, 21, 4); ND(b)->n.mem[103] = 9; CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 0, 0, 6) == 0, "a message comes on wire 0"); CHECK(seen(a, 0, 1, 0, 3) == 0, "and is moved to wire 1"); CHECK(f.place[a].wire[0].rx->used == 0 && f.place[c].wire[2].rx->used == 7 + 6, "it is off the one and on the other"); CHECK(ask(c, 2, 200, 220, 6) == 0 && seen(c, 2, 200, 220, 2) == 0 && ND(c)->n.mem[203] == 8 && msg_is_but(&ND(c)->n, 200, 220, 21, 4, 3), "and arrives whole, its fourth word one fewer"); /* the other full: 1, and nothing moved */ put_msg(&ND(b)->n, 100, 110, 21, 4); CHECK(ask(b, 3, 100, 110, 5) == 0 && ask(a, 0, 0, 0, 6) == 0, "another comes"); fill(f.place[a].wire[1].tx, 0); (void)ask(a, 0, 500, 0, 1); memcpy(©, f.place[a].wire[0].rx, sizeof copy); { v4_wire_queue copy2; memcpy(©2, f.place[a].wire[1].tx, sizeof copy2); CHECK(ask(a, 0, 1, 0, 3) == 1, "looked at, and moved to a wire with no room: 1"); CHECK(memcmp(©, f.place[a].wire[0].rx, sizeof copy) == 0 && memcmp(©2, f.place[a].wire[1].tx, sizeof copy2) == 0, "and neither wire has changed"); } CHECK(ask(a, 0, 5, 0, 3) == 2 && ask(a, 0, -1, 0, 3) == 2 && ask(a, 5, 1, 0, 3) == 2, "move to a port with nothing, or from one: 2"); /* a node removed with messages waiting both ways on two wires */ v4_fabric_init(&f, more_places, 8); v4_fabric_queues(&f, qpool, 4); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); c = (unsigned)v4_fabric_add(&f, &pool[2], PB); CHECK(v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire(&f, c, 1, b, 5), "a node in the middle with two neighbours"); put_msg(&ND(a)->n, 100, 110, 21, 1); CHECK(ask(a, 0, 100, 110, 5) == 0 && ask(b, 3, 100, 110, 5) == 0, "a message each way on the first wire"); CHECK(ask(c, 1, 100, 110, 5) == 0 && ask(b, 5, 100, 110, 5) == 0, "and on the second"); (void)ask(a, 0, 0, 0, 7); (void)ask(b, 3, 0, 0, 7); { v4_wire_queue *q0 = f.place[a].wire[0].tx, *q1 = f.place[a].wire[0].rx, *q2 = f.place[c].wire[1].tx, *q3 = f.place[c].wire[1].rx; fill(q0, 0); fill(q2, 0); (void)v4_node_fetch(&ND(a)->n, P_HAVE); (void)v4_node_fetch(&ND(c)->n, P_HAVE); (void)v4_fabric_step(&f); CHECK(ask(a, 0, 300, 0, 9) == -99 && ask(c, 1, 300, 0, 9) == -99, "both neighbours are asleep, waiting for room on the wire to it"); CHECK(v4_fabric_remove(&f, b) == &pool[1], "the node in the middle is removed"); (void)v4_fabric_step(&f); CHECK(!ND(a)->n.doing && ND(a)->n.how == 2 && !ND(c)->n.doing && ND(c)->n.how == 2, "each neighbour asleep for room is woken with 2"); CHECK(q0->used == 0 && q0->mark == q0->used && q1->used == 0 && q1->mark == q1->used && q2->used == 0 && q2->mark == q2->used && q3->used == 0 && q3->mark == q3->used, "and what waited on the four queues is let go, their marks at no message"); CHECK(f.place[a].wire[0].kind == V4_WIRE_NONE && f.place[a].wire[0].rx == 0 && f.place[a].wire[0].tx == 0, "the neighbour's wire is cut, with no queue"); CHECK(ask(a, 0, 100, 110, 5) == 2 && ask(a, 0, 100, 110, 1) == 2, "a put or a look on it is 2"); CHECK(v4_fabric_wire(&f, a, 4, c, 4) && v4_fabric_wire(&f, a, 5, c, 5) && v4_fabric_wire(&f, a, 6, c, 6) == 0, "the queues went back to the pool: all four, so two wires and not a third"); } /* a device's wire */ v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, qpool, 8); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); CHECK(v4_fabric_wire_device(&f, a, 4, ¬hing) && v4_fabric_wire(&f, a, 1, b, 1), "a device on wire 4 of a node, and another node on wire 1"); put_msg(&ND(a)->n, 100, 110, 17, 8); for (w = 0; w < 7; w++) h[w] = ND(a)->n.mem[100 + w]; for (w = 0; w < 5; w++) t[w] = ND(a)->n.mem[110 + w]; CHECK(v4_fabric_device_put(&f, a, 4, h, t) == 0, "the device puts a message"); CHECK(ask(a, 4, 200, 220, 6) == 0 && seen(a, 4, 200, 220, 2) == 0 && msg_is(&ND(a)->n, 200, 220, 17, 8), "and the node takes it, whole"); put_msg(&ND(a)->n, 100, 110, 3, 9); CHECK(ask(a, 4, 100, 110, 5) == 0, "the node puts one"); memset(h, 0, sizeof h); memset(t, 0, sizeof t); CHECK(v4_fabric_device_take(&f, a, 4, h, t, 0) == 4, "the device, with no room for its text, is told 4"); CHECK(v4_fabric_device_take(&f, a, 4, h, t, V4_WIRE_CELLS) == 0 && h[6] == 3 && h[0] == 900 && h[2] == 902, "and takes it with room"); CHECK(v4_fabric_device_take(&f, a, 4, h, t, V4_WIRE_CELLS) == 3, "no more: 3"); CHECK(v4_fabric_device_put(&f, a, 1, h, t) == 2 && v4_fabric_device_take(&f, a, 1, h, t, 1) == 2, "a wire to a node is not a device's: 2"); CHECK(v4_fabric_device_put(&f, a, 6, h, t) == 2 && v4_fabric_device_take(&f, a, 6, h, t, 1) == 2 && v4_fabric_device_put(&f, 7, 4, h, t) == 2, "nor is a port with nothing, or a node that is not there"); h[6] = 1025; CHECK(v4_fabric_device_put(&f, a, 4, h, t) == 4, "a device cannot put what is not a message"); h[6] = 1024; fill(f.place[a].wire[4].rx, 0); CHECK(v4_fabric_device_put(&f, a, 4, h, t) == 1, "nor onto a full wire: 1"); /* a wire when the pool has queues for two: a third fails and changes nothing */ v4_fabric_init(&f, places, 4); v4_fabric_queues(&f, qpool, 4); a = (unsigned)v4_fabric_add(&f, &pool[0], PB); b = (unsigned)v4_fabric_add(&f, &pool[1], PB); c = (unsigned)v4_fabric_add(&f, &pool[2], PB); CHECK(v4_fabric_wire(&f, a, 0, b, 0) && v4_fabric_wire_device(&f, c, 0, ¬hing), "two wires take the four queues"); CHECK(v4_fabric_wire(&f, a, 1, c, 1) == 0 && v4_fabric_wire_device(&f, b, 1, ¬hing) == 0, "a third wire, of either kind, fails"); CHECK(f.place[a].wire[1].kind == V4_WIRE_NONE && f.place[c].wire[1].kind == V4_WIRE_NONE && f.place[b].wire[1].kind == V4_WIRE_NONE, "and it is not there"); CHECK(f.place[a].wire[0].kind == V4_WIRE_NODE && f.place[a].wire[0].node == b && f.place[c].wire[0].kind == V4_WIRE_DEVICE, "and what was wired still is"); CHECK(v4_fabric_wire(&f, a, 0, c, 2) && f.place[b].wire[0].kind == V4_WIRE_NONE && f.place[c].wire[2].node == a, "a port that is wired again gives back its queues first, and gets them"); CHECK(v4_fabric_unwire(&f, a, 0) && v4_fabric_wire(&f, a, 1, b, 1), "and one that is cut gives them back"); CHECK(ask(a, 0, 100, 110, 5) == 2, "a put on the cut one: 2"); } printf(" %d checks, %d failures\n", checks, failures); return failures != 0; }