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 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-08 13:50:15 -04:00
co-authored by Claude Opus 5.5
parent 50be766e7b
commit d5cd64605d
14 changed files with 1267 additions and 22 deletions
+37 -1
View File
@@ -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
+46 -3
View File
@@ -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
+78
View File
@@ -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 */