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++;
|
||||
|
||||
Reference in New Issue
Block a user