feat(v4.0.0): nodes that talk -- ports, a node born empty, and the fabric

MESH.md step 1, in the engine, which knows nothing of StarForth or of any
kernel.

- node: V4_PORTS ports (8), a build parameter; "any port" and the port the
  last such read came from; a read blocks until the neighbour writes, as a
  write blocks until the neighbour reads; v4_node_born: empty, P at "any
  port"
- exec: a fetch from a port -- @ @b @+ @p, or of an instruction word when P
  is a port -- waits for a word; a node executes what arrives at a port
  without advancing P; a blocked node goes on from the slot it stopped at
- fabric: the nodes there are and the table of how their ports are wired,
  both changed while the nodes run; devices on a port; asleep and awake; a
  step is every unblocked node executing one instruction word, then every
  write with a reader waiting being handed over
- DECOMPOSITION.md section 6: four named ports withdrawn for V4_PORTS
  numbered ones and wiring as data, as ruled

Verified: tests/test_fabric.c, 53 checks at both widths: two nodes exchange
words; an empty node is filled through its port by a device, and by another
node, and runs what it was sent; a word is passed on by a node in between;
a waiting node executes nothing; the wiring is changed while they run; a
node is put to sleep, woken and removed while looping; a node is born while
others run; the fabric is given more room.  make -C v4 test and make -C v4
sanitize pass.  The single-node products are unchanged: hosted-check on
three ISAs, and clean qemu with STARFORTH_V4=1 on amd64, aarch64 and
riscv64 with lines typed at each prompt (logs/20261006-074907, -075150,
-075532).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-06 07:58:00 -04:00
co-authored by Claude Opus 5.5
parent 2c5427d9c1
commit 9af442f793
17 changed files with 1170 additions and 39 deletions
+171
View File
@@ -0,0 +1,171 @@
/* fabric.c -- nodes, wiring, and time passing for all of them. See fabric.h.
*
* Nothing here uses the C library: the bare-metal kernel links this file.
*/
#include "v4/fabric.h"
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; }
}
void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity)
{
unsigned i;
f->place = places;
f->capacity = capacity;
for (i = 0; i < capacity; i++) clear_place(&places[i]);
}
void v4_fabric_grow(v4_fabric *f, v4_place *places, unsigned capacity)
{
unsigned i;
if (capacity < f->capacity) return;
for (i = 0; i < f->capacity; i++) places[i] = f->place[i];
for (; i < capacity; i++) clear_place(&places[i]);
f->place = places;
f->capacity = capacity;
}
int v4_fabric_add(v4_fabric *f, v4_fabric_node *storage, v4_cell port_base)
{
unsigned i;
for (i = 0; i < f->capacity && f->place[i].node; i++) { }
if (i == f->capacity || !storage) return -1;
clear_place(&f->place[i]);
f->place[i].node = storage;
v4_node_born(&storage->n, port_base);
v4_exec_reset(&storage->es);
v4_heat_reset(&storage->h);
return (int)i;
}
v4_fabric_node *v4_fabric_node_at(const v4_fabric *f, unsigned id)
{
return id < f->capacity ? f->place[id].node : 0;
}
static int there(const v4_fabric *f, unsigned id, unsigned port)
{
return id < f->capacity && f->place[id].node && port < V4_PORTS;
}
/* 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; }
}
w->kind = V4_WIRE_NONE;
w->device = 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;
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;
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;
cut(f, a, pa);
f->place[a].wire[pa].kind = V4_WIRE_DEVICE;
f->place[a].wire[pa].device = device;
return 1;
}
int v4_fabric_unwire(v4_fabric *f, unsigned a, unsigned pa)
{
if (!there(f, a, pa)) return 0;
cut(f, a, pa);
return 1;
}
v4_fabric_node *v4_fabric_remove(v4_fabric *f, unsigned id)
{
v4_fabric_node *storage;
unsigned k;
if (id >= f->capacity || !f->place[id].node) return 0;
for (k = 0; k < V4_PORTS; k++) cut(f, id, k);
storage = f->place[id].node;
clear_place(&f->place[id]);
return storage;
}
void v4_fabric_sleep(v4_fabric *f, unsigned id)
{
if (id < f->capacity && f->place[id].node) f->place[id].asleep = 1;
}
void v4_fabric_wake(v4_fabric *f, unsigned id)
{
if (id < f->capacity && f->place[id].node) f->place[id].asleep = 0;
}
static int awake(const v4_fabric *f, unsigned id)
{
return id < f->capacity && f->place[id].node && !f->place[id].asleep;
}
/* What node `a` wrote to one of its ports, handed to whatever is there. */
static unsigned hand_over_write(v4_fabric *f, unsigned a)
{
v4_node *n = &f->place[a].node->n;
const v4_wire *w = &f->place[a].wire[n->ask_port];
if (w->kind == V4_WIRE_DEVICE) {
if (w->device->take && w->device->take(w->device->self, n->request)) { v4_node_port_served(n); return 1; }
} else if (w->kind == V4_WIRE_NODE && awake(f, w->node)) {
v4_node *r = &f->place[w->node].node->n;
if (r->reading && !r->given && (r->read_port == w->port || r->read_port == V4_PORT_ANY)) {
v4_node_port_give(r, w->port, n->request);
v4_node_port_served(n);
return 1;
}
}
return 0;
}
/* Node `a` is reading: a device on that port, or on any of them, is asked. */
static unsigned hand_over_read(v4_fabric *f, unsigned a)
{
v4_node *n = &f->place[a].node->n;
unsigned first = n->read_port == V4_PORT_ANY ? 0 : n->read_port;
unsigned last = n->read_port == V4_PORT_ANY ? V4_PORTS - 1 : n->read_port;
unsigned k;
v4_cell value;
for (k = first; k <= last; k++) {
const v4_wire *w = &f->place[a].wire[k];
if (w->kind == V4_WIRE_DEVICE && w->device->give && w->device->give(w->device->self, &value)) {
v4_node_port_give(n, k, value);
return 1;
}
}
return 0;
}
unsigned v4_fabric_step(v4_fabric *f)
{
unsigned i, done = 0;
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);
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++)
if (awake(f, i) && f->place[i].node->n.reading && !f->place[i].node->n.given) done += hand_over_read(f, i);
return done;
}