The review of step 6c found it did not keep its rule. Ruled: fix them all; 16 hops. - Paying a NACK no longer waits on a lower-numbered neighbour; a node that owes one does not sleep, and goes on taking in what it is sent. - AWAIT looks first through the messages already waiting. - A node waiting to write to a removed neighbour gets error 18: the word that says who is reading also says which ports have anything on them. - A console line lets Hera go when she is blocked on the first word of a message (v4_fabric_interrupt_error, a device's `pending`). - A refused answer is told to the node that waits, not the one that answered; one owed to the node itself goes to its own queue. - A message is passed on by at most 16 nodes, then refused. - A GONE is believed only when it says it is from the node's centre. - Tests: the check that tested nothing is replaced; no room for a waiting sender; the limit that remains is tested as a limit. - MESH.md 7b's rule reworded to what holds, 7b.7 says where it falls short; README likewise. make -C v4 test, sanitize, hosted-check; three bare-metal boots. POST 538 of 538, word_count=317, dict_hash=0xc0769523a47b7dc3 on all six. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
206 lines
11 KiB
Markdown
206 lines
11 KiB
Markdown
# v4/
|
|
|
|
StarForth v4: the 32-instruction F18-derived core. The design lives in
|
|
`docs/v4.0.0/JUSTIFICATION.md` (why) and `docs/v4.0.0/DECOMPOSITION.md`
|
|
(every v3 word mapped to a v4 fate).
|
|
|
|
The first deliverable is the hosted C99 golden model (`JUSTIFICATION.md` §10,
|
|
step 1). It must pass POST and hold K≡1.0 with 32- and 64-bit cells on all
|
|
three host ISAs.
|
|
|
|
Acceptance (`JUSTIFICATION.md` §16): v4 must be equivalent to v3 at any point
|
|
in time, with the same vocabularies and behaviour, on the F18-derived engine;
|
|
and every ISA, hosted and bare metal, must still reach its `ok` prompt.
|
|
`make -C v4 test` passing is a development check, not acceptance.
|
|
|
|
What exists so far is the single node: registers, circular stacks, memory, all
|
|
32 opcodes, per-opcode and per-call-target heat, and three console
|
|
registers (`CONSOLE-TX`, which captures output, and `CONSOLE-RX` and `CONSOLE-STATUS`, which hand out
|
|
input a test feeds) standing in for the console node until the mesh exists. The tests load definitions
|
|
onto a node either opcode by opcode (`v4/include/v4/asm.h`) or as text in the notation `DECOMPOSITION.md`
|
|
uses (`v4/include/v4/text.h`). `make -C v4 test` builds
|
|
and runs the tests at both cell widths; `make -C v4 sanitize` repeats them
|
|
under ASan and UBSan. There is no POST and no K measurement yet.
|
|
|
|
## The system: nucleus, capsules, prompt
|
|
|
|
`docs/v4.0.0/NUCLEUS.md` is the design. A v4 system is four things:
|
|
|
|
| Part | Where | What it is |
|
|
|---|---|---|
|
|
| Engine | `v4/src` | The golden model of the 32-opcode node |
|
|
| Nucleus | `v4/capsule/*.v4`, built by `v4/tools/mkimage.c` into `capsules/v4/nucleus-64.f18` | The assembled words, as a capsule of F18 code: sent through its port to a node born empty |
|
|
| Capsules | `capsules/v4/*.4th`, baked by `tools/mkcapsule.c` | FORTH source, loaded when the system comes up |
|
|
| Boot | `v4/system/boot.c` | Starts the nucleus, checks and loads each capsule, prints the parity lines, gives the prompt |
|
|
|
|
Two products link the same four and differ only in the console:
|
|
|
|
- **Hosted Linux**, `v4/tools/hosted.c`: `make -C v4 hosted` builds
|
|
`v4/build/starforth4-amd64`, `-aarch64` and `-riscv64`, static, 64-bit cells.
|
|
- **Bare metal**, `kernel/src/v4/sk_v4.c`: `make -f kernel/Makefile ARCH=<arch> STARFORTH_V4=1`.
|
|
|
|
A boot prints:
|
|
|
|
```
|
|
PARITY:V4_NUCLEUS words=292 image_hash=0x...
|
|
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x... capsule_hash=0x... dict_hash=0x...
|
|
PARITY:OK
|
|
ok>
|
|
```
|
|
|
|
Every build of one commit prints the same hashes. `make -C v4 hosted-check`
|
|
boots the three hosted binaries (the two foreign ones under user-mode QEMU)
|
|
and fails unless their output is identical and ends in `PARITY:OK`.
|
|
|
|
A capsule line the node does not answer ` ok` to ends the boot, naming the
|
|
capsule, block and line, with `PARITY:FAIL` and `POST: FAILED`.
|
|
|
|
State, 2026-10-05, after `ENGINE.md` step 1: all six builds start the nucleus,
|
|
load `v4:forth79.4th`, pass POST and reach `ok>`, with the same lines:
|
|
|
|
```
|
|
PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=... capsule_hash=0x97a655081b2af559 words=298
|
|
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=... dict_hash=...
|
|
PARITY:V4_POST tests=538 pass=538 fail=0
|
|
PARITY:V4_SYSTEM word_count=314 dict_hash=...
|
|
PARITY:OK
|
|
POST: PASSED
|
|
ok>
|
|
```
|
|
|
|
Bare-metal logs: `logs/20261006-110551/amd64/`, `logs/20261006-111621/aarch64/`,
|
|
`logs/20261006-111341/riscv64/`. On each, seven lines were then typed at the
|
|
prompt through the serial port — a definition, its use with the capsule's
|
|
`U*`, `COLD`, the capsule word again, a `FORGET` of it (refused), a kernel
|
|
word no one serves, and `BYE` — and each was answered as the hosted binary
|
|
answers it. The
|
|
capsules were unsigned (no signing key on this machine).
|
|
|
|
**A node is sent text as a message** (`docs/v4.0.0/MESH.md` section 6). It
|
|
does not read its own command line, prints no prompt and has no console of
|
|
its own. With nothing to do it is blocked reading its ports. A neighbour
|
|
writes it a message; text for it is interpreted; what it prints goes back
|
|
as messages to the sender, and then a message saying how the text ended.
|
|
Its console says ` ok` or ` ERROR` and prompts, as the kernel's REPL does
|
|
for a v3 VM. A message carries 1024 characters, a block. `v4/src/message.c`
|
|
is the format for a host; `v4_boot_line` (`v4/system/boot.c`) is the
|
|
console both products and the capsule loader use.
|
|
|
|
**A node asks its kernel by writing to its port** (`ENGINE.md` 3.3). The
|
|
write blocks the node until it has been served. A kernel word is a
|
|
dictionary entry made by `n KERNEL-WORD name`, whose body writes `n` to the
|
|
port; its arguments and results are on the data stack. `BYE` is the one
|
|
kernel word so far. A node can be stopped between any two instruction
|
|
words and is blocked while it waits at its port, which is what a system of
|
|
many users and tasks, preemptive and cooperative, needs of it
|
|
(`ENGINE.md` 3a).
|
|
|
|
**The two products have parted** (`ENGINE.md` 3c). They share the engine,
|
|
the FORTH-79 capsule and its POST; their nuclei may differ. The hosted
|
|
product is its own and may become a hosted SDK. The bare-metal product is
|
|
to be LithosAnanke with the node in the StarForth VM's place, and
|
|
everything about word records, the fleet and identity is its alone.
|
|
|
|
**Nodes that talk** (`docs/v4.0.0/MESH.md`) are the next step, ahead of
|
|
making v4 equal v3 on bare metal. Its first part is built and tested in
|
|
the engine: a node has `V4_PORTS` ports; a read from one blocks until the
|
|
neighbour writes and a write until the neighbour reads; a node is born
|
|
empty with `P` at its ports and executes what a neighbour sends it, which
|
|
is how each product's node now gets its nucleus, as a capsule; and
|
|
the fabric (`v4/src/fabric.c`) is the nodes there are and the table of how
|
|
their ports are wired, both of which change while they run. The products
|
|
below are still one node each; they become the five nodes at `MESH.md`
|
|
steps 8 and 9.
|
|
|
|
**Finding the way** (`MESH.md` step 4) is built: a node has a table of
|
|
which port leads toward which node (`ROUTE`, `DEFAULT-ROUTE`, `NO-ROUTES`),
|
|
passes on a message that is not for it, and `SEND`s text to another node;
|
|
`v4/tests/test_host_mesh.c` runs three StarForth nodes in a row. A node
|
|
looks before it begins a message (`MESH.md` section 7a): it takes in what
|
|
its neighbours are waiting to write to it, keeps those messages until it
|
|
has nothing else to do, and writes only to a neighbour that is waiting to
|
|
read -- or, of two neighbours, the one with the lower number may wait.
|
|
`NEIGHBOUR ( node port -- )` tells a node who is on each port. A node with
|
|
no room for one more message lets it go and counts it in `(LOST)`.
|
|
|
|
**Five nodes from nothing** (`MESH.md` step 5) is built and proven in the
|
|
fabric under test: Hera asks whoever holds the fabric for a node
|
|
(`v4/src/manage.c`: `NODE-BORN`, `NODE-WIRE` and the rest), sends it the
|
|
nucleus through the port that joins them, then FORTH-79 a line at a
|
|
time, and joins four such nodes in a square round her
|
|
(`capsules/v4/hera.4th`: `BIRTH`, `UNIT`). `v4/tests/test_host_unit.c`.
|
|
The two products are not yet this: they become the five nodes at steps 8
|
|
and 9.
|
|
A node she births is not POSTed: POST is the kernel's, once, on Hera.
|
|
|
|
**Refusals and waits** (`MESH.md` 7b, step 6c). A node is never left
|
|
waiting on a node that is gone, and a wait on a node that is alive and
|
|
stuck is ended by Hera's killing it, or from the console. A node with no
|
|
room for a message, or no way to pass it on, or that would be the
|
|
sixteenth to pass it on, sends a NACK to whoever waits on it; `(REFUSED)`
|
|
counts them. `AWAIT` ends in an error if the answer is not going to come:
|
|
"Message refused", "Node gone", or "Interrupted" when a line is typed at
|
|
the console. `n KILL` on Hera removes node n and tells every other node it
|
|
is gone; a node believes that only from its centre. A stuck node can hold
|
|
up its neighbours until it is killed: `MESH.md` 7b.7 says how.
|
|
|
|
**Blocks** (`MESH.md` step 6). The block words are the kernel's, as they
|
|
are for a v3 VM: `BLOCK`, `BUFFER`, `UPDATE`, `SAVE-BUFFERS` and
|
|
`EMPTY-BUFFERS` each write one request to port 0, by block number only
|
|
(`v4/include/v4/blocks.h`). The node has a window of four slots in its
|
|
memory; the kernel copies blocks into it, keeps the record of what is in
|
|
it, and decides when a block is written (`v4/system/blocks.c`). The blocks
|
|
are the kernel's block subsystem's, which is v3's
|
|
(`v3/src/block_subsystem.c`):
|
|
one chain, the same blocks at the same numbers for every node. A hosted
|
|
program has the chain's fast RAM, blocks 1 to 2047. On bare metal the chain
|
|
is fast RAM, the ramdrive and the virtio disk; the disk is read and not yet
|
|
written, because nothing in v4 gives its owner's word that it may be
|
|
formatted. Who may have which block is not checked yet.
|
|
|
|
**This is still the lone node.** On bare metal `kernel_main.c` starts it
|
|
before the fleet tables, beside the kernel's own system and not in the VM's
|
|
place. `ENGINE.md` sets out the steps from here; step 1 is done and step 2 is
|
|
part done. `V3-PARITY.md` records the rulings it is built from.
|
|
|
|
**What this does not yet show.** `forth79.4th` holds two definitions, `U*`
|
|
and `U/MOD`. Every other word is still in the assembled nucleus, so POST is
|
|
so far a test of the assembled words. Moving them to the capsule, a group at
|
|
a time with POST after each, is the next step (`NUCLEUS.md` section 8).
|
|
|
|
## POST
|
|
|
|
POST is the kernel's, as in v3 (`MESH.md` step 6b, `NUCLEUS.md` 6.3). The
|
|
kernel holds the cases, `v4/system/post_cases.c`: 538 of them, covering 126
|
|
of the 130 words of the FORTH-79 Required Word Set. 431 are v3's cases with
|
|
what the hosted v3 binary did as the expected result; every other case,
|
|
and the v3 cases left out, are listed with reasons in
|
|
`docs/v4.0.0/POST79.md`.
|
|
|
|
- `make -C v4 post` boots the amd64 system: the quick check after a change.
|
|
- `make -C v4 hosted-check` boots all three hosted ISAs and compares them.
|
|
- `make -C v4 post79` writes the cases again (`tools/mkpost.py`,
|
|
`tools/post79_rules.py`); it needs the hosted v3 binary.
|
|
|
|
The runner, `v4/system/post.c`, feeds each case to the node a line at a
|
|
time, keeps what the node prints, reads its data stack from outside, and
|
|
judges: an error exactly if one is expected, and otherwise the stack and
|
|
every character printed. Nothing of POST is in the node's dictionary, and
|
|
POST leaves nothing: the system is sealed before it, and afterwards the
|
|
kernel puts the node back to the system as sealed, as v3 puts the
|
|
dictionary back after each of its suites. The boot then prints
|
|
`PARITY:V4_SYSTEM word_count=N dict_hash=0x...`, and passes only if no
|
|
case failed (`v4/system/boot.c`). A case the node does not come back from
|
|
ends POST there, named. `make -C v4 hosted-check` also boots a program
|
|
whose POST has failing cases and requires that the boot fails.
|
|
|
|
Not tested: `KEY`, `EXPECT`, `QUERY` (the keyboard) and `QUIT` (it returns
|
|
without ` ok`).
|
|
|
|
Open: `PAD 42 OVER !` faults on v4, because `PAD` is a byte address and `!`
|
|
takes a cell address (D-1). FORTH-79 expects it to work. The v3 case for it
|
|
is left out until that is ruled.
|
|
|
|
The Required Word Set list in `tools/mkpost.py` was written from memory of
|
|
the standard and has not been checked against the document.
|