The final review of step 6e, by a fresh reviewer told to break it, and what was ruled: fix them all, test first. Each has a test that failed before the mend. - Two texts from one node could be begun in the wrong order when room for their answers came between the node's look at the first and at the second. Texts whose answers go by the same port are begun in the order they came: (D-MASK), quit.v4. 102 of 700 timings were wrong before. - A word written with PORT! to a node that speaks whole messages, or to a console's port, blocked its writer for ever and cut off every node beyond it. It is error 18 now, whether that node is asleep, running or stuck: fabric.c, node.c. - A node asleep for room on one wire is woken when another of its wires is cut. - A line there was no room for on the lone node's wire was reported as a fault and ended the hosted program. It is Message refused, and the prompt comes back: V4_BOOT_LINE_REFUSED. hosted-check has the case; no bare-metal boot has typed it. - Ruled: text behind held output for the same port is refused though it would fit. MESH.md 7d.4 said otherwise and is corrected. - The removal test also cuts and re-makes a wire, and has an exchange in which output is held at a full wire: 26,814 disturbances in all. - MESH.md and v4/README.md no longer say that a stuck node holds up no other node, that only printing waits, or that no node is left waiting on one that is gone; 7d.6 lists the limits and what the products do that they did not before. make -C v4 test and sanitize at both widths, hosted-check, lint. Three bare-metal boots: logs/20261013-194827 (amd64), -195131 (aarch64), -195518 (riscv64). POST 538 of 538, word_count=317, dict_hash=0xe5a57f3eb72ff94a on all six; the three sessions are the same character for character. The boots were built with the working tree's uncommitted change to kernel/Makefile that adds the ISA to the kernel's version line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
181 lines
9.7 KiB
C
181 lines
9.7 KiB
C
/* fabric.h -- the nodes there are, how their ports are wired, and time
|
|
* passing for all of them at once. docs/v4.0.0/MESH.md 4.3.
|
|
*
|
|
* On the fabric every node runs at the same time and a node with nothing to
|
|
* do is blocked at a port. Here that is: every node that is awake and not
|
|
* blocked executes one instruction word; then every operation on a whole
|
|
* message that a node asked for is done (WHOLE MESSAGES, below); and then
|
|
* every word written that has a reader waiting on the other end of its wire
|
|
* is handed over. There is no choice of whose turn it is.
|
|
*
|
|
* THE GEOMETRY IS DATA. Which port connects to what is a table, and it can
|
|
* be changed while the nodes run; nodes can be added and removed while they
|
|
* run. Nothing here knows what shape the wiring makes.
|
|
*
|
|
* Nothing here knows what the nodes are for, either: no message, no
|
|
* capsule, no StarForth. And nothing here uses the C library: whoever owns
|
|
* the fabric supplies the memory for it and for each node.
|
|
*/
|
|
#ifndef V4_FABRIC_H
|
|
#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. */
|
|
typedef struct {
|
|
int (*take)(void *self, v4_cell value); /* the node wrote `value`: 1 if it is taken, 0 if not yet */
|
|
int (*give)(void *self, v4_cell *value); /* the node is reading: 1 with a word in *value, 0 if not yet */
|
|
void *self;
|
|
} v4_device;
|
|
|
|
/* One end of a wire. */
|
|
enum { V4_WIRE_NONE = 0, V4_WIRE_NODE, V4_WIRE_DEVICE };
|
|
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
|
|
* these for every node there is to be. */
|
|
typedef struct {
|
|
v4_node n;
|
|
v4_exec_state es;
|
|
v4_heat h;
|
|
} v4_fabric_node;
|
|
|
|
/* A place in the fabric: a node, or nothing. */
|
|
typedef struct {
|
|
v4_fabric_node *node; /* 0: no node here */
|
|
int asleep;
|
|
int cut; /* not 0: a wire of this node's has been cut since it was last woken by that (operation 9) */
|
|
v4_wire wire[V4_PORTS];
|
|
} v4_place;
|
|
|
|
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_fabric;
|
|
|
|
/* 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
|
|
* left there: when it is blocked writing to, or reading from, one port that
|
|
* nothing is wired to -- because nothing ever was, or because what was
|
|
* there has been removed or the wire cut -- the error `code` is raised on
|
|
* it and it goes on (node.h, v4_node_port_gone). A node whose neighbour is
|
|
* asleep waits, and so does one reading "any port". With `code` 0, which
|
|
* is how a fabric begins, nothing is raised. */
|
|
void v4_fabric_gone_error(v4_fabric *f, v4_cell code);
|
|
|
|
/* A WORD NO ONE WILL READ. The same error is raised on a node that can take
|
|
* one and is blocked writing a word to a port whose other end will not read
|
|
* it: a device that takes no words (its `take` is 0: it speaks whole
|
|
* messages only), or a node that speaks whole messages -- one that has
|
|
* asked for a whole-message operation (below) since it was born, whatever
|
|
* it is doing now: asleep at its wires, running, stuck in a loop, or put to
|
|
* sleep by the fabric. Such a node does not read words, and before this a
|
|
* word written to one blocked its writer for ever (found by the final
|
|
* review of step 6e). A node that has asked for none yet may still read:
|
|
* the writer waits for it. That is a newborn node being sent its nucleus
|
|
* a word at a time, which executes from its ports and has asked nothing of
|
|
* a wire. */
|
|
|
|
/* More room: `places`, of `capacity` no less than before, takes over; every
|
|
* node keeps its number and its wiring. */
|
|
void v4_fabric_grow(v4_fabric *f, v4_place *places, unsigned capacity);
|
|
|
|
/* A node is born (node.h, v4_node_born): empty, its ports at `port_base`,
|
|
* wired to nothing, awake and blocked reading its ports. Returns its
|
|
* number, or -1 if there is no room. */
|
|
int v4_fabric_add(v4_fabric *f, v4_fabric_node *storage, v4_cell port_base);
|
|
|
|
/* A node is removed, whatever it was doing. Every wire to it is cut; a
|
|
* neighbour blocked on one of them stays blocked, as it would on a port
|
|
* with nothing there. Returns the storage it was given, or 0. */
|
|
v4_fabric_node *v4_fabric_remove(v4_fabric *f, unsigned id);
|
|
|
|
/* The node numbered `id`, or 0 if there is none. */
|
|
v4_fabric_node *v4_fabric_node_at(const v4_fabric *f, unsigned id);
|
|
|
|
/* Wire port `pa` of node `a` to port `pb` of node `b`, or to a device; or
|
|
* cut whatever is on it. Whatever was on a port before is cut, at both
|
|
* ends. Each returns 1, or 0 if a node or a port is not there. */
|
|
int v4_fabric_wire(v4_fabric *f, unsigned a, unsigned pa, unsigned b, unsigned pb);
|
|
int v4_fabric_wire_device(v4_fabric *f, unsigned a, unsigned pa, const v4_device *device);
|
|
/* THE KERNEL ON A PORT (MESH.md 7d.5, ruled 2026-10-08). A node asks its
|
|
* kernel by words written to a port, and the kernel takes no messages: its
|
|
* port is wired to the device as above but is given no queues. Words are
|
|
* handed over as for any device; every operation on whole messages there
|
|
* answers that no one is on the port, so that a message sent to it is
|
|
* refused at once and nothing waits for it. */
|
|
int v4_fabric_wire_kernel(v4_fabric *f, unsigned a, unsigned pa, const v4_device *device);
|
|
int v4_fabric_unwire(v4_fabric *f, unsigned a, unsigned pa);
|
|
|
|
/* A node that is asleep executes nothing, and nothing is handed to it or
|
|
* taken from it. It can be put to sleep and woken at any instruction
|
|
* word. */
|
|
void v4_fabric_sleep(v4_fabric *f, unsigned id);
|
|
void v4_fabric_wake(v4_fabric *f, unsigned id);
|
|
|
|
/* 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).
|
|
* Take, move and drop are done only on the message the node last looked at
|
|
* on that wire (wire.h, THE LOOK): if the mark is at any other -- the wire
|
|
* was cut and made again since -- they answer 3 and nothing is written.
|
|
* 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. 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.
|
|
* SLEEP FOR ROOM (9) answers 0 at once if the wire it names has the room,
|
|
* and 1 at once if it can never have it. Otherwise the node sleeps until a
|
|
* message has come, as for 8, or a message has come off ANY of the wires
|
|
* that lead from it -- taken, moved on or dropped by whoever reads that wire
|
|
* -- since it was last woken that way, or one of its wires has been cut;
|
|
* then it is answered 0 if the wire it named now has the room and 1 if not,
|
|
* and it asks which wire has what (10). A node may be waiting to put on several wires at once and can name
|
|
* only one. A wake-up that finds nothing to do is answered by asking
|
|
* again.
|
|
*
|
|
* 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
|
|
* nothing can happen until something outside changes. */
|
|
unsigned v4_fabric_step(v4_fabric *f);
|
|
|
|
#endif /* V4_FABRIC_H */
|