From d5cd64605d03e30565da0f2ac18d00bfe9c79f4e Mon Sep 17 00:00:00 2001 From: rajames Date: Thu, 8 Oct 2026 13:50:15 -0400 Subject: [PATCH] feat(v4.0.0): the wire in the engine -- whole messages, put and taken in one step Queues of whole messages on the wires between nodes (v4/wire.h), the six addresses and ten operations a node asks of the fabric (MESH.md 7d.3), and the fabric's pool of queues. Engine only: the nucleus does not use any of it yet. The ports in the host map move to BUF0_W - 140 so that the new addresses are not among the nucleus variables, with a compile-time check. Co-Authored-By: Claude Opus 5.5 --- capsules/v4/nucleus-64.f18 | Bin 56808 -> 56808 bytes kernel/Makefile | 2 +- v4/Makefile | 2 +- v4/include/v4/fabric.h | 38 +++- v4/include/v4/node.h | 49 ++++- v4/include/v4/wire.h | 78 ++++++++ v4/src/exec.c | 6 +- v4/src/fabric.c | 240 +++++++++++++++++++++- v4/src/node.c | 38 +++- v4/src/wire.c | 124 ++++++++++++ v4/tests/host_map.h | 9 +- v4/tests/test_fabric.c | 398 ++++++++++++++++++++++++++++++++++++- v4/tests/test_host_quit.c | 11 +- v4/tests/test_wire.c | 294 +++++++++++++++++++++++++++ 14 files changed, 1267 insertions(+), 22 deletions(-) create mode 100644 v4/include/v4/wire.h create mode 100644 v4/src/wire.c create mode 100644 v4/tests/test_wire.c diff --git a/capsules/v4/nucleus-64.f18 b/capsules/v4/nucleus-64.f18 index b108c931b23dda5bd5292c80c4cd7a2eb2b66551..d9b2d7efb9416804f5c436fcabd96cb589b6c2e6 100644 GIT binary patch delta 316 zcmaE{oB73V<_#a%mEKq|fPv4fbaxQr$A1X;vTiDv|J`D<5Jw#|(>IIBlepEG-dIfD z1)&~she>^hY7Vl57;?eJIts)BstV%K!zVq9=N%VJH&FE?-ZY@mn}6}%(+1JEEY}Hu zZ0z!q1KGIBOHJwv)T*ygI-_4R2x8UdS6=^ELAqWA$T5OwuAnkzkhZ2!KM?m)Xc{Yq t_L8@Tu2Ahk(d(E%>X_pGWrDOZop1wDUJz>1i7*iF)rmHcnxd0sA^=$+YIFbq delta 319 zcmaE{oB73V<_#a%l_D$|z`$o#x;u#R<39v^SvM8TPqy4F#8Jo0lw>)161N&tgyrO2 z5b6yTsRXDMiBLMDUo!||)#g`T|5!n~UIoZ8f@rRwGG>srrcgf+_flvYD~9%x xw}!4z?LpD&m>44_FAP-QtPyu36Qq;rgd2$Rf>4uAgn@XkPPBoHD>_*w0szJ0S{VQU diff --git a/kernel/Makefile b/kernel/Makefile index bc9bdd47..3b80d83c 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -613,7 +613,7 @@ V4_DEFS := -DSTARFORTH_V4=1 -Iv4/include \ KERNEL_CFLAGS += $(V4_DEFS) LOADER_CFLAGS += $(V4_DEFS) -V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c message.c) +V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c message.c wire.c) V4_IMAGE_C := $(BUILD_DIR)/v4_image_64.c LOADER_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c diff --git a/v4/Makefile b/v4/Makefile index 20fc19fd..a0f8053e 100644 --- a/v4/Makefile +++ b/v4/Makefile @@ -98,7 +98,7 @@ ROOT := $(abspath $(HERE)/..) NUCLEUS_64 := $(ROOT)/capsules/v4/nucleus-64.f18 NUCLEUS_32 := $(BINDIR)/nucleus-32.f18 HOST_DEFS := -DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING) -ENGINE_SRCS := $(addprefix $(HERE)/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c message.c) +ENGINE_SRCS := $(addprefix $(HERE)/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c message.c wire.c) define IMAGE_RULE $(BINDIR)/mkimage-$(1): $(HERE)/tools/mkimage.c $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/tests/host_map.h $(HERE)/Makefile diff --git a/v4/include/v4/fabric.h b/v4/include/v4/fabric.h index 1d0898d1..9f754c79 100644 --- a/v4/include/v4/fabric.h +++ b/v4/include/v4/fabric.h @@ -19,6 +19,7 @@ #define V4_FABRIC_H #include "v4/exec.h" +#include "v4/wire.h" /* What is on the other end of a port that is not a node: a console, a disk, * the kernel that serves a node's requests. Either function may be 0. */ @@ -35,6 +36,7 @@ typedef struct { int kind; unsigned node, port; /* V4_WIRE_NODE: the other node and its port */ const v4_device *device; /* V4_WIRE_DEVICE */ + v4_wire_queue *rx, *tx; /* the messages that come to this end of the wire, and that go from it (7d.2); 0: none */ } v4_wire; /* A node, with what the engine needs to run it. Its owner supplies one of @@ -55,6 +57,9 @@ typedef struct { typedef struct { v4_place *place; unsigned capacity; + v4_wire_queue *queue; /* see v4_fabric_queues; 0: none were given */ + unsigned queue_count, queue_free; /* how many, and how many are not on a wire */ + unsigned queue_first; /* the first that is not, and the index plus one; 0: none. Each links to the next by its `head`. */ v4_cell gone_error; /* see v4_fabric_gone_error; 0: none */ v4_cell interrupt_error; /* see v4_fabric_interrupt_error; 0: none */ } v4_fabric; @@ -62,6 +67,27 @@ typedef struct { /* An empty fabric with room for `capacity` nodes, in `places`. */ void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity); +/* THE WIRES' MEMORY (MESH.md 7d.2). Between two nodes that are wired, or a + * node and a device, the fabric keeps two queues of whole messages, one each + * way (wire.h). The owner gives it the queues: `count` of them, in `pool`, + * before anything is wired. Wiring a port takes two, cutting it gives them + * back, emptied, and wiring fails, changing nothing, when there are not two + * to take (counting those a port that is wired already would give back). + * The fabric allocates nothing. + * + * A fabric that was never given queues wires as it always did, and the + * operations on a node's wires answer that no one is on the port. That is + * how the fabrics of nothing that uses messages on the wire are made. */ +void v4_fabric_queues(v4_fabric *f, v4_wire_queue *pool, unsigned count); + +/* What is on the other end of a device's wire, put or taken as a whole + * message through the fabric. The device's side of `node`'s port `port`: + * put adds a message to those coming to the node, take removes the front + * message of those going from it. Each answers a V4_WIRE_ result, and + * V4_WIRE_NO_ONE when the port is not wired to a device. */ +int v4_fabric_device_put(v4_fabric *f, unsigned node, unsigned port, const v4_cell *header7, const v4_cell *text); +int v4_fabric_device_take(v4_fabric *f, unsigned node, unsigned port, v4_cell *header7, v4_cell *text, unsigned text_cap); + /* A PORT WITH NOTHING ON IT. On the fabric a node that writes to such a * port, or reads from it, waits for ever (docs/v4.0.0/MESH.md 4.1). That * is so here for a bare node. But a node that can take an error is not @@ -116,7 +142,17 @@ int v4_fabric_unwire(v4_fabric *f, unsigned a, unsigned pa); void v4_fabric_sleep(v4_fabric *f, unsigned id); void v4_fabric_wake(v4_fabric *f, unsigned id); -/* Time passes: every node that is awake and not blocked executes one +/* WHOLE MESSAGES. A node that has stored an operation to Do (node.h) is + * blocked, and in the step in which it did the operation is done, whole, and + * the node is let go: put, take, move and drop and the rest (MESH.md 7d.3). + * Sleep (8) and sleep for room (9) are the two that wait: the node stays + * blocked, executing nothing, until a message has come to one of its wires + * since it last fetched which wires have one -- or, for 9, until its wire + * has the room. A message has "come" when a put or a move or a device put + * put it on the back of a queue; fetching Have clears that for all of the + * node's wires. + * + * Time passes: every node that is awake and not blocked executes one * instruction word; then every write with a reader waiting on the other end * of its wire is handed over, and every device is asked. Returns how many * instructions were executed and words handed over together: 0 means diff --git a/v4/include/v4/node.h b/v4/include/v4/node.h index 6d4108b7..4d17d6ac 100644 --- a/v4/include/v4/node.h +++ b/v4/include/v4/node.h @@ -44,6 +44,15 @@ #endif #define V4_PORT_ANY V4_PORTS /* not a port: "whichever port has a neighbour writing" */ +/* The indexes, after the ports, of what a node asks of the fabric about whole + * messages on its wires (docs/v4.0.0/MESH.md 7d.3); see v4_node_port_attach. */ +#define V4_PORT_WIRE (V4_PORTS + 4u) /* store the number of a port: what follows is about that wire */ +#define V4_PORT_A (V4_PORTS + 5u) /* store a value an operation needs */ +#define V4_PORT_B (V4_PORTS + 6u) /* store a second */ +#define V4_PORT_DO (V4_PORTS + 7u) /* store the number of an operation: the node is blocked until it is done */ +#define V4_PORT_HOW (V4_PORTS + 8u) /* fetch how it went */ +#define V4_PORT_HAVE (V4_PORTS + 9u) /* fetch which wires have a message waiting: bit k for port k */ + #ifndef V4_NODE_WORDS #define V4_NODE_WORDS 1024u #endif @@ -113,6 +122,15 @@ typedef struct { unsigned readers; /* bit k: the neighbour on port k is blocked reading from this node */ unsigned since_look; /* words taken from this node since it last fetched either of those */ + /* Whole messages on the wires (MESH.md 7d.3). See v4_node_port_attach. */ + unsigned wire; /* the port that A, B and Do are about; V4_PORTS if what was stored is not a port */ + v4_cell wire_a, wire_b; /* A and B */ + int doing; /* non-zero: blocked, having stored `do_op` to Do */ + v4_cell do_op; /* the operation */ + v4_cell how; /* how the last one went */ + unsigned have; /* which wires had a message waiting, as the fabric last said: bit k for port k */ + int have_fetched; /* non-zero: `have` has been fetched since the fabric last cleared what had arrived */ + /* DSTACK-DEPTH and RSTACK-DEPTH (D-16). See v4_node_stack_regs_attach. */ v4_cell dstack_reg; /* its word address, or -1 */ v4_cell rstack_reg; /* its word address, or -1 */ @@ -187,11 +205,24 @@ void v4_node_console_attach(v4_node *n, v4_cell addr); * ports have anything on them at all: bit V4_PORTS + k * for port k. A port with nothing on it can never * be read from or written to. - * The last two are how a node looks before it writes (MESH.md 7a), as the + * base + V4_PORTS + 4 .. + 9 whole messages on the wires (MESH.md 7d.3), the + * indexes V4_PORT_WIRE, V4_PORT_A, V4_PORT_B, V4_PORT_DO, + * V4_PORT_HOW and V4_PORT_HAVE: + * Wire, A, B store: what an operation is about and + * needs; fetch gives back what was stored + * Do store: the number of an operation. The + * node is blocked until whoever connects + * its ports has done it (v4_node_doing, + * v4_node_done); fetch gives the number + * How fetch: how the last one went + * Have fetch: which wires have a message + * waiting, bit k for port k, as the fabric + * last said (v4_node_have) + * The two before them are how a node looks before it writes (MESH.md 7a), as the * F18's io register lets it. They are what whoever connects the ports last * said, v4_node_port_status; fetching them waits for nothing and changes - * nothing. - * (read only) + * nothing. Of the whole-message addresses How and Have are read only: a + * store to either is an address fault, as to "any port". * None of them is memory while the ports are attached. * * A WRITE. A store to a port does not write memory: the node keeps the @@ -219,6 +250,18 @@ void v4_node_port_attach(v4_node *n, v4_cell base); /* The neighbour has taken what the node wrote. */ void v4_node_port_served(v4_node *n); +/* WHOLE MESSAGES (MESH.md 7d.3). A store to Do records the operation in + * n->do_op and blocks the node, as a store to a port does: n->doing. For + * whoever connects the node's ports: 1 if the node is blocked at one; the + * operation is done, `how` is what the node will fetch from How, and the + * node goes on from the opcode after the store; and `mask` is which wires + * have a message waiting, as the node will fetch it from Have. Fetching + * Have also sets n->have_fetched, for whoever connects the ports to see and + * clear. */ +int v4_node_doing(const v4_node *n); +void v4_node_done(v4_node *n, v4_cell how); +void v4_node_have(v4_node *n, unsigned mask); + /* How many words the node has had taken since it last looked at its * neighbours (a fetch of either of the two status addresses). A node that * looks before it begins a message (MESH.md 7a) and is blocked writing with diff --git a/v4/include/v4/wire.h b/v4/include/v4/wire.h new file mode 100644 index 00000000..910f7f7c --- /dev/null +++ b/v4/include/v4/wire.h @@ -0,0 +1,78 @@ +/* wire.h -- a queue of whole messages, one way along a wire. + * docs/v4.0.0/MESH.md section 7d.2. + * + * A message is what section 6 says: seven words, of which the seventh + * (word 6) is how many characters of text follow, 0 to 1024, and then the + * text, four characters to a word. On a queue it takes 7 cells and one for + * each word of text, and the queue holds V4_WIRE_CELLS of them, going round. + * + * ALL OF A MESSAGE OR NONE. Every function here leaves the queue exactly as + * it found it unless it answers V4_WIRE_DONE. There is no state in which + * part of a message has been put on, or taken off. + * + * THE MARK. The one that reads a queue has a mark on it: the message that + * look, take, move and drop are about. `mark` counts the cells from the + * front to where that message begins; equal to `used`, it is at no message, + * and those four answer V4_WIRE_NO_MESSAGE. A message put on the back of a + * queue whose mark is at no message is the one the mark is then at. + * + * Nothing here allocates, and nothing uses the C library: whoever owns the + * queue supplies its memory. + */ +#ifndef V4_WIRE_H +#define V4_WIRE_H + +#include "v4/cell.h" + +#ifndef V4_WIRE_CELLS +#define V4_WIRE_CELLS 1024 +#endif + +#define V4_WIRE_HEADER 7u /* the words of a message that are not its text */ + +typedef struct { + v4_cell cell[V4_WIRE_CELLS]; /* the messages, one after another, going round */ + unsigned head; /* where the front message begins */ + unsigned used; /* how many cells they take */ + unsigned mark; /* how many cells from the front the reader's mark is; == used: at no message */ + unsigned arrived; /* not 0: a message has come since the reader last asked which wires have one */ +} v4_wire_queue; + +enum { V4_WIRE_DONE = 0, V4_WIRE_NO_ROOM = 1, V4_WIRE_NO_ONE = 2, V4_WIRE_NO_MESSAGE = 3, V4_WIRE_NOT_A_MESSAGE = 4 }; + +/* Empty, and the mark at no message. */ +void v4_wire_reset(v4_wire_queue *q); + +/* The cells a message of `length` characters takes: 7 and a cell for each + * four characters. 0 if `length` is not 0 to 1024: it is not a message. */ +unsigned v4_wire_cells(v4_cell length); + +/* V4_WIRE_DONE if `cells` more would fit, V4_WIRE_NO_ROOM if not. Changes + * nothing. */ +int v4_wire_room(const v4_wire_queue *q, unsigned cells); + +/* The message whose seven words are at `header7` and whose text, a cell to + * four characters, is at `text` goes on the back. V4_WIRE_NOT_A_MESSAGE if + * word 6 is not 0 to 1024, V4_WIRE_NO_ROOM if it does not fit. */ +int v4_wire_put(v4_wire_queue *q, const v4_cell *header7, const v4_cell *text); + +/* The mark to the front message; to the one after the marked one, or + * V4_WIRE_NO_MESSAGE if there is none (the mark is then at no message). */ +void v4_wire_first(v4_wire_queue *q); +int v4_wire_next(v4_wire_queue *q); + +/* The marked message: its seven words copied out and nothing taken; + * copied out and taken off the queue, its text to `text`, which holds + * `text_cap` cells (V4_WIRE_NOT_A_MESSAGE if that is too few); taken off and + * gone. After a take or a drop the mark is at the message that followed. + * V4_WIRE_NO_MESSAGE with the mark at no message. */ +int v4_wire_look(const v4_wire_queue *q, v4_cell *header7); +int v4_wire_take(v4_wire_queue *q, v4_cell *header7, v4_cell *text, unsigned text_cap); +int v4_wire_drop(v4_wire_queue *q); + +/* The marked message of `from` goes to the back of `to`, with its fourth + * word (word 3) one fewer: how many more nodes may pass it on. V4_WIRE_NO_ROOM + * if `to` has no room for it, or is `from`. */ +int v4_wire_move(v4_wire_queue *from, v4_wire_queue *to); + +#endif /* V4_WIRE_H */ diff --git a/v4/src/exec.c b/v4/src/exec.c index cf99a66a..9757897f 100644 --- a/v4/src/exec.c +++ b/v4/src/exec.c @@ -260,7 +260,7 @@ unsigned v4_exec_step_word(v4_node *n, v4_exec_state *es, v4_heat *h) /* D-14: a node that faulted with no handler has stopped. A node that * has written to its port is blocked until it has been served. */ - if (n->stopped || n->asking || n->reading) return 0; + if (n->stopped || n->asking || n->reading || n->doing) return 0; if (es->resume && es->resume_faults != n->faults) es->resume = 0; /* it faulted while blocked: P is the handler */ if (es->resume) { /* unblocked: the rest of the word it was in */ @@ -305,8 +305,8 @@ unsigned v4_exec_step_word(v4_node *n, v4_exec_state *es, v4_heat *h) } executed++; - if (n->asking) { - /* The store was to the port. The node is blocked here; what is + if (n->asking || n->doing) { + /* The store was to the port, or to Do. The node is blocked here; what is * left of the word waits until it has been served. */ if (slot + 1 < V4_SLOT_COUNT) { es->resume = 1; diff --git a/v4/src/fabric.c b/v4/src/fabric.c index cb269156..13f2028c 100644 --- a/v4/src/fabric.c +++ b/v4/src/fabric.c @@ -9,7 +9,10 @@ static void clear_place(v4_place *p) unsigned k; p->node = 0; p->asleep = 0; - for (k = 0; k < V4_PORTS; k++) { p->wire[k].kind = V4_WIRE_NONE; p->wire[k].node = 0; p->wire[k].port = 0; p->wire[k].device = 0; } + for (k = 0; k < V4_PORTS; k++) { + p->wire[k].kind = V4_WIRE_NONE; p->wire[k].node = 0; p->wire[k].port = 0; p->wire[k].device = 0; + p->wire[k].rx = 0; p->wire[k].tx = 0; + } } void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity) @@ -17,6 +20,10 @@ void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity) unsigned i; f->place = places; f->capacity = capacity; + f->queue = 0; + f->queue_count = 0; + f->queue_free = 0; + f->queue_first = 0; f->gone_error = 0; f->interrupt_error = 0; for (i = 0; i < capacity; i++) clear_place(&places[i]); @@ -65,34 +72,102 @@ static int there(const v4_fabric *f, unsigned id, unsigned port) return id < f->capacity && f->place[id].node && port < V4_PORTS; } +void v4_fabric_queues(v4_fabric *f, v4_wire_queue *pool, unsigned count) +{ + unsigned i; + f->queue = pool; + f->queue_count = count; + f->queue_free = count; + f->queue_first = count ? 1 : 0; + for (i = 0; i < count; i++) { + v4_wire_reset(&pool[i]); + pool[i].head = i + 1 < count ? i + 2 : 0; + } +} + +/* a queue that is not on a wire; the fabric has been given some, and there is one */ +static v4_wire_queue *take_queue(v4_fabric *f) +{ + v4_wire_queue *q = &f->queue[f->queue_first - 1]; + f->queue_first = q->head; + f->queue_free--; + v4_wire_reset(q); + return q; +} + +/* a queue comes off a wire: what was waiting on it is let go */ +static void give_queue(v4_fabric *f, v4_wire_queue *q) +{ + if (!q) return; + v4_wire_reset(q); + q->head = f->queue_first; + f->queue_first = (unsigned)(q - f->queue) + 1; + f->queue_free++; +} + +/* Can the ports a and b be wired: if the fabric has been given queues, are + * there two once what those ports are wired to now has been given back? */ +static int queues_for(const v4_fabric *f, unsigned a, unsigned pa, unsigned b, unsigned pb) +{ + const v4_wire_queue *seen[4]; + unsigned i, k, n = 0, back = 0; + + if (!f->queue) return 1; + seen[n++] = f->place[a].wire[pa].rx; seen[n++] = f->place[a].wire[pa].tx; + if (b < f->capacity) { seen[n++] = f->place[b].wire[pb].rx; seen[n++] = f->place[b].wire[pb].tx; } + for (i = 0; i < n; i++) { + if (!seen[i]) continue; + for (k = 0; k < i && seen[k] != seen[i]; k++) { } + if (k == i) back++; + } + return f->queue_free + back >= 2; +} + /* cut what is on a port, at both ends */ static void cut(v4_fabric *f, unsigned a, unsigned pa) { v4_wire *w = &f->place[a].wire[pa]; if (w->kind == V4_WIRE_NODE && w->node < f->capacity && w->port < V4_PORTS) { v4_wire *other = &f->place[w->node].wire[w->port]; - if (other->kind == V4_WIRE_NODE && other->node == a && other->port == pa) { other->kind = V4_WIRE_NONE; other->device = 0; } + if (other->kind == V4_WIRE_NODE && other->node == a && other->port == pa) { + other->kind = V4_WIRE_NONE; other->device = 0; + other->rx = 0; other->tx = 0; /* the same two queues as w's */ + } } + if (f->queue) { give_queue(f, w->rx); give_queue(f, w->tx); } w->kind = V4_WIRE_NONE; w->device = 0; + w->rx = 0; + w->tx = 0; } int v4_fabric_wire(v4_fabric *f, unsigned a, unsigned pa, unsigned b, unsigned pb) { if (!there(f, a, pa) || !there(f, b, pb) || (a == b && pa == pb)) return 0; + if (!queues_for(f, a, pa, b, pb)) return 0; cut(f, a, pa); cut(f, b, pb); f->place[a].wire[pa].kind = V4_WIRE_NODE; f->place[a].wire[pa].node = b; f->place[a].wire[pa].port = pb; f->place[b].wire[pb].kind = V4_WIRE_NODE; f->place[b].wire[pb].node = a; f->place[b].wire[pb].port = pa; + if (f->queue) { + v4_wire_queue *ab = take_queue(f), *ba = take_queue(f); + f->place[a].wire[pa].tx = ab; f->place[b].wire[pb].rx = ab; + f->place[b].wire[pb].tx = ba; f->place[a].wire[pa].rx = ba; + } return 1; } int v4_fabric_wire_device(v4_fabric *f, unsigned a, unsigned pa, const v4_device *device) { if (!there(f, a, pa) || !device) return 0; + if (!queues_for(f, a, pa, f->capacity, 0)) return 0; cut(f, a, pa); f->place[a].wire[pa].kind = V4_WIRE_DEVICE; f->place[a].wire[pa].device = device; + if (f->queue) { + f->place[a].wire[pa].rx = take_queue(f); /* from the device */ + f->place[a].wire[pa].tx = take_queue(f); /* to it */ + } return 1; } @@ -103,6 +178,25 @@ int v4_fabric_unwire(v4_fabric *f, unsigned a, unsigned pa) return 1; } +int v4_fabric_device_put(v4_fabric *f, unsigned node, unsigned port, const v4_cell *header7, const v4_cell *text) +{ + const v4_wire *w; + if (!there(f, node, port)) return V4_WIRE_NO_ONE; + w = &f->place[node].wire[port]; + if (w->kind != V4_WIRE_DEVICE || !w->rx) return V4_WIRE_NO_ONE; + return v4_wire_put(w->rx, header7, text); +} + +int v4_fabric_device_take(v4_fabric *f, unsigned node, unsigned port, v4_cell *header7, v4_cell *text, unsigned text_cap) +{ + const v4_wire *w; + if (!there(f, node, port)) return V4_WIRE_NO_ONE; + w = &f->place[node].wire[port]; + if (w->kind != V4_WIRE_DEVICE || !w->tx) return V4_WIRE_NO_ONE; + v4_wire_first(w->tx); /* the device takes them in the order they were put */ + return v4_wire_take(w->tx, header7, text, text_cap); +} + v4_fabric_node *v4_fabric_remove(v4_fabric *f, unsigned id) { v4_fabric_node *storage; @@ -189,21 +283,159 @@ static void look_about(v4_fabric *f, unsigned a) v4_node_port_status(&f->place[a].node->n, writers, readers); } +/* ---- whole messages: what a node asked for by storing to Do (MESH.md 7d.3) ---- */ + +/* 1 if `count` words from `addr` are memory that is not the ports, or the + * addresses after them, of this node: they may be copied to and from. */ +static int span_ok(const v4_node *n, v4_cell addr, unsigned count) +{ + if (count == 0) return 1; + if (count > V4_NODE_WORDS || addr < 0 || (v4_ucell)addr > (v4_ucell)(V4_NODE_WORDS - count)) return 0; + if (n->port >= 0 && addr <= n->port + (v4_cell)V4_PORT_HAVE && addr + (v4_cell)count > n->port) return 0; + return 1; +} + +/* the queue of messages that come to the node's wire, and that go from it; 0: no one is on it */ +static v4_wire_queue *coming(const v4_fabric *f, unsigned id, unsigned port) +{ + return port < V4_PORTS ? f->place[id].wire[port].rx : 0; +} +static v4_wire_queue *going(const v4_fabric *f, unsigned id, unsigned port) +{ + return port < V4_PORTS ? f->place[id].wire[port].tx : 0; +} + +/* has a message come to any of the node's wires since it last fetched which have one */ +static int arrived(const v4_fabric *f, unsigned id) +{ + unsigned k; + for (k = 0; k < V4_PORTS; k++) { + const v4_wire_queue *q = f->place[id].wire[k].rx; + if (q && q->arrived) return 1; + } + return 0; +} + +/* Node `id` is doing an operation. Returns 1 and puts how it went in + * *how, or returns 0: it waits (8 and 9 only). Nothing is copied, put or + * taken unless the answer is 0 -- done. */ +static int operate(v4_fabric *f, unsigned id, v4_cell *how) +{ + v4_node *n = &f->place[id].node->n; + v4_wire_queue *rx = coming(f, id, n->wire), *tx = going(f, id, n->wire), *to; + v4_cell a = n->wire_a, b = n->wire_b, h[V4_WIRE_HEADER]; + unsigned cells; + + switch (n->do_op) { + case 1: /* look */ + if (!rx) { *how = V4_WIRE_NO_ONE; break; } + if (rx->mark >= rx->used) { *how = V4_WIRE_NO_MESSAGE; break; } + if (!span_ok(n, a, V4_WIRE_HEADER)) { *how = V4_WIRE_NOT_A_MESSAGE; break; } + *how = v4_wire_look(rx, &n->mem[a]); + break; + case 2: /* take */ + if (!rx) { *how = V4_WIRE_NO_ONE; break; } + if (rx->mark >= rx->used) { *how = V4_WIRE_NO_MESSAGE; break; } + (void)v4_wire_look(rx, h); + cells = v4_wire_cells(h[6]) - V4_WIRE_HEADER; + if (!span_ok(n, a, V4_WIRE_HEADER) || !span_ok(n, b, cells)) { *how = V4_WIRE_NOT_A_MESSAGE; break; } + *how = v4_wire_take(rx, &n->mem[a], cells ? &n->mem[b] : &n->mem[0], cells); + break; + case 3: /* move */ + if (!rx) { *how = V4_WIRE_NO_ONE; break; } + if (rx->mark >= rx->used) { *how = V4_WIRE_NO_MESSAGE; break; } + to = a >= 0 && a < (v4_cell)V4_PORTS ? going(f, id, (unsigned)a) : 0; + *how = to ? v4_wire_move(rx, to) : V4_WIRE_NO_ONE; + break; + case 4: /* drop */ + *how = rx ? v4_wire_drop(rx) : V4_WIRE_NO_ONE; + break; + case 5: /* put */ + if (!tx) { *how = V4_WIRE_NO_ONE; break; } + if (!span_ok(n, a, V4_WIRE_HEADER)) { *how = V4_WIRE_NOT_A_MESSAGE; break; } + cells = v4_wire_cells(n->mem[a + 6]); + if (cells == 0 || !span_ok(n, b, cells - V4_WIRE_HEADER)) { *how = V4_WIRE_NOT_A_MESSAGE; break; } + *how = v4_wire_put(tx, &n->mem[a], cells > V4_WIRE_HEADER ? &n->mem[b] : &n->mem[0]); + break; + case 6: /* first */ + if (rx) v4_wire_first(rx); + *how = rx ? V4_WIRE_DONE : V4_WIRE_NO_ONE; + break; + case 7: /* next */ + *how = rx ? v4_wire_next(rx) : V4_WIRE_NO_ONE; + break; + case 8: /* sleep */ + if (!arrived(f, id)) return 0; + *how = V4_WIRE_DONE; + break; + case 9: /* sleep for room */ + if (!tx) { *how = V4_WIRE_NO_ONE; break; } + if (a < 0) { *how = V4_WIRE_NOT_A_MESSAGE; break; } + if (a <= (v4_cell)V4_WIRE_CELLS && v4_wire_room(tx, (unsigned)a) == V4_WIRE_DONE) { *how = V4_WIRE_DONE; break; } + if (!arrived(f, id)) return 0; + *how = V4_WIRE_NO_ROOM; + break; + case 10: /* room */ + if (!tx) { *how = V4_WIRE_NO_ONE; break; } + if (a < 0) { *how = V4_WIRE_NOT_A_MESSAGE; break; } + *how = a <= (v4_cell)V4_WIRE_CELLS ? v4_wire_room(tx, (unsigned)a) : V4_WIRE_NO_ROOM; + break; + default: /* not an operation */ + *how = V4_WIRE_NOT_A_MESSAGE; + break; + } + return 1; +} + unsigned v4_fabric_step(v4_fabric *f) { - unsigned i, done = 0; + unsigned i, k, done = 0; + + /* which of its wires have a message waiting, for each node to fetch */ + for (i = 0; i < f->capacity; i++) + if (awake(f, i)) { + unsigned have = 0; + for (k = 0; k < V4_PORTS; k++) { + const v4_wire_queue *q = f->place[i].wire[k].rx; + if (q && q->used > 0) have |= 1u << k; + } + v4_node_have(&f->place[i].node->n, have); + } /* a node that is blocked fetches nothing, and is looked about for when * it is next to execute */ for (i = 0; i < f->capacity; i++) if (awake(f, i)) { const v4_node *n = &f->place[i].node->n; - if (!n->stopped && !n->asking && !(n->reading && !n->given)) look_about(f, i); + if (!n->stopped && !n->asking && !n->doing && !(n->reading && !n->given)) look_about(f, i); } for (i = 0; i < f->capacity; i++) if (awake(f, i)) done += v4_exec_step_word(&f->place[i].node->n, &f->place[i].node->es, &f->place[i].node->h); + /* a node that fetched which wires have a message has seen what had come */ + for (i = 0; i < f->capacity; i++) + if (awake(f, i) && f->place[i].node->n.have_fetched) { + for (k = 0; k < V4_PORTS; k++) + if (f->place[i].wire[k].rx) f->place[i].wire[k].rx->arrived = 0; + f->place[i].node->n.have_fetched = 0; + } + + /* the operations on the wires, each whole; the two that wait are looked at last, so that + * what every other node has put or taken this step has been, whatever their numbers */ + for (i = 0; i < f->capacity; i++) + if (awake(f, i) && f->place[i].node->n.doing && f->place[i].node->n.do_op != 8 && f->place[i].node->n.do_op != 9) { + v4_cell how; + (void)operate(f, i, &how); + v4_node_done(&f->place[i].node->n, how); + done++; + } + for (i = 0; i < f->capacity; i++) + if (awake(f, i) && f->place[i].node->n.doing) { + v4_cell how; + if (operate(f, i, &how)) { v4_node_done(&f->place[i].node->n, how); done++; } + } + for (i = 0; i < f->capacity; i++) if (awake(f, i) && f->place[i].node->n.asking) done += hand_over_write(f, i); for (i = 0; i < f->capacity; i++) diff --git a/v4/src/node.c b/v4/src/node.c index 665310b6..99354623 100644 --- a/v4/src/node.c +++ b/v4/src/node.c @@ -87,13 +87,17 @@ void v4_node_store(v4_node *n, v4_cell addr, v4_cell value) * neighbour has taken it. */ { int port = v4_node_port_index(n, addr); + if (port == (int)V4_PORT_WIRE) { n->wire = (value >= 0 && value < (v4_cell)V4_PORTS) ? (unsigned)value : V4_PORTS; return; } + if (port == (int)V4_PORT_A) { n->wire_a = value; return; } + if (port == (int)V4_PORT_B) { n->wire_b = value; return; } + if (port == (int)V4_PORT_DO) { n->do_op = value; n->doing = 1; return; } if (port >= 0 && port < (int)V4_PORTS) { n->request = value; n->ask_port = (unsigned)port; n->asking = 1; return; } - if (port >= 0) { v4_node_fault(n, V4_FAULT_ADDRESS, addr); return; } /* "any" and "which port" are not written to */ + if (port >= 0) { v4_node_fault(n, V4_FAULT_ADDRESS, addr); return; } /* "any", "which port", How and Have are not written to */ } /* The stack registers: a store empties the stack. -1 when not attached. */ if (addr == n->dstack_reg) { v4_dstack_clear(&n->ds); return; } @@ -122,6 +126,12 @@ v4_cell v4_node_fetch(v4_node *n, v4_cell addr) if (port == (int)V4_PORTS + 1) return (v4_cell)n->last_from; if (port == (int)V4_PORTS + 2) { n->since_look = 0; return (v4_cell)n->writers; } if (port == (int)V4_PORTS + 3) { n->since_look = 0; return (v4_cell)n->readers; } + if (port == (int)V4_PORT_WIRE) return (v4_cell)n->wire; + if (port == (int)V4_PORT_A) return n->wire_a; + if (port == (int)V4_PORT_B) return n->wire_b; + if (port == (int)V4_PORT_DO) return n->do_op; + if (port == (int)V4_PORT_HOW) return n->how; + if (port == (int)V4_PORT_HAVE) { n->have_fetched = 1; return (v4_cell)n->have; } if (port >= 0) { /* the word its neighbour wrote; v4_node_read_ready has said it is there */ if (!n->given) return 0; @@ -180,6 +190,14 @@ void v4_node_port_attach(v4_node *n, v4_cell base) n->writers = 0; n->readers = 0; n->since_look = 0; + n->wire = 0; + n->wire_a = 0; + n->wire_b = 0; + n->doing = 0; + n->do_op = 0; + n->how = 0; + n->have = 0; + n->have_fetched = 0; } void v4_node_port_status(v4_node *n, unsigned writers, unsigned readers) @@ -190,7 +208,7 @@ void v4_node_port_status(v4_node *n, unsigned writers, unsigned readers) int v4_node_port_index(const v4_node *n, v4_cell addr) { - if (n->port < 0 || addr < n->port || addr > n->port + (v4_cell)V4_PORTS + 3) return -1; + if (n->port < 0 || addr < n->port || addr > n->port + (v4_cell)V4_PORT_HAVE) return -1; return (int)(addr - n->port); } @@ -225,6 +243,22 @@ void v4_node_port_served(v4_node *n) n->since_look++; } +int v4_node_doing(const v4_node *n) +{ + return n->doing; +} + +void v4_node_done(v4_node *n, v4_cell how) +{ + n->how = how; + n->doing = 0; +} + +void v4_node_have(v4_node *n, unsigned mask) +{ + n->have = mask; +} + unsigned v4_node_words_since_look(const v4_node *n) { return n->since_look; diff --git a/v4/src/wire.c b/v4/src/wire.c new file mode 100644 index 00000000..41c38d0a --- /dev/null +++ b/v4/src/wire.c @@ -0,0 +1,124 @@ +/* wire.c -- a queue of whole messages. See wire.h. + * + * Nothing here uses the C library: the bare-metal kernel links this file. + */ +#include "v4/wire.h" +#include "v4/message.h" + +void v4_wire_reset(v4_wire_queue *q) +{ + unsigned i; + for (i = 0; i < V4_WIRE_CELLS; i++) q->cell[i] = 0; + q->head = 0; + q->used = 0; + q->mark = 0; + q->arrived = 0; +} + +unsigned v4_wire_cells(v4_cell length) +{ + if (length < 0 || length > (v4_cell)V4_MSG_MAX_CHARS) return 0; + return V4_WIRE_HEADER + ((unsigned)length + 3u) / 4u; +} + +/* the cell `off` cells from the front */ +static unsigned slot(const v4_wire_queue *q, unsigned off) +{ + unsigned s = q->head + off; + return s >= V4_WIRE_CELLS ? s - V4_WIRE_CELLS : s; +} + +/* the cells the message `off` cells from the front takes */ +static unsigned size_at(const v4_wire_queue *q, unsigned off) +{ + return v4_wire_cells(q->cell[slot(q, off + 6u)]); +} + +int v4_wire_room(const v4_wire_queue *q, unsigned cells) +{ + return cells <= V4_WIRE_CELLS - q->used ? V4_WIRE_DONE : V4_WIRE_NO_ROOM; +} + +/* `n` cells, already checked to fit, onto the back */ +static unsigned back(v4_wire_queue *q, unsigned n) +{ + unsigned at = q->used; + q->used += n; + q->arrived = 1; + return at; +} + +int v4_wire_put(v4_wire_queue *q, const v4_cell *header7, const v4_cell *text) +{ + unsigned n = v4_wire_cells(header7[6]), at, i; + if (n == 0) return V4_WIRE_NOT_A_MESSAGE; + if (v4_wire_room(q, n) != V4_WIRE_DONE) return V4_WIRE_NO_ROOM; + at = back(q, n); + for (i = 0; i < V4_WIRE_HEADER; i++) q->cell[slot(q, at + i)] = header7[i]; + for (; i < n; i++) q->cell[slot(q, at + i)] = text[i - V4_WIRE_HEADER]; + return V4_WIRE_DONE; +} + +void v4_wire_first(v4_wire_queue *q) +{ + q->mark = 0; +} + +int v4_wire_next(v4_wire_queue *q) +{ + if (q->mark >= q->used) return V4_WIRE_NO_MESSAGE; + q->mark += size_at(q, q->mark); + return q->mark < q->used ? V4_WIRE_DONE : V4_WIRE_NO_MESSAGE; +} + +int v4_wire_look(const v4_wire_queue *q, v4_cell *header7) +{ + unsigned i; + if (q->mark >= q->used) return V4_WIRE_NO_MESSAGE; + for (i = 0; i < V4_WIRE_HEADER; i++) header7[i] = q->cell[slot(q, q->mark + i)]; + return V4_WIRE_DONE; +} + +/* the marked message is off the queue; the mark is at the one that followed */ +static void remove_marked(v4_wire_queue *q) +{ + unsigned n = size_at(q, q->mark), i; + if (q->mark == 0) { + q->head = slot(q, n); + } else { + for (i = q->mark; i + n < q->used; i++) q->cell[slot(q, i)] = q->cell[slot(q, i + n)]; + } + q->used -= n; +} + +int v4_wire_take(v4_wire_queue *q, v4_cell *header7, v4_cell *text, unsigned text_cap) +{ + unsigned n, i; + if (q->mark >= q->used) return V4_WIRE_NO_MESSAGE; + n = size_at(q, q->mark); + if (n - V4_WIRE_HEADER > text_cap) return V4_WIRE_NOT_A_MESSAGE; + for (i = 0; i < V4_WIRE_HEADER; i++) header7[i] = q->cell[slot(q, q->mark + i)]; + for (; i < n; i++) text[i - V4_WIRE_HEADER] = q->cell[slot(q, q->mark + i)]; + remove_marked(q); + return V4_WIRE_DONE; +} + +int v4_wire_drop(v4_wire_queue *q) +{ + if (q->mark >= q->used) return V4_WIRE_NO_MESSAGE; + remove_marked(q); + return V4_WIRE_DONE; +} + +int v4_wire_move(v4_wire_queue *from, v4_wire_queue *to) +{ + unsigned n, at, i; + if (from->mark >= from->used) return V4_WIRE_NO_MESSAGE; + n = size_at(from, from->mark); + if (from == to || v4_wire_room(to, n) != V4_WIRE_DONE) return V4_WIRE_NO_ROOM; + at = back(to, n); + for (i = 0; i < n; i++) to->cell[slot(to, at + i)] = from->cell[slot(from, from->mark + i)]; + to->cell[slot(to, at + 3u)] = (v4_cell)(from->cell[slot(from, from->mark + 3u)] - 1); + remove_marked(from); + return V4_WIRE_DONE; +} diff --git a/v4/tests/host_map.h b/v4/tests/host_map.h index 98e3cca2..6278c03b 100644 --- a/v4/tests/host_map.h +++ b/v4/tests/host_map.h @@ -73,7 +73,7 @@ #define OWED (BUF0_W - 112) /* those: for each, the node it is owed to and the node the refused message was for */ #define ACL_HOOK (BUF0_W - 6) /* the xt of the access control recheck word, or 0 */ #define LINE_STATUS (BUF0_W - 9) /* how the last line ended: 0 QUIT, 1 completed, 2 an error */ -#define PORT (BUF0_W - 32) /* the node's ports (node.h): V4_PORTS of them, then "any port" and "which port". Port 0 is where its requests go. */ +#define PORT (BUF0_W - 140) /* the node's ports (node.h): V4_PORTS of them, then "any port", "which port" and the rest as far as V4_PORT_HAVE. Port 0 is where its requests go. */ #define CONSOLE (BUF0_W - 16) /* the node what this one prints is sent to; 0: whoever sent the text being served */ #define ROUTE_COUNT (BUF0_W - 17) /* how many entries the table of ways holds */ #define ROUTE_DEFAULT (BUF0_W - 18) /* the port address for a node not in the table, or 0: there is none */ @@ -105,8 +105,11 @@ #define LOG_LEVEL (BUF0_W - 5) /* the level at or below which a message is printed */ #define BOOT_CELLS (BVARS + 14) /* (BOOT): DP and LATEST as the loader left them, 2 cells */ #define BUF0_W (BVARS - (v4_cell)(V4_BLOCK_SLOTS * V4_BLOCK_CELLS)) /* the block window: the kernel's slots, 256 cells each (v4/blocks.h) */ -/* the ports' ten addresses and the two that follow them end below BUF0_W - 20 */ -typedef char host_map_ports_fit[(V4_PORTS <= 8u) ? 1 : -1]; +/* The ports and the addresses after them, through V4_PORT_HAVE (node.h, MESH.md 7d.3), are not memory and must be where + * no variable is: after the output buffer, which ends at BUF0_W - 144, and before A_FOUND, the first variable above it. + * With 8 ports that is 18 addresses, BUF0_W - 140 to BUF0_W - 123. (They were once among the variables, and what was + * stored to the variables was stored to ports.) */ +typedef char host_map_ports_fit[(PORT >= OUT_W + OUT_CELLS && PORT + (v4_cell)V4_PORT_HAVE < A_FOUND) ? 1 : -1]; /* Nothing printed and no message waiting: a node's variables for them, as at switch-on. */ #define HOST_MESSAGES_EMPTY(n) do { (n)->mem[OUT_PTR] = OUT_W; (n)->mem[MQ_HEAD] = MQ_W; (n)->mem[MQ_TAIL] = MQ_W; (n)->mem[MQ_COUNT] = 0; } while (0) #define QVARS (BUF0_W - 4) /* (Q): quit.v4, system.v4 and blocks.v4, 4 cells */ diff --git a/v4/tests/test_fabric.c b/v4/tests/test_fabric.c index 7af08fe6..d0f61b6f 100644 --- a/v4/tests/test_fabric.c +++ b/v4/tests/test_fabric.c @@ -15,7 +15,13 @@ 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 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 */ @@ -105,6 +111,60 @@ static unsigned capsule(v4_cell *out, const v4_cell *words, unsigned count, v4_c 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); +} + int main(void) { unsigned a, b, c, i, steps; @@ -423,6 +483,342 @@ int main(void) 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"); } + /* ---- 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_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)"); + 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); + 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 && ask(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 && ask(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"); + + /* 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"); + (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(ask(b, 3, 400, 420, 4) == 0, "the reader drops one message (which takes a few cells)"); + CHECK(ND(a)->n.doing || f.place[a].wire[0].tx->used + 200 <= V4_WIRE_CELLS, "woken when the reader has taken enough, not before"); + while (ND(a)->n.doing && f.place[b].wire[3].rx->used > 0) { + (void)ask(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"); + /* 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"); + + /* 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(ask(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 && ask(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); + 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, "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 && ask(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; } diff --git a/v4/tests/test_host_quit.c b/v4/tests/test_host_quit.c index 42662519..600c25ae 100644 --- a/v4/tests/test_host_quit.c +++ b/v4/tests/test_host_quit.c @@ -1136,19 +1136,24 @@ int main(void) /* A flush of the output buffer that ends in a fault leaves the buffer full: what is printed next -- the * error's own message -- must not go past its end (MESH.md 7c.6). */ { + /* The cells just after the buffer that are still plain memory: the ports follow them (host_map.h), and a + * store to a port is not a store to memory -- it blocks the node, which is seen below, as is the pointer + * itself: it is past the end only if something was put there. */ + const unsigned plain = (unsigned)(PORT - (OUT_W + OUT_CELLS)); unsigned over = 0, worst = 0, at; for (k = most > 6 ? most - 6 : 0; k <= most + 3; k++) { char line[4 * V4_DATA_DEPTH + 16]; size_t n_at = 0; unsigned m; boot_bare(); - for (m = 0; m < 28; m++) n.mem[OUT_W + 256 + m] = 0; + for (m = 0; m < plain; m++) n.mem[OUT_W + OUT_CELLS + m] = 0; for (m = 0; m < k; m++) n_at += (size_t)snprintf(line + n_at, sizeof line - n_at, "1 "); snprintf(line + n_at, sizeof line - n_at, "\n"); (void)say(line); (void)say("WORDS\n"); - for (at = 0; at < 28; at++) if (n.mem[OUT_W + 256 + at] != 0) { over++; worst = k; break; } - if (n.mem[OUT_PTR] > OUT_W + 256) { over++; worst = k; } + for (at = 0; at < plain; at++) if (n.mem[OUT_W + OUT_CELLS + at] != 0) { over++; worst = k; break; } + if (n.mem[OUT_PTR] > OUT_W + OUT_CELLS) { over++; worst = k; } + if (n.asking || n.doing) { over++; worst = k; } /* it stored to a port */ CHECK(strcmp(say("1 2 + .\n"), "3 ok\nok> ") == 0, "WORDS with %u values on the stack: the node goes on", k); } CHECK(over == 0, "nothing was ever put past the end of the output buffer (it was, with %u values on the stack)", worst); diff --git a/v4/tests/test_wire.c b/v4/tests/test_wire.c new file mode 100644 index 00000000..46918da7 --- /dev/null +++ b/v4/tests/test_wire.c @@ -0,0 +1,294 @@ +/* test_wire.c -- a queue of whole messages, one way along a wire, with no + * node and no fabric: docs/v4.0.0/MESH.md section 7d.2. + * + * The first attempt at messages that wait (7c.6) moved a message a word at a + * time and left both ends out of step when a fault came in the middle. So + * what is tested here, over and over, is that a queue which answers anything + * but "done" is exactly as it was. + */ +#include "v4/wire.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) + +static v4_wire_queue q, other, before, before_other; + +/* A message of `len` characters: seven words, then its text. */ +static void make(v4_cell *h, v4_cell *t, v4_cell len, v4_cell seed) +{ + unsigned i, n = len > 0 ? (unsigned)(len + 3) / 4u : 0; + for (i = 0; i < 7; i++) h[i] = seed * 100 + (v4_cell)i; + h[6] = len; + for (i = 0; i < n; i++) t[i] = seed * 1000 + 17 * (v4_cell)i + 5; +} + +/* 1 if the message taken back is the one made from `len` and `seed` */ +static int same(const v4_cell *h, const v4_cell *t, v4_cell len, v4_cell seed) +{ + v4_cell wh[7], wt[V4_WIRE_CELLS]; + unsigned i, n = (unsigned)(len + 3) / 4u; + make(wh, wt, len, seed); + for (i = 0; i < 7; i++) if (h[i] != wh[i]) return 0; + for (i = 0; i < n; i++) if (t[i] != wt[i]) return 0; + return 1; +} + +static void put_ok(v4_wire_queue *w, v4_cell len, v4_cell seed, int *ok) +{ + v4_cell h[7], t[V4_WIRE_CELLS]; + make(h, t, len, seed); + if (v4_wire_put(w, h, t) != V4_WIRE_DONE) *ok = 0; +} + +int main(void) +{ + static const v4_cell lens[] = { 0, 1, 4, 5, 1024 }; + v4_cell h[7], t[V4_WIRE_CELLS], h2[7], t2[V4_WIRE_CELLS]; + unsigned i, k, round, count, wrapped, cap_cells; + int ok; + + printf("v4 wire tests: V4_CELL_BITS=%d, %d cells to a queue\n", V4_CELL_BITS, V4_WIRE_CELLS); + + /* ---- an empty queue ---- */ + v4_wire_reset(&q); + CHECK(v4_wire_room(&q, 1024) == V4_WIRE_DONE, "an empty queue has room for 1,024"); + CHECK(v4_wire_room(&q, 1025) == V4_WIRE_NO_ROOM, "and not for 1,025"); + memcpy(&before, &q, sizeof q); + CHECK(v4_wire_look(&q, h) == V4_WIRE_NO_MESSAGE, "look at nothing answers 3"); + CHECK(v4_wire_take(&q, h, t, V4_WIRE_CELLS) == V4_WIRE_NO_MESSAGE, "take answers 3"); + CHECK(v4_wire_drop(&q) == V4_WIRE_NO_MESSAGE, "drop answers 3"); + CHECK(v4_wire_next(&q) == V4_WIRE_NO_MESSAGE, "next answers 3"); + CHECK(memcmp(&before, &q, sizeof q) == 0, "and none of them changed it"); + CHECK(q.mark == q.used && q.used == 0 && q.arrived == 0, "its mark is at no message, and nothing has arrived"); + + /* ---- the cells a message takes ---- */ + CHECK(v4_wire_cells(0) == 7 && v4_wire_cells(1) == 8 && v4_wire_cells(4) == 8 && v4_wire_cells(5) == 9 && v4_wire_cells(1024) == 263, + "a message takes 7 cells and one for each four characters: 7 8 8 9 and 263"); + CHECK(v4_wire_cells(-1) == 0 && v4_wire_cells(1025) == 0, "a length of -1 or 1,025 is not a message"); + + /* ---- lengths of 0, 1, 4, 5 and 1,024, each put and taken back ---- */ + for (k = 0; k < sizeof lens / sizeof lens[0]; k++) { + v4_wire_reset(&q); + make(h, t, lens[k], (v4_cell)k + 1); + CHECK(v4_wire_put(&q, h, t) == V4_WIRE_DONE, "a message of %ld characters is put", (long)lens[k]); + CHECK(q.used == v4_wire_cells(lens[k]), "it takes %u cells: %u", v4_wire_cells(lens[k]), q.used); + memset(h2, 0, sizeof h2); memset(t2, 0, sizeof t2); + CHECK(v4_wire_look(&q, h2) == V4_WIRE_DONE && memcmp(h, h2, sizeof h2) == 0, "look gives its seven words"); + CHECK(q.used == v4_wire_cells(lens[k]), "and takes nothing"); + CHECK(v4_wire_take(&q, h2, t2, V4_WIRE_CELLS) == V4_WIRE_DONE && same(h2, t2, lens[k], (v4_cell)k + 1), + "taken, its seven words and every word of its text are what was put"); + CHECK(q.used == 0 && q.mark == 0, "and the queue is empty again, its mark at no message"); + } + + /* ---- round the end of the array: fill and empty it three times, with lengths that do not divide 1,024 ---- */ + v4_wire_reset(&q); + wrapped = 0; + for (round = 0; round < 3; round++) { + static const v4_cell mix[] = { 100, 37, 333, 1, 250, 58, 0, 411 }; + v4_cell seed[200], len[200]; + count = 0; + for (;;) { + v4_cell l = mix[(count + round * 3) % 8]; + make(h, t, l, (v4_cell)(round * 50 + count + 1)); + if (q.head + v4_wire_cells(l) > V4_WIRE_CELLS && q.used + v4_wire_cells(l) <= V4_WIRE_CELLS) wrapped++; + if (v4_wire_put(&q, h, t) != V4_WIRE_DONE) break; + seed[count] = (v4_cell)(round * 50 + count + 1); + len[count++] = l; + } + CHECK(count > 5 && count < 200 && q.used <= V4_WIRE_CELLS, "round %u: %u messages fill it, %u cells", round, count, q.used); + ok = 1; + for (i = 0; i < count; i++) { + if (v4_wire_take(&q, h2, t2, V4_WIRE_CELLS) != V4_WIRE_DONE || !same(h2, t2, len[i], seed[i])) ok = 0; + } + CHECK(ok && q.used == 0, "round %u: they come out whole and in the order they went in", round); + } + CHECK(wrapped > 0, "and some of them went across the end of the array: %u", wrapped); + + /* ---- a put that does not fit changes nothing ---- */ + v4_wire_reset(&q); + ok = 1; + put_ok(&q, 1024, 1, &ok); /* 263 */ + put_ok(&q, 1024, 2, &ok); /* 526 */ + put_ok(&q, 1024, 3, &ok); /* 789 */ + CHECK(ok && q.used == 789, "three long messages take 789 cells"); + CHECK(v4_wire_room(&q, 235) == V4_WIRE_DONE && v4_wire_room(&q, 236) == V4_WIRE_NO_ROOM, "which leaves room for 235 and not 236"); + make(h, t, 4 * (235 - 7) + 1, 9); /* 236 cells */ + memcpy(&before, &q, sizeof q); + CHECK(v4_wire_put(&q, h, t) == V4_WIRE_NO_ROOM, "a message of 236 cells is refused, 1"); + CHECK(memcmp(&before, &q, sizeof q) == 0, "and the queue is exactly as it was: head, used, mark and every cell"); + make(h, t, 4 * (235 - 7), 9); /* 235 cells: the last that fits */ + CHECK(v4_wire_put(&q, h, t) == V4_WIRE_DONE && q.used == V4_WIRE_CELLS, "one of 235 cells fits, and fills it"); + CHECK(v4_wire_room(&q, 0) == V4_WIRE_DONE && v4_wire_room(&q, 1) == V4_WIRE_NO_ROOM, "a full queue has room for none"); + /* with 200 cells free: a short message goes, a long one is refused */ + v4_wire_reset(&q); + ok = 1; + put_ok(&q, 1024, 1, &ok); put_ok(&q, 1024, 2, &ok); put_ok(&q, 1024, 3, &ok); + put_ok(&q, 4 * (35 - 7), 4, &ok); + CHECK(ok && V4_WIRE_CELLS - q.used == 200, "a queue with 200 cells free: %u", V4_WIRE_CELLS - q.used); + make(h, t, 4 * (201 - 7), 5); + memcpy(&before, &q, sizeof q); + CHECK(v4_wire_put(&q, h, t) == V4_WIRE_NO_ROOM && memcmp(&before, &q, sizeof q) == 0, "refuses a message of 201 cells and is as it was"); + make(h, t, 4 * (200 - 7), 5); + CHECK(v4_wire_put(&q, h, t) == V4_WIRE_DONE, "and takes one of 200"); + v4_wire_reset(&q); + put_ok(&q, 1024, 1, &ok); put_ok(&q, 1024, 2, &ok); put_ok(&q, 1024, 3, &ok); put_ok(&q, 4 * (35 - 7), 4, &ok); + make(h, t, 40, 6); + CHECK(v4_wire_put(&q, h, t) == V4_WIRE_DONE && q.used == 824 + v4_wire_cells(40), "and a short one, of 40 characters"); + + /* ---- not a message ---- */ + v4_wire_reset(&q); + ok = 1; + put_ok(&q, 100, 1, &ok); put_ok(&q, 7, 2, &ok); + memcpy(&before, &q, sizeof q); + make(h, t, 10, 3); + h[6] = -1; + CHECK(v4_wire_put(&q, h, t) == V4_WIRE_NOT_A_MESSAGE, "a length of -1 answers 4"); + CHECK(memcmp(&before, &q, sizeof q) == 0, "and changes nothing"); + h[6] = 1025; + CHECK(v4_wire_put(&q, h, t) == V4_WIRE_NOT_A_MESSAGE, "a length of 1,025 answers 4"); + CHECK(memcmp(&before, &q, sizeof q) == 0, "and changes nothing"); + v4_wire_reset(&q); + ok = 1; + put_ok(&q, 1024, 1, &ok); put_ok(&q, 1024, 2, &ok); put_ok(&q, 1024, 3, &ok); put_ok(&q, 900, 4, &ok); + memcpy(&before, &q, sizeof q); + h[6] = 1025; + CHECK(v4_wire_put(&q, h, t) == V4_WIRE_NOT_A_MESSAGE && memcmp(&before, &q, sizeof q) == 0, + "a length that is not a message is 4 and not 1 even when the queue is nearly full"); + + /* ---- first and next walk the messages; take and drop of the middle one ---- */ + v4_wire_reset(&q); + ok = 1; + put_ok(&q, 20, 1, &ok); put_ok(&q, 33, 2, &ok); put_ok(&q, 9, 3, &ok); + v4_wire_first(&q); + CHECK(ok && q.mark == 0 && v4_wire_look(&q, h) == V4_WIRE_DONE && h[0] == 100, "first puts the mark at the front message"); + CHECK(v4_wire_next(&q) == V4_WIRE_DONE && v4_wire_look(&q, h) == V4_WIRE_DONE && h[0] == 200, "next, at the second"); + CHECK(v4_wire_next(&q) == V4_WIRE_DONE && v4_wire_look(&q, h) == V4_WIRE_DONE && h[0] == 300, "next, at the third"); + memcpy(&before, &q, sizeof q); + CHECK(v4_wire_next(&q) == V4_WIRE_NO_MESSAGE, "and then 3"); + CHECK(q.mark == q.used && v4_wire_look(&q, h) == V4_WIRE_NO_MESSAGE, "the mark is then at no message"); + CHECK(v4_wire_next(&q) == V4_WIRE_NO_MESSAGE && q.mark == q.used, "and stays there"); + + v4_wire_first(&q); + (void)v4_wire_next(&q); /* the middle one */ + CHECK(v4_wire_take(&q, h, t, V4_WIRE_CELLS) == V4_WIRE_DONE && same(h, t, 33, 2), "take of the middle one gives it"); + CHECK(q.used == v4_wire_cells(20) + v4_wire_cells(9) && v4_wire_look(&q, h) == V4_WIRE_DONE && h[0] == 300, "the mark is at the third"); + v4_wire_first(&q); + CHECK(v4_wire_take(&q, h, t, V4_WIRE_CELLS) == V4_WIRE_DONE && same(h, t, 20, 1), "the first is still first"); + CHECK(v4_wire_take(&q, h, t, V4_WIRE_CELLS) == V4_WIRE_DONE && same(h, t, 9, 3), "and the third follows it, whole"); + CHECK(q.used == 0, "and nothing is left"); + + v4_wire_reset(&q); + ok = 1; + put_ok(&q, 20, 1, &ok); put_ok(&q, 33, 2, &ok); put_ok(&q, 9, 3, &ok); + v4_wire_first(&q); + (void)v4_wire_next(&q); + CHECK(v4_wire_drop(&q) == V4_WIRE_DONE && v4_wire_look(&q, h) == V4_WIRE_DONE && h[0] == 300, "drop of the middle one: the mark is at the third"); + v4_wire_first(&q); + CHECK(v4_wire_take(&q, h, t, V4_WIRE_CELLS) == V4_WIRE_DONE && same(h, t, 20, 1) && v4_wire_take(&q, h, t, V4_WIRE_CELLS) == V4_WIRE_DONE && same(h, t, 9, 3) && q.used == 0, + "the first and third are left, in order"); + /* the last one, dropped, leaves the mark at no message */ + ok = 1; + put_ok(&q, 20, 1, &ok); put_ok(&q, 33, 2, &ok); + v4_wire_first(&q); + (void)v4_wire_next(&q); + CHECK(v4_wire_drop(&q) == V4_WIRE_DONE && q.mark == q.used && q.used == v4_wire_cells(20), "drop of the last: the mark is at no message"); + + /* middle take across the end of the array */ + v4_wire_reset(&q); + ok = 1; + put_ok(&q, 1024, 1, &ok); put_ok(&q, 1024, 2, &ok); put_ok(&q, 1024, 3, &ok); + v4_wire_first(&q); + (void)v4_wire_take(&q, h, t, V4_WIRE_CELLS); /* head is at 263 */ + put_ok(&q, 1024, 4, &ok); put_ok(&q, 77, 5, &ok); /* the last of them goes round */ + CHECK(ok && q.head + q.used > V4_WIRE_CELLS, "messages are across the end of the array"); + v4_wire_first(&q); + (void)v4_wire_next(&q); (void)v4_wire_next(&q); /* the 1,024 that came last but one */ + CHECK(v4_wire_take(&q, h, t, V4_WIRE_CELLS) == V4_WIRE_DONE && same(h, t, 1024, 4), "the middle one is taken, and the one after it moves down across the end"); + CHECK(v4_wire_look(&q, h) == V4_WIRE_DONE && h[0] == 500, "the mark is at the one that followed it"); + v4_wire_first(&q); + ok = 1; + ok &= v4_wire_take(&q, h, t, V4_WIRE_CELLS) == V4_WIRE_DONE && same(h, t, 1024, 2); + ok &= v4_wire_take(&q, h, t, V4_WIRE_CELLS) == V4_WIRE_DONE && same(h, t, 1024, 3); + ok &= v4_wire_take(&q, h, t, V4_WIRE_CELLS) == V4_WIRE_DONE && same(h, t, 77, 5); + CHECK(ok && q.used == 0, "and the others come out whole, in order"); + + /* ---- move ---- */ + v4_wire_reset(&q); v4_wire_reset(&other); + ok = 1; + put_ok(&q, 20, 1, &ok); put_ok(&q, 33, 2, &ok); put_ok(&other, 5, 7, &ok); + other.arrived = 0; q.arrived = 0; + v4_wire_first(&q); + (void)v4_wire_next(&q); + CHECK(ok && v4_wire_move(&q, &other) == V4_WIRE_DONE, "the marked message moves to another queue"); + CHECK(other.arrived != 0, "which has had a message arrive"); + CHECK(v4_wire_look(&q, h) == V4_WIRE_NO_MESSAGE && q.used == v4_wire_cells(20), "it is off the first, whose mark is at no message"); + v4_wire_first(&other); + CHECK(v4_wire_take(&other, h, t, V4_WIRE_CELLS) == V4_WIRE_DONE && same(h, t, 5, 7), "the other's own message is first"); + CHECK(v4_wire_take(&other, h, t, V4_WIRE_CELLS) == V4_WIRE_DONE && h[3] == 203 - 1 && h[0] == 200 && h[6] == 33 && h[5] == 205, + "and the moved one is at the back, its fourth word one fewer and the rest as they were"); + { + v4_cell wh[7], wt[V4_WIRE_CELLS]; + unsigned n; + make(wh, wt, 33, 2); + for (n = 0; n < 9 && t[n] == wt[n]; n++) { } + CHECK(n == 9, "and every word of its text"); + } + /* no room in the other: 1, and neither changes */ + v4_wire_reset(&q); v4_wire_reset(&other); + ok = 1; + put_ok(&q, 100, 1, &ok); + put_ok(&other, 1024, 2, &ok); put_ok(&other, 1024, 3, &ok); put_ok(&other, 1024, 4, &ok); + put_ok(&other, 4 * (226 - 7), 5, &ok); + v4_wire_first(&q); + memcpy(&before, &q, sizeof q); memcpy(&before_other, &other, sizeof other); + CHECK(ok && V4_WIRE_CELLS - other.used < v4_wire_cells(100), "the other has %u cells free, a message of 100 characters takes %u", V4_WIRE_CELLS - other.used, v4_wire_cells(100)); + CHECK(v4_wire_move(&q, &other) == V4_WIRE_NO_ROOM, "move to a queue with no room answers 1"); + CHECK(memcmp(&before, &q, sizeof q) == 0 && memcmp(&before_other, &other, sizeof other) == 0, "and changes neither"); + v4_wire_reset(&other); + v4_wire_reset(&q); + CHECK(v4_wire_move(&q, &other) == V4_WIRE_NO_MESSAGE && other.used == 0, "move with nothing marked answers 3"); + + /* ---- take with too little room for the text ---- */ + v4_wire_reset(&q); + ok = 1; + put_ok(&q, 100, 1, &ok); /* 25 cells of text */ + memcpy(&before, &q, sizeof q); + memset(h2, 0, sizeof h2); memset(t2, 0, sizeof t2); + cap_cells = 24; + CHECK(v4_wire_take(&q, h2, t2, cap_cells) == V4_WIRE_NOT_A_MESSAGE, "take with room for 24 cells of a text of 25 answers 4"); + CHECK(memcmp(&before, &q, sizeof q) == 0, "the queue is as it was"); + ok = 1; + for (i = 0; i < 7; i++) if (h2[i] != 0) ok = 0; + for (i = 0; i < 25; i++) if (t2[i] != 0) ok = 0; + CHECK(ok, "and nothing was copied out"); + CHECK(v4_wire_take(&q, h2, t2, 25) == V4_WIRE_DONE && same(h2, t2, 100, 1), "with room for 25 it is taken"); + v4_wire_reset(&q); + ok = 1; + put_ok(&q, 0, 1, &ok); + CHECK(ok && v4_wire_take(&q, h2, 0, 0) == V4_WIRE_DONE, "a message with no text needs no room for any"); + + /* ---- arrived ---- */ + v4_wire_reset(&q); v4_wire_reset(&other); + CHECK(q.arrived == 0, "a queue that has had nothing has had nothing arrive"); + ok = 1; + put_ok(&q, 4, 1, &ok); + CHECK(ok && q.arrived != 0, "a put sets arrived"); + q.arrived = 0; + make(h, t, 4, 2); + h[6] = 2000; + (void)v4_wire_put(&q, h, t); + CHECK(q.arrived == 0, "a put that is refused does not"); + put_ok(&other, 4, 3, &ok); + other.arrived = 0; + v4_wire_first(&other); + CHECK(v4_wire_move(&other, &q) == V4_WIRE_DONE && q.arrived != 0, "nor does a move into it, when it is done, fail to"); + q.arrived = 0; + v4_wire_first(&q); + (void)v4_wire_take(&q, h, t, V4_WIRE_CELLS); + CHECK(q.arrived == 0, "a take does not set it"); + + printf(" %d checks, %d failures\n", checks, failures); + return failures != 0; +}