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
+63 -5
View File
@@ -120,9 +120,18 @@ void v4_node_store(v4_node *n, v4_cell addr, v4_cell value)
n->console_dropped++;
return;
}
/* The port: the node keeps what it wrote and is blocked until its
* neighbour has taken it. -1 when no port is attached. */
if (addr == n->port) { n->request = value; n->asking = 1; return; }
/* A port: the node keeps what it wrote and is blocked until its
* neighbour has taken it. */
{
int port = v4_node_port_index(n, addr);
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 */
}
/* The stack registers: a store empties the stack. -1 when not attached. */
if (addr == n->dstack_reg) { v4_dstack_clear(&n->ds); return; }
if (addr == n->rstack_reg) { v4_rstack_clear(&n->rs); return; }
@@ -147,6 +156,17 @@ v4_cell v4_node_fetch(v4_node *n, v4_cell addr)
if (n->input_pos == n->input_len) n->input_pos = n->input_len = 0;
return c;
}
{
int port = v4_node_port_index(n, addr);
if (port == (int)V4_PORTS + 1) return (v4_cell)n->last_from;
if (port >= 0) {
/* the word its neighbour wrote; v4_node_read_ready has said it is there */
if (!n->given) return 0;
n->given = 0;
if (port == (int)V4_PORT_ANY) n->last_from = n->given_port;
return n->given_value;
}
}
if (addr == n->dstack_reg) return (v4_cell)n->ds.depth;
if (addr == n->rstack_reg) return (v4_cell)n->rs.depth;
return v4_node_load(n, addr);
@@ -182,11 +202,49 @@ void v4_node_console_attach(v4_node *n, v4_cell addr)
n->console_dropped = 0;
}
void v4_node_port_attach(v4_node *n, v4_cell addr)
void v4_node_port_attach(v4_node *n, v4_cell base)
{
n->port = addr;
n->port = base;
n->asking = 0;
n->request = 0;
n->ask_port = 0;
n->reading = 0;
n->read_port = 0;
n->given = 0;
n->given_value = 0;
n->given_port = 0;
n->last_from = 0;
}
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 + 1) return -1;
return (int)(addr - n->port);
}
int v4_node_read_ready(v4_node *n, v4_cell addr)
{
int port = v4_node_port_index(n, addr);
if (port < 0 || port == (int)V4_PORTS + 1) return 1;
if (n->given && (port == (int)V4_PORT_ANY || (unsigned)port == n->given_port)) return 1;
n->reading = 1;
n->read_port = (unsigned)port;
return 0;
}
void v4_node_port_give(v4_node *n, unsigned port, v4_cell value)
{
n->given = 1;
n->given_value = value;
n->given_port = port;
n->reading = 0;
}
void v4_node_born(v4_node *n, v4_cell base)
{
v4_node_reset(n);
v4_node_port_attach(n, base);
n->p = base + (v4_cell)V4_PORT_ANY;
}
void v4_node_port_served(v4_node *n)