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:
co-authored by
Claude Opus 5.5
parent
2c5427d9c1
commit
9af442f793
+31
-4
@@ -143,20 +143,30 @@ void v4_exec_op(v4_node *n, v4_exec_state *es, v4_heat *h,
|
||||
|
||||
case V4_OP_FETCH_P:
|
||||
if (faulted(n, n->p)) return;
|
||||
if (v4_node_port_index(n, n->p) >= 0) {
|
||||
/* P is a port: the literal is the next word its neighbour
|
||||
* writes, and P stays where it is */
|
||||
if (!v4_node_read_ready(n, n->p)) return;
|
||||
v4_dstack_push(ds, v4_node_fetch(n, n->p));
|
||||
break;
|
||||
}
|
||||
v4_dstack_push(ds, v4_node_load(n, n->p));
|
||||
n->p = add_wrap(n->p, 1);
|
||||
break;
|
||||
case V4_OP_FETCH_INC:
|
||||
if (faulted(n, n->a)) return;
|
||||
if (!v4_node_read_ready(n, n->a)) return; /* a port with nothing written to it yet */
|
||||
v4_dstack_push(ds, v4_node_fetch(n, n->a));
|
||||
n->a = add_wrap(n->a, 1);
|
||||
break;
|
||||
case V4_OP_FETCH_B:
|
||||
if (faulted(n, n->b)) return;
|
||||
if (!v4_node_read_ready(n, n->b)) return; /* a port with nothing written to it yet */
|
||||
v4_dstack_push(ds, v4_node_fetch(n, n->b));
|
||||
break;
|
||||
case V4_OP_FETCH_A:
|
||||
if (faulted(n, n->a)) return;
|
||||
if (!v4_node_read_ready(n, n->a)) return; /* a port with nothing written to it yet */
|
||||
v4_dstack_push(ds, v4_node_fetch(n, n->a));
|
||||
break;
|
||||
case V4_OP_STORE_P:
|
||||
@@ -250,18 +260,25 @@ 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) return 0;
|
||||
if (n->stopped || n->asking || n->reading) return 0;
|
||||
if (es->resume && es->resume_faults != n->faults) es->resume = 0; /* it faulted while blocked: P is the handler */
|
||||
if (es->resume) {
|
||||
/* served: the rest of the word the store was in */
|
||||
/* unblocked: the rest of the word it was in */
|
||||
es->resume = 0;
|
||||
iw = es->resume_iw;
|
||||
slot = es->resume_slot;
|
||||
} else {
|
||||
/* P itself may be what is outside memory */
|
||||
if (faulted(n, n->p)) return 0;
|
||||
iw = fetch_iword(n, n->p);
|
||||
n->p = add_wrap(n->p, 1);
|
||||
if (v4_node_port_index(n, n->p) >= 0) {
|
||||
/* P is a port: the node executes what its neighbour writes
|
||||
* there, and P stays where it is */
|
||||
if (!v4_node_read_ready(n, n->p)) return 0;
|
||||
iw = (v4_iword)((v4_ucell)v4_node_fetch(n, n->p) & 0xFFFFFFFFu);
|
||||
} else {
|
||||
iw = fetch_iword(n, n->p);
|
||||
n->p = add_wrap(n->p, 1);
|
||||
}
|
||||
}
|
||||
|
||||
while (slot < V4_SLOT_COUNT) {
|
||||
@@ -271,6 +288,16 @@ unsigned v4_exec_step_word(v4_node *n, v4_exec_state *es, v4_heat *h)
|
||||
unsigned faults = n->faults;
|
||||
|
||||
v4_exec_op(n, es, h, op, iw, slot);
|
||||
if (n->reading) {
|
||||
/* The fetch was from a port with nothing written to it yet. The
|
||||
* opcode has not executed: the node waits here, and this slot
|
||||
* is where it goes on from. */
|
||||
es->resume = 1;
|
||||
es->resume_iw = iw;
|
||||
es->resume_slot = slot;
|
||||
es->resume_faults = n->faults;
|
||||
break;
|
||||
}
|
||||
if (n->faults != faults) { /* P is the handler now, or the node has stopped */
|
||||
/* a store that raised an error (D-18) did execute; any other fault means the opcode did not */
|
||||
if (n->fault_kind == V4_FAULT_RAISED) executed++;
|
||||
|
||||
+171
@@ -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;
|
||||
}
|
||||
+63
-5
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user