feat(v4.0.0): a node asks its kernel by a blocking write to its port

ENGINE.md step 2, the carrier.  Ruled 2026-10-05 (V3-PARITY.md 1i), on
DECOMPOSITION.md section 6: a write to a port blocks until the neighbour
reads.

- node: v4_node_port_attach, v4_node_port_served; a store to the port keeps
  the value as the request and blocks the node
- exec: a blocked node executes nothing; served, it goes on from the opcode
  after the store, in the same instruction word; a fault meanwhile abandons
  the rest of the word
- compile.v4: n KERNEL-WORD name makes a word whose body writes n to the
  port; its arguments and results are on the data stack
- boot: the kernel's words are made by handing the node text, and requests
  are served between the node's opcodes; one no one serves is error 12
- BYE, the first kernel word: hosted it leaves the program, as hosted v3;
  on the lone node it is v3's cold restart
- ENGINE.md 3a: multiuser, multitasking, preemptive and cooperative, and
  what that asks of the engine

Verified: make -C v4 test passes at both widths, with tests/test_port.c;
hosted-check passes on three ISAs; clean qemu with STARFORTH_V4=1 on amd64,
aarch64 and riscv64 passes POST with the same hashes as hosted, and a
kernel word no one serves and BYE typed at each prompt are answered
(logs/20261005-185506, -185734, -190101; -185234 is an amd64 run in which
those two lines were not typed).

Not done: v3's own C functions serving a node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-05 19:03:03 -04:00
co-authored by Claude Opus 5.5
parent 5f1f60fc84
commit 8df6c16766
21 changed files with 908 additions and 21 deletions
+29
View File
@@ -88,6 +88,13 @@ typedef struct {
unsigned faults; /* how many there have been */
int stopped; /* non-zero: faulted with no handler */
/* The port (DECOMPOSITION.md section 6): a write to it blocks the node
* until its neighbour has taken what was written. See
* v4_node_port_attach below. */
v4_cell port; /* its word address, or -1: no port */
int asking; /* non-zero: blocked, having written `request` */
v4_cell request; /* what it wrote */
/* DSTACK-DEPTH and RSTACK-DEPTH (D-16). See v4_node_stack_regs_attach. */
v4_cell dstack_reg; /* its word address, or -1 */
v4_cell rstack_reg; /* its word address, or -1 */
@@ -149,6 +156,28 @@ void v4_node_store(v4_node *n, v4_cell addr, v4_cell value);
* Passing -1 detaches. The address is the caller's choice. */
void v4_node_console_attach(v4_node *n, v4_cell addr);
/* THE PORT. A node reaches its neighbour through a port, and a port is an
* address (DECOMPOSITION.md section 6): "a write blocks until the neighbour
* reads. This is the GA144 model. No instruction is added."
*
* After v4_node_port_attach(n, addr), a store to word address `addr` does
* not write memory: the node keeps the value in n->request, sets n->asking
* and is blocked. A blocked node executes nothing -- v4_exec_step_word
* returns 0 and changes nothing -- until its neighbour calls
* v4_node_port_served(n). It then goes on from the opcode after the store,
* in the same instruction word if there are any left.
*
* This is how a node asks its kernel for something (docs/v4.0.0/ENGINE.md
* 3.3): the neighbour on the port is the kernel, what is written is the
* number of the request, and the arguments and results are on the node's
* data stack, which the kernel may use while the node is blocked. Only the
* write is here. Nothing reads from a port yet, and a fetch from the
* address is a fetch from memory.
*
* A node has no port until one is attached: v4_node_reset detaches it. */
void v4_node_port_attach(v4_node *n, v4_cell addr);
void v4_node_port_served(v4_node *n);
/* CONSOLE-RX and CONSOLE-STATUS, the console's receive side, on the
* single-node model.
*