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
+60 -5
View File
@@ -85,10 +85,14 @@ framebuffer words. It takes its arguments from the VM's data stack.
*Ruled:* a node asks for a block by number and the kernel decides the
rest; a node sends a message by asking.
*v4:* a kernel word is a dictionary entry on the node whose code hands a
request to the kernel, with its arguments on the node's data stack. The
C functions are v3's. **How the request is carried is not designed yet**
and is the first thing step 2 settles.
*v4, ruled 2026-10-05:* a kernel word is an ordinary dictionary entry on
the node whose body writes its request number to the node's port. The
write blocks the node until the kernel, its neighbour on that port, has
served it (`DECOMPOSITION.md` section 6: "a write blocks until the
neighbour reads"). The kernel serves between the node's opcodes, so the
node is always stopped when C touches it; the function takes its arguments
from the node's data stack and leaves its results there. Kernel words are
made by handing the node text at boot. The C functions are v3's.
### 3.4 The stacks and the dictionary, from the kernel's side
@@ -129,6 +133,41 @@ engine's.
*v3:* `vm_dict_hash_fn`, a hook the birth protocol calls. *v4:* the same
hook, answered from the node's dictionary.
## 3a. Many VMs at once (Captain Bob, 2026-10-05)
> Do not forget that this is multiuser, multitasking, and a hybrid of
> preemptive and cooperative.
And, the same day: "Hera will be the process manager via compudynamics per
node."
What that asks of the engine, and what it has:
- **A node can be stopped between any two instruction words and gone on
with later.** Everything a node is doing is in the node and its
execution state; `v4_exec_step_word` runs one instruction word and
returns. So whoever runs the nodes can take the processor from one at any
word and give it to another: that is the preemptive half, and the engine
already allows it. Nothing may be built that needs a node to run a line,
or a request, to its end without interruption.
- **A node gives way by itself when it writes to its port.** It is blocked
until served (3.3), and while it is blocked another can run: that is the
cooperative half.
- **Each node has its own execution state.** The place a blocked node goes
on from is kept per node (`v4_exec_state`), never in one shared place.
- **Each user is a VM** (`FABRIC-2.md` D.2: "a session IS a VM"), so each is
a node, with its own dictionary, stacks and ACL cards.
What is not designed: who decides which node runs next, and when. In v3
that is the switcher, reading what kernel-Hermes publishes, at the
checkpoint in the inner loop; the ruling makes it Hera's, by compudynamics.
It is step 6's, and section 6 lists it as open.
**The loop that runs one node until its line ends** (`v4_boot_line`,
`v4/system/boot.c`) is the lone node's and the hosted program's, where
there is one node and nothing to share the processor with. It is not how
the kernel will run a fleet, and goes with the lone node at step 4.
## 4. What was built on the detour
| Built 2026-10-05 | What becomes of it |
@@ -156,6 +195,22 @@ its logs.
2. **Asking the kernel, and the stacks from the kernel's side (3.3,
3.4).** Settle how a request is carried. Kernel words callable from a
node.
**The carrier is done, 2026-10-05; the rest is not.** Ruled: a node asks
by a blocking write to a port. Built: the port (`v4/src/node.c`,
`v4_node_port_attach`, `v4_node_port_served`); a blocked node goes on
from the opcode after the store, in the same instruction word
(`v4/src/exec.c`); `KERNEL-WORD` (`v4/capsule/compile.v4`), which makes a
word whose body writes its request number to the port; the boot makes
the kernel's words by handing the node text, and serves requests
(`v4/system/boot.c`). A request no one serves is error 12 on the node.
The one kernel word so far is `BYE`, on both products: hosted it leaves
the program, as hosted v3; on the lone node it is v3's cold restart.
`v4/tests/test_port.c`; six builds agree; `BYE` and an unserved request
typed at each bare-metal prompt. `logs/20261005-185506`, `-185734`,
`-190101`.
**Not done:** v3's own C functions serving a node. They take a `VM *`
and use `vm_push`, `vm_pop` and, in places, the stack's fields directly;
a node has to stand behind that `VM` record first.
3. **A word is being executed (3.5).** The hook at `call`; the ACL's
countdown and recheck; the word's count; `stadium_word_dispatch`.
In-line words become calls. The per-call-target count goes.
@@ -175,10 +230,10 @@ these, a group at a time, with POST after each.
| Open | Before |
|---|---|
| How a node's request to the kernel is carried | Step 2 |
| How the node tells a dictionary entry from a bare address at `call` | Step 3 |
| `EXECUTE`, which enters a word by a return | Step 3 |
| `>R R> R@ I J LEAVE`, which cannot be called | Step 3 |
| Who decides which node runs next, and when: Hera, by compudynamics; preemptive and cooperative | Step 6 |
| The node's safe moment for message delivery and switching | Step 6 |
| `PAD 42 OVER !`, a byte address given to `!` (D-1) | Step 5 |
| Which word patrons' accounts the hosted product keeps, having no Stadium | Step 3 |