feat(v4.0.0): a node looks before it writes; two neighbours no longer stop each other
MESH.md step 4, the fault found there, as ruled (section 7a): a node writes only to a neighbour that is reading, keeps what it takes in meanwhile, and loses and counts what it has no room for. Engine: two more addresses after a node's ports -- which ports have a neighbour waiting to write to it, which to read from it. Nucleus: (GATE) before every message; the messages waiting, a ring of 400 cells, dealt with when the node is idle. Of two neighbours the lower number may wait to write (ruled after it was built); NEIGHBOUR tells a node who is on each port. test_host_mesh.c, 44 checks: the case that stopped the nodes passes; six messages from each node to each at once all arrive; 800 at once, the nodes come to rest and every message arrived or was counted (287 arrived, 714 of all kinds let go). test_fabric.c: the two looks. Both widths, ASan+UBSan. POST: twelve cases handed HERE, a cell address on v4, to words that take a byte address, and so wrote into or read from the nucleus's code at cell HERE/4. Ruled: left out, marked OPEN, until HERE and the byte words are made to agree as its own step. POST is 538 cases. hosted-check on three ISAs, 538/538. Bare metal: logs/20261006-134817 (amd64), -135044 (aarch64), -135440 (riscv64). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
630d03fa4e
commit
f9c034cadd
+83
-46
@@ -213,38 +213,60 @@ outer nodes, and for each other unit the port toward that unit's centre.
|
||||
A different geometry is a different way of filling in the wiring table
|
||||
and these tables. Nothing else changes.
|
||||
|
||||
## 7a. Two neighbours writing to each other (open; awaits a ruling)
|
||||
## 7a. Two neighbours writing to each other (ruled 2026-10-06)
|
||||
|
||||
The fault is in section 10, step 4. Three ways out, none built:
|
||||
The fault is in section 10, step 4. **Ruled: a node writes only to a
|
||||
neighbour that is already reading; a node keeps the messages it takes in
|
||||
meanwhile, and one there is no room for is lost and counted.**
|
||||
|
||||
**A. A node writes only to a neighbour that is already reading.** The
|
||||
engine gives a node two more things to fetch, as the F18's `io` register
|
||||
does: which ports have a neighbour writing, and which have a neighbour
|
||||
reading. A node with a message to send looks first. If the neighbour is
|
||||
reading, it writes, and the write does not wait. If the neighbour is
|
||||
writing to it instead, it takes that message in first, and deals with it,
|
||||
and looks again. A node therefore holds messages waiting to go, in its own
|
||||
memory, and what it does when that room is full has to be decided (the
|
||||
message is lost and counted, as one with nowhere to go is now). Writes
|
||||
still block, as ruled; a node simply does not start one it cannot finish.
|
||||
This is how it is done on the chip. It is all in the nucleus but for the
|
||||
two fetches.
|
||||
**As built.**
|
||||
|
||||
**B. Passing on is not the node's programme's work.** `DECOMPOSITION.md`
|
||||
6.1 says "the router in each node forwards packets". Read as a thing apart
|
||||
from the programme -- logic beside the F18 on the FPGA, a part of the
|
||||
fabric in the model -- it takes in and sends on whatever the programme is
|
||||
doing, and holds what is for the programme until it reads. The programme's
|
||||
own writes then never wait on a neighbour's programme. This is more
|
||||
hardware per node and takes passing-on out of F18 code, and out of what a
|
||||
capsule can change.
|
||||
- *The engine.* Two more addresses follow a node's ports, as the F18's
|
||||
`io` register would give them: which ports have a neighbour waiting to
|
||||
write to this node, and which have one waiting to read from it, a bit to
|
||||
a port. Fetching them waits for nothing and changes nothing
|
||||
(`v4/src/node.c`; the fabric keeps them, `v4/src/fabric.c`). A device
|
||||
that takes what is written to it shows as waiting to read.
|
||||
- *Looking before writing* (`(GATE)`, `v4/capsule/core.v4`). Before a node
|
||||
begins any message it takes in every message a neighbour is waiting to
|
||||
write to it; then it writes if the neighbour it means to write to is
|
||||
waiting to read; and if not, it looks again. Once a message is begun it
|
||||
is written to its end: the neighbour that took its first word reads the
|
||||
rest.
|
||||
- *The messages waiting* (`(MQ)`). What a node takes in is kept in a ring
|
||||
of 400 cells of its own memory -- for each message its seven words, the
|
||||
port it came on, and its text -- and dealt with, oldest first, when the
|
||||
node has nothing else to do. A node with none waiting is blocked reading
|
||||
its ports, as before. One there is no room for is read to its end, let
|
||||
go, and counted in `(LOST)`.
|
||||
|
||||
**C. No answer to a `SEND`.** It removes the case found and leaves the
|
||||
fault: any two messages crossing still stop both nodes. Not offered as a
|
||||
way out; listed so that it is seen to have been weighed.
|
||||
**What had to be added to the ruling, and why.** As put to Captain Bob the
|
||||
rule was "write only to a neighbour that is reading, and look again if it
|
||||
is not". That is not enough: two neighbours each with a message for the
|
||||
other would each look, see the other not reading, and look again, for
|
||||
ever. No rule that treats both ends of a wire alike can get out of that.
|
||||
So one end of every wire may write without waiting for a reader, and one
|
||||
only: **the node with the lower number.** A node that waits to write is
|
||||
then always waiting on a higher number, so no ring of nodes can all be
|
||||
waiting on each other, and the highest of any that wait is not waiting to
|
||||
write: it is looking, and takes in what is being written to it.
|
||||
|
||||
Recommended: A. It keeps every ruling as it stands, keeps a node an F18
|
||||
and nothing more, and what it adds to the engine the chip already has.
|
||||
For this a node must know the number of the node on each of its ports.
|
||||
Whoever wires it tells it, as it is told the ways: `NEIGHBOUR ( node port
|
||||
-- )`, `v4/capsule/quit.v4`. A port it has not been told of -- a device's
|
||||
-- is written to only when what is there is waiting to read. **Two nodes
|
||||
wired together and not told of each other can still stop each other**,
|
||||
each looking for ever; they execute, but nothing passes. Telling them is
|
||||
part of wiring them. Put to Captain Bob after it was built, and ruled:
|
||||
"1 is fine."
|
||||
|
||||
**What it costs.** A node that is flooded loses messages, of every kind:
|
||||
text for it, answers to text it sent, and messages it was only passing
|
||||
on. In the test three nodes send each other 800 messages at once; 287
|
||||
arrive and 714 messages are let go (the count includes answers and text
|
||||
that was to start a node sending). Six from each to each, at once, all
|
||||
arrive. Nothing here makes a sender slow down or send again; that is
|
||||
kernel-Hermes's work in v3 and is not decided for the mesh.
|
||||
|
||||
## 8. Storage
|
||||
|
||||
@@ -344,8 +366,7 @@ Each is tested, committed and pushed before the next.
|
||||
input registers, not a message. A message not for this node, or not
|
||||
text, is let go: passing it on is step 4.
|
||||
4. **Finding the way (section 7).**
|
||||
**Built 2026-10-06; NOT done: one fault stands, below, and it needs a
|
||||
ruling.** Each node has a table of up to 16 destinations and the port
|
||||
**Built 2026-10-06.** Each node has a table of up to 16 destinations and the port
|
||||
toward each, and one port for everything else (`ROUTE ( node port -- )`,
|
||||
`DEFAULT-ROUTE ( port -- )`, `NO-ROUTES`; `(PORT-FOR)` in
|
||||
`v4/capsule/core.v4`). A message not for this node is written, whole,
|
||||
@@ -365,23 +386,39 @@ Each is tested, committed and pushed before the next.
|
||||
unchanged: `hosted-check` on three ISAs; bare metal
|
||||
`logs/20261006-115225` (amd64), `-115501` (aarch64), `-115849`
|
||||
(riscv64).
|
||||
**THE FAULT: two neighbours that write to each other at once wait for
|
||||
ever.** A write blocks until the neighbour reads (ruling, section 4.1),
|
||||
and a node that is blocked writing is not reading. Passing a message on
|
||||
is done here by the node's own programme, so a node cannot pass
|
||||
anything on, or take anything in, while it waits to write. Found so: the
|
||||
far node `SEND`s text to the middle one and then goes to send word of
|
||||
how its own text ended, which goes by the middle one; the middle one has
|
||||
done the text and goes to answer the far one. Each is writing the first
|
||||
word of a message to the other. It is not peculiar to `SEND`: any two
|
||||
messages going opposite ways on one wire at the same moment do it, and
|
||||
so would a ring of nodes each writing to the next. The last check in
|
||||
`test_host_mesh.c` shows it and is named KNOWN FAULT; it is there so the
|
||||
fault is on record and is seen to go.
|
||||
It is not a slip in the code. It is what blocking writes and a
|
||||
programme that does one thing at a time give. Section 7 said nothing
|
||||
about it and should have. What is to be done about it is not decided:
|
||||
the choices are in section 7a.
|
||||
**The fault found here, and put right 2026-10-06 (section 7a).** Two
|
||||
neighbours that wrote to each other at once waited for ever: a write
|
||||
blocks until the neighbour reads, and a node that is blocked writing is
|
||||
not reading. Found when the far node `SEND`s text to the middle one and
|
||||
then sends word of how its own text ended, which goes by the middle
|
||||
one, while the middle one answers the far one. Ruled: a node writes only
|
||||
to a neighbour that is reading, keeps what it takes in meanwhile, and
|
||||
loses and counts what it has no room for. Built so, with one thing
|
||||
added that the ruling needs (section 7a): of two neighbours the lower
|
||||
number may wait to write, and `NEIGHBOUR` tells a node who is on each
|
||||
port. `test_host_mesh.c`, 44 checks: the case that stopped the nodes
|
||||
now passes; every node sending every other six messages at once, all
|
||||
arrive; 800 at once, the nodes come to rest and every message either
|
||||
arrived or was counted. `test_fabric.c`: a node sees who is waiting to
|
||||
write to it and who to read from it, and looking disturbs neither.
|
||||
The prompt and `EMIT` take no more of the data stack than they did.
|
||||
All v4 tests at both widths and under ASan and UBSan; `hosted-check` on
|
||||
three ISAs; bare metal, with lines typed at each prompt,
|
||||
`logs/20261006-134817` (amd64), `-135044` (aarch64), `-135440`
|
||||
(riscv64).
|
||||
**Found on the way: POST was writing into the nucleus's own code.** On
|
||||
v4 `HERE` is a cell address and `C!`, `FILL` and `CMOVE` take byte
|
||||
addresses (D-1), so cases such as `65 HERE C!` wrote their bytes into
|
||||
the nucleus at cell `HERE/4`, read them back from there, and passed.
|
||||
`65 HERE C!` was watched changing cell 2223 during POST; the other
|
||||
cases of the same form were not watched, and the boots committed
|
||||
before this were not gone back to. It showed only when this step moved
|
||||
other code onto that cell and POST stopped. Ruled: the twelve cases
|
||||
are left out, marked OPEN (`v4/tools/post79_rules.py`,
|
||||
`docs/v4.0.0/POST79.md` section 4), and how `HERE` and the byte words
|
||||
are to agree is its own step. POST is 538 cases, not 550. `C!` and
|
||||
`FILL` now have fewer cases; nothing stops any other text doing what
|
||||
those cases did.
|
||||
5. **Birth and Hera's requests (section 9); the unit of five.**
|
||||
6. **Storage (section 8).**
|
||||
7. **A second unit; scaling while running; sleep, wake and kill by
|
||||
|
||||
+17
-5
@@ -3,7 +3,7 @@
|
||||
Written by `v4/tools/mkpost.py` from `v4/tools/post79_rules.py`; do not edit.
|
||||
Design: `NUCLEUS.md` section 6. The capsule is `capsules/v4/post79.4th`.
|
||||
|
||||
POST runs 550 cases. 443 are v3's, with what the hosted v3 binary did as the
|
||||
POST runs 538 cases. 431 are v3's, with what the hosted v3 binary did as the
|
||||
expected result. This file lists every other case, and why.
|
||||
|
||||
## 1. Rewritten for FORTH-79 (59)
|
||||
@@ -60,11 +60,11 @@ standard's and not v3's.
|
||||
| `CMOVE.basic` | `HERE S" Test" DUP >R HERE 10 + SWAP CMOVE CR` | stack `325`, printed `\n` | `65 PAD C! 66 PAD 1+ C! PAD PAD 10 + 2 CMOVE PAD 10 + C@ . PAD 11 + C@ . CR` | stack `empty`, prints `65 66 \n` | v3's address unit is a byte; a v4 address unit is a cell (D-1); and the v3 case leaves a value on the return stack |
|
||||
| `TYPE.basic_string` | `HERE S" Hello" DUP >R HERE SWAP CMOVE HERE R> TYPE CR` | stack `400`, printed `Hello\n` | `72 PAD C! 105 PAD 1+ C! PAD 2 TYPE CR` | stack `empty`, prints `Hi\n` | v3's address unit is a byte; a v4 address unit is a cell (D-1); and FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt |
|
||||
| `TYPE.numbers` | `HERE S" 12345" DUP >R HERE SWAP CMOVE HERE R> TYPE CR` | stack `406`, printed `12345\n` | `49 PAD C! 50 PAD 1+ C! PAD 2 TYPE CR` | stack `empty`, prints `12\n` | v3's address unit is a byte; a v4 address unit is a cell (D-1); and FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt |
|
||||
| `>IN.initial` | `>IN @ . CR` | stack `empty`, printed `852597488 \n` | unchanged | stack `empty`, prints `9 \n` | FORTH-79: >IN is the offset into the input, 9 here; v3 prints an address |
|
||||
| `>IN.initial` | `>IN @ . CR` | stack `empty`, printed `1009040112 \n` | unchanged | stack `empty`, prints `9 \n` | FORTH-79: >IN is the offset into the input, 9 here; v3 prints an address |
|
||||
| `UPDATE.no_block` | `0 SCR ! UPDATE` | error | unchanged | stack `empty`, prints nothing | v3 raises an error FORTH-79 does not ask for |
|
||||
| `SCR.after_load` | `1 LOAD SCR @ . CR` | stack `empty`, printed `` | unchanged | stack `empty`, prints `1 \n` | v3 prints nothing after 1 LOAD; SCR is still 1 from the LIST before |
|
||||
| `FIND.existing` | `FIND DUP . CR` | stack `empty`, printed `852596848 \n` | unchanged | 0 value(s) left; output not compared | FORTH-79: FIND takes the next word, DUP here, and leaves its address, which is printed |
|
||||
| `FIND.user_word` | `: test5 44 ; FIND test5 . CR` | stack `empty`, printed `852923888 \n` | unchanged | 0 value(s) left; output not compared | the address FIND leaves is printed |
|
||||
| `FIND.existing` | `FIND DUP . CR` | stack `empty`, printed `1009039472 \n` | unchanged | 0 value(s) left; output not compared | FORTH-79: FIND takes the next word, DUP here, and leaves its address, which is printed |
|
||||
| `FIND.user_word` | `: test5 44 ; FIND test5 . CR` | stack `empty`, printed `1009366512 \n` | unchanged | 0 value(s) left; output not compared | the address FIND leaves is printed |
|
||||
| `FIND.empty` | `FIND` | error | unchanged | stack `0`, prints nothing | FORTH-79: FIND leaves 0 when there is no word; v3 raises an error |
|
||||
| `VOCABULARY.cross_vocab_access` | `VOCABULARY V1 V1 DEFINITIONS : V1WORD 11 ; FORTH V1WORD . CR` | stack `empty`, printed `11 \n` | unchanged | an error | FORTH-79: a word defined in another vocabulary is not found from FORTH; v3 finds it |
|
||||
| `VOCABULARY.duplicate` | `VOCABULARY TESTVOC VOCABULARY TESTVOC` | error | unchanged | stack `empty`, prints nothing | v3 raises an error FORTH-79 does not ask for: defining a name again |
|
||||
@@ -139,7 +139,7 @@ standard's; no v3 run stands behind them.
|
||||
| `WORD.count` | `: W3 BL WORD C@ . ; W3 ABC` | stack `empty`, prints `3 ` |
|
||||
| `WORD.skips_leading` | `: W4 BL WORD COUNT TYPE ; W4 X` | stack `empty`, prints `X` |
|
||||
|
||||
## 4. v3 cases left out (27)
|
||||
## 4. v3 cases left out (39)
|
||||
|
||||
| Case | Why |
|
||||
|---|---|
|
||||
@@ -155,7 +155,19 @@ standard's; no v3 run stands behind them.
|
||||
| `].basic` | v3 did not get through it in one session |
|
||||
| `ELSE.double` | v3 did not get through it in one session |
|
||||
| `THEN.extra` | v3 did not get through it in one session |
|
||||
| `C!.basic` | OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): the case wrote to or read from the nucleus's code at cell HERE/4 |
|
||||
| `C!.zero` | OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): the case wrote to or read from the nucleus's code at cell HERE/4 |
|
||||
| `C!.high_byte` | OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): the case wrote to or read from the nucleus's code at cell HERE/4 |
|
||||
| `C!.truncation` | OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): the case wrote to or read from the nucleus's code at cell HERE/4 |
|
||||
| `C@.after_cstore` | OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): the case wrote to or read from the nucleus's code at cell HERE/4 |
|
||||
| `C@.zero_byte` | OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): the case wrote to or read from the nucleus's code at cell HERE/4 |
|
||||
| `FILL.basic` | OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): the case wrote to or read from the nucleus's code at cell HERE/4 |
|
||||
| `FILL.zero_byte` | OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): the case wrote to or read from the nucleus's code at cell HERE/4 |
|
||||
| `COUNT.string_bounds` | OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): the case wrote to or read from the nucleus's code at cell HERE/4 |
|
||||
| `CMOVE.overlap` | OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): the case wrote to or read from the nucleus's code at cell HERE/4 |
|
||||
| `CMOVE.bounds` | moves 1000 bytes over the system; v3 refuses by a bounds rule FORTH-79 has not got |
|
||||
| `TYPE.single_char` | OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): the case wrote to or read from the nucleus's code at cell HERE/4 |
|
||||
| `COUNT.zero_addr_plus_one` | OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): the case wrote to or read from the nucleus's code at cell HERE/4 |
|
||||
| `WORD.empty_input` | v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand |
|
||||
| `WORD.space_delim` | v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand |
|
||||
| `WORD.newline_delim` | v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand |
|
||||
|
||||
Reference in New Issue
Block a user