feat(v4.0.0): the wait replaces looking and the console release; step 6d written up

The engine's console release (v4_fabric_interrupt_error), a device's
pending, and the count of words since a look are removed: nothing uses
them.  MESH.md 4.1, 7b and 7c say what was built, where it differs from
what was approved and why, and what is still a limit; step 6d is marked
built, with the defects found on the way.

make -C v4 test and sanitize at both widths, hosted-check, lint; three
bare-metal boots.  POST 538 of 538, word_count=317,
dict_hash=0x6a39c0bb9d183418 on all six.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-08 01:24:09 -04:00
co-authored by Claude Opus 5.5
parent ec817a8264
commit 2b587206ee
13 changed files with 638 additions and 181 deletions
+113 -53
View File
@@ -104,6 +104,9 @@ word address `base`:
| `base` … `base + V4_PORTS − 1` | Port 0 … port `V4_PORTS − 1` |
| `base + V4_PORTS` | Any port: a read here takes from whichever port has a neighbour writing |
| `base + V4_PORTS + 1` | Which port the last read from "any" came from. Read only. |
| `base + V4_PORTS + 2`, `+ 3` | Who is waiting to write to this node, and who to read from it: section 7a |
| `base + V4_PORTS + 4 + k` | The offer on port *k*: section 7c |
| `base + 2 * V4_PORTS + 4` | The wait: section 7c |
- **A write to a port blocks the node until the neighbour has read it.**
Built 2026-10-05 for one port; the opcode after the write runs when the
@@ -304,7 +307,8 @@ kernel-Hermes's work in v3 and is not decided for the mesh.
gone; and a wait on a node that is alive and stuck is ended by Hera's
killing that node, or from the console. A message that is refused is
told to whoever waits on it, while the node that refuses it has room to
remember that it owes the telling.** As first written the rule was "no
remember that it owes the telling.** (Since step 6d, section 7c, a node
that is stuck also holds up no other node.) As first written the rule was "no
message is lost without its sender being told, and no node waits for
ever"; the review showed that was more than what was built could keep,
and section 7b.7 says exactly where it falls short. It closes two defects
@@ -371,12 +375,12 @@ Two types, beside text (1), output (2) and done (3):
- A NACK owed to this node itself -- its own message came back to it and
was let go here -- is put with its messages waiting, as if it had come
by a port.
- Paying one: if the neighbour it goes by is reading, it is sent. If not,
and that neighbour's number is the higher, it is written all the same,
as any message to it is (section 7a). If its number is the lower, the
NACK stays owed; the node goes on taking in what is written to it and
tries again, and does not sleep while it owes one. One there is no way
for, or whose way is a port with nothing on it, is dropped and counted.
- Paying one: it is offered on the port it goes by, with everything else
the node has to send, and the node sleeps in the wait (section 7c). One
there is no way for, or whose way is a port with nothing on it, is
dropped and counted. (As first mended it was written without looking to
a higher-numbered neighbour and kept back from a lower; step 6d
replaced that.)
- A message is passed on by at most 16 nodes. Its fourth word, the TTL,
carries how many more may pass it on; the node that would be the
sixteenth refuses it as it would one it had no way for. (Ruled
@@ -422,14 +426,11 @@ Two types, beside text (1), output (2) and done (3):
as usual.
- On the two products there is one node, and nobody but the console could
ever answer it: a line typed while it waits does the same.
- **A blocked write (ruled 2026-10-07).** Hera has the lowest number, so
she writes to a neighbour without looking and is blocked until it reads
(section 7a); if it never does, nothing could be typed to her again. So
when a node is blocked writing the first word of a message to another
node, and its console has a line for it, the fabric raises error 21 on
it: it gives that message up and goes on to read the line. Only on the
first word, so that half a message is never left on a wire
(`v4_fabric_interrupt_error`, `v4/include/v4/fabric.h`).
- **A blocked write (ruled 2026-10-07; replaced by section 7c the same
day).** As first mended the fabric raised error 21 on a node blocked on
the first word of a message while its console had a line for it. Since
step 6d no node is blocked on a first word: it offers it and sleeps,
and a console line reaches it as any message does (7c.3).
- **A node waiting to write to a neighbour that has been removed** is told:
the word that says which neighbours are reading also says which ports
have anything on them at all, and with nothing there the wait ends in
@@ -443,22 +444,11 @@ Two types, beside text (1), output (2) and done (3):
console. v3's pool is finite too, 32 messages, and a send beyond it is
refused.
- A node whose neighbour is asleep waits until it is woken (section 4.1).
- **A node that is stuck can hold up its neighbours until it is killed.**
A node cannot tell a neighbour that is busy for a moment from one that
is stuck for good, and there is no clock (ruling 2). So:
- a node that owes a NACK to a stuck neighbour whose number is the
higher is blocked writing it;
- a node that owes one to a stuck neighbour whose number is the lower
goes on taking in what lower-numbered neighbours write to it, but does
not sleep, and a higher-numbered neighbour with something for it
waits;
- a node with a message for a stuck neighbour waits to write it.
Each ends when Hera kills the stuck node: the wire is then empty, the
waiting node is told so (error 18) and goes on. Hera herself can always
be typed to (7b.6). None of this can happen on a node that is gone.
- A node that is waiting for an answer keeps what comes for other nodes
and passes it on when its wait ends, not before.
- ~~A node that is stuck can hold up its neighbours until it is killed.~~
**Closed by step 6d (section 7c).** As step 6c left it, a node that
owed a stuck neighbour a NACK, or had a message for it, was blocked or
went round until Hera killed the stuck node; and a node waiting for an
answer kept back what was passing through it.
- A node that was never told who is on its centre's port believes no GONE.
## 7c. The wait (ruled 2026-10-07)
@@ -525,9 +515,15 @@ a node may offer a word on each of its ports at once.)*
it by its own case 1.
- A node in the wait executes nothing and spends no heat. It shows to its
neighbours as waiting to read.
- With nothing on a port it has an offer on, a node that can take an error
has error 18 raised on it (section 4.1); a bare node waits. A neighbour that
is asleep takes nothing: the offer stands.
- With nothing on a port it has an offer on -- because nothing ever was,
or because what was there has been removed -- a node that can take an
error is woken: the fetch gives 0 and "which port" gives
`2 * V4_PORTS + k`. No error is raised: the node may be in the middle of
something that has nothing to do with that port. A bare node waits. A
neighbour that is asleep takes nothing: the offer stands. *(Changed
while it was built: as approved, error 18 was raised. That ended the
text of a node that merely had a message queued for a neighbour that
was then killed.)*
- `v4_fabric_interrupt_error`, a device's `pending`, and
`v4_node_words_since_look` are removed.
- The lone node of the two products (`v4/system/boot.c`) has the same
@@ -536,30 +532,59 @@ a node may offer a word on each of its ports at once.)*
### 7c.3 The nucleus
- *Beginning a message* (`(GATE)`): offer its first word; if a word comes
instead, take that message in and offer again; when the offer is taken,
write the rest, each word waiting for the neighbour, which has taken the
first and reads to the end.
- *Paying what is owed* (`(PAY)`): every NACK owed is offered at once,
each on the port it goes by (one to a port). A node with nothing to do
that owes one sleeps in the wait; it no longer goes round, and it no
longer writes one without looking.
- *Text from its console for a node that is waiting to begin a message*
ends what the node was doing in error 21, "Interrupted", and is then
done, as in `AWAIT` (7b.6). This is how Hera is typed to while she waits
on a stuck node.
*(Rewritten 2026-10-08 as built. The first form had the node passing a
message on by beginning it at once, as its own text does. That held the
node passing on whenever the node it was for was stuck: text typed for a
stuck node left Hera deaf. So what is passed on waits, offered.)*
- *Beginning a message from text* (`(GATE)`): offer its first word; if a
word comes instead, take that message in and offer again; when the
offer is taken, write the rest, each word waiting for the neighbour,
which has taken the first and reads to the end. With nothing on the
port it is error 18.
- *Text from its console for a node whose text is waiting there* ends that
text in error 21, "Interrupted", and is then done. This is how Hera is
typed to while her line waits on a stuck node.
- *A node with nothing to do* (`(IDLE)`) deals first with what is for
itself, the oldest first. With nothing for itself it offers, all at
once, the first message it has for each port and each NACK it owes, and
sleeps in the wait. What is taken is written and done with; the rest
stays with the messages waiting, in the order it came. A message for a
stuck node waits there and holds up nothing.
- *What a node sends other than from text* goes the same way: how text
ended (type 3), and a GONE. Each is put with the messages waiting and
goes when it can. So a node that did text for a sender that then stuck
is not held by it, and `KILL` is not held by one stuck node from telling
the next.
- *Order.* How text ended is put before the other messages waiting, and a
node does not begin another text from a sender while its answer to that
sender is still with it: what one sender is told comes in the order its
texts were done.
- *A node waiting for an answer* (`AWAIT`) does all of this but begin
text: it looks through the messages waiting for what would end its
wait, offers what is to be passed on and what it owes, and sleeps in
the wait. It no longer keeps back what passes through it.
- `NEIGHBOUR` stays: a node must know who is on its centre's port to
believe a GONE (7b.4). It is no longer needed to write.
### 7c.4 What is still a limit
- A node's own line that must begin a message to a stuck node waits until
Hera kills that node or, on Hera, a line is typed. It takes in
everything sent to it meanwhile, and holds up no one.
- A node in `AWAIT` keeps what comes for other nodes, and what it owes,
until its wait ends (7b.7).
- A node's own text that must begin a message (`SEND`, or what it prints)
to a stuck node waits until Hera kills that node or, on Hera, a line is
typed. It takes in everything sent to it meanwhile, but passes nothing
on until its text ends.
- A line typed at Hera while her text is waiting to begin a message ends
that text, whether the node it waits on is stuck or only busy.
- What a finished text printed is still begun as a message from text is,
toward the console's node: if that node is stuck, nothing could be seen
in any case.
- An answer that reaches a waiting node whose messages waiting are full
is refused, and the wait ends "Message refused".
- A message once begun is written to its end. A node that takes a first
word and is then removed leaves its writer with error 18.
- The messages waiting are finite, 400 cells: a node that is passing on
for a stuck node keeps what is for it until that node is killed, and
with no room left refuses what comes, telling the sender.
### 7c.5 Acceptance
@@ -577,7 +602,8 @@ a node may offer a word on each of its ports at once.)*
5. *Hera.* Waiting to begin a message to a stuck node, she is typed to:
the line that waited ends "Interrupted", and the new line, which kills
the stuck node, is done.
6. *An empty port.* An offer to a port with nothing on it is error 18.
6. *An empty port.* Text that begins a message on a port with nothing on
it ends in error 18.
7. *Nothing deadlocks.* The flood of section 7a's test, 800 messages
among three nodes, comes to rest.
8. *Nothing else changes.* POST 538 of 538, the existing tests, and one
@@ -1195,7 +1221,41 @@ Each is tested, committed and pushed before the next.
8. *Nothing else changes.* POST 538 of 538, the existing tests, and the
same hashes on all six systems.
**6d. The wait (section 7c).** Ruled 2026-10-07; not built.
**6d. The wait (section 7c).** Ruled 2026-10-07. **Built 2026-10-08.**
- *The engine* (`v4/src/node.c`, `fabric.c`): the offer addresses and
the wait; the fabric hands an offer over in one step; a node told
there is nothing on a port it offered on. Removed: the console
release of 7b.6, a device's `pending`, the count of words since a
look.
- *The nucleus* (`v4/capsule/core.v4`, `quit.v4`): `(OFFERS)`, `(GATE)`;
`(IDLE)` as in 7c.3, with `(MINE?)`, `(FWD-SET)`, `(FWD-GO)`,
`(PAY-SET)`, `(PAY-GO)`; `(FINISH)` and `GONE` put what they send with
the messages waiting; `AWAIT` goes on passing on and paying. The
lower-number rule, the looking again, `(PASS-ON)` and `(PAY)` are
gone. The ports moved in the nucleus's map to make room.
- *The lone node* (`v4/system/boot.c`): an offer is served as a write.
- *Found while it was built, and mended, each with a check that failed
first:* a message passed on toward a stuck node held the node passing
it on (7c.3); a node waited on a stuck sender to give it its answer;
with answers queued, a line typed during a wait was done before the
earlier line's answer had gone; a node with two answers it could not
give began no text; `KILL` waited to tell a stuck node, and a typed
line then left the rest untold; removing a node raised "No one on
that port" on the unrelated text of a neighbour with something
queued for it (7c.2). Two commits on the way were wrong as pushed:
`482cc75f` (the first of these) and `1c720ab2` (pushed with
`hosted-check` failing); `35378372` and `ec817a82` mend them.
- *Tests.* `test_fabric.c` 104 checks: acceptance 1, 2, 3, 6, and the
ring, the same-step and the asleep cases. `test_host_mesh.c` 127:
acceptance 4 for a creditor of either number, two refusals owed,
order kept through a node passing on, a waiting node passing on, a
line that waits and is typed to, and 7. `test_host_unit.c` 84:
acceptance 5; a node with answers for two stuck senders; `KILL` with
another node stuck.
- *Verified.* `make -C v4 test` and `sanitize` at both widths,
`hosted-check`, lint; three bare-metal boots, `logs/20261008-011558`
(amd64), `-011833` (aarch64), `-012159` (riscv64). On all six: POST
538 of 538, `word_count=317`, `dict_hash=0x6a39c0bb9d183418`.
7. **A second unit; scaling while running; sleep, wake and kill by
command.**
@@ -0,0 +1,165 @@
[=3hBdsDxe: loading Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0)
BdsDxe: starting Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0)
[=3hStarKernel UEFI Loader
Loading kernel from ESP...
[CKPT 001] Entered efi_main - ConOut live
RAW SERIAL UP
[CKPT 002] Serial (COM1) initialized
Monolithic build - kernel linked directly
Collecting boot information...
CmdLine: parsed OK
[CKPT 004] Command line parsed
[CKPT 005] Kernel stack allocation decided
[CKPT 006] Boot info collected (ACPI table located)
GOP: linear framebuffer found
[CKPT 007] GOP: linear framebuffer found
[CKPT 008] About to enter ExitBootServices retry loop
EBS...
EBS OK
Calling kernel_main (monolithic)...
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0
Architecture: amd64
Build: Oct 8 2026 01:15:32
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 132
Total memory: 1023 MB
Usable memory: 966 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 248741
Free pages : 247218
Used pages : 1523
Total MB : 971
Free MB : 965
Used MB : 5
VMM initialized (mapped RAM, CR3 switched)
VMM initialized (mapped RAM, CR3 switched)
VMM self-test: mapped OK at 0xffff800000000000
VMM self-test complete.
IDT installed.
APIC: init...
APIC: IA32_APIC_BASE MSR=0x00000000fee00900
APIC: stale-ISR drain: 0 EOI(s) issued
APIC: initialized (xAPIC MMIO, TPR=0, SIVR=0x1FF, EOI-clear)
APIC: init done
I/O APIC: init...
I/O APIC: base=0xfec00000, gsi_base=0, overrides=5
override: source=00 gsi=2 flags=0x0000
override: source=05 gsi=5 flags=0x000d
override: source=09 gsi=9 flags=0x000d
override: source=0a gsi=10 flags=0x000d
override: source=0b gsi=11 flags=0x000d
i8042: keyboard ACKed enable-scanning
i8042: IRQ1 enabled in controller config byte
I/O APIC: keyboard IRQ1 routed (masked)
Timer: init...
Timer: init start
Timer: PM_TMR_BLK discovered from FADT at port 1544
Timer: VM mode detected (hypervisor present).
Timer: HPET calibration disabled (VM-exit MMIO would poison timing).
Timer: WARNING: invariant TSC not present under hypervisor.
Timer: continuing in RELATIVE mode (no determinism guarantees).
Timer: RDTSCP not present; using RDTSC (less serialized).
Timer: CPUID frequency unavailable; trying PM Timer...
Timer: trust=1 (0=NONE,1=REL,2=ABS), TSC=2095226674 Hz
Timer: init done
Kernel heap initialized.
Heap statistics:
Total bytes: 536870872
Free bytes: 536870872
Used bytes: 0
Peak bytes: 0
Heap base addr: 24641536
Heap end addr: 561512448
Heartbeat: init...
APIC Timer: calibrating...
APIC Timer: apic_hz=1004544835, tick_hz=100, initial_count=10045448
APIC Timer: configured (masked, ready to start)
Heartbeat: init done
Kernel initialization complete.
Boot successful!
StarForth v4: one host node, the F18-derived engine
V4: capsule v4:nucleus-64.f18 signature: missing (unsigned)
PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=0xe4f71542c2097e28 capsule_hash=0xe4f71542c2097e28 words=311
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0xe1367a919849c14b
PARITY:V4_POST tests=538 pass=538 fail=0
PARITY:V4_SYSTEM word_count=317 dict_hash=0x6a39c0bb9d183418
PARITY:OK
POST: PASSED
[HADES][INFO ] [KRELTSC: 0] blk: raw device LBN 2048..3071 (1024 blocks)
PCI: init...
PCI: ECAM mapped (amd64)
virtio-blk: found device
[HADES][INFO ] [KRELTSC: 80444133] blk: disk 'StarForth Volume' v2 LBN 3072..789009 (785938 user b
Artemis: virtio-blk attached
ok> : SQ DUP * ;
ok
ok> 7 SQ . 3 4 U* . .
49 0 12 ok
ok> T{
UNKNOWN WORD: 'T{'
ERROR
ok> RS1
UNKNOWN WORD: 'RS1'
ERROR
ok> COLD
FORTH-79 Cold Start
System initialized.
ok
ok> 3 4 U* . .
0 12 ok
ok> FORGET U*
Protected word
ERROR
ok> 9 KERNEL-WORD ASK9 ASK9
Argument out of range
ERROR
ok> 1 BLOCK C@ . 2047 BLOCK C@ .
0 0 ok
ok> 2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 2100 BLOCK C@ .
65 ok
ok> 3072 BLOCK C@ .
0 ok
ok> 3072 BLOCK DROP UPDATE SAVE-BUFFERS
Storage refused
ERROR
ok> 9999999 BLOCK
Block out of range
ERROR
ok> 5 7 PORT!
No one on that port
ERROR
ok> 1 2 + .
3 ok
ok> 5 AWAIT
7 8 * .
Interrupted
ERROR
ok> 56 ok
ok> HERE .
8300 ok
ok> BYE
BYE: cold restart
@@ -0,0 +1,138 @@
UEFI firmware (version 2025.11-3ubuntu7.3 built at 15:40:26 on Sep 23 2026)
[=3hBdsDxe: failed to load Boot0002 "UEFI Misc Device" from PciRoot(0x0)/Pci(0x2,0x0): Not Found
BdsDxe: failed to load Boot0003 "UEFI QEMU QEMU USB HARDDRIVE 1-0000:00:04.0-1" from PciRoot(0x0)/Pci(0x4,0x0)/USB(0x0,0x0): Not Found
BdsDxe: loading Boot0004 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x6,0x0)
BdsDxe: starting Boot0004 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x6,0x0)
[=3hStarKernel UEFI Loader
Loading kernel from ESP...
[CKPT 001] Entered efi_main - ConOut live
Collecting boot information...
[CKPT 004] Command line parsed
[CKPT 005] Kernel stack allocation decided
[CKPT 006] Boot info collected (ACPI table located)
[CKPT 007] GOP: linear framebuffer found
[CKPT 008] About to enter ExitBootServices retry loop
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0
Architecture: aarch64
Build: Oct 8 2026 01:18:07
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 108
Total memory: 4093 MB
Usable memory: 4054 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 1039353
Free pages : 1037942
Used pages : 1411
Total MB : 4059
Free MB : 4054
Used MB : 5
VMM initialized (mapped RAM, CR3 switched)
VMM initialized (mapped RAM, CR3 switched)
VMM self-test: mapped OK at 0xffff800000000000
VMM self-test complete.
AArch64: running at EL1
IDT installed.
APIC: init...
PSCI: no DTB -- using HVC (QEMU virt-machine default)
GICv2: no DTB GIC node -- using QEMU virt-machine defaults
GICv2: distributor+CPU interface enabled, PPI 30
APIC: init done
Timer: init...
Timer: AArch64 generic timer initialised.
Timer: init done
Kernel heap initialized.
Heap statistics:
Total bytes: 2147483608
Free bytes: 2147483608
Used bytes: 0
Peak bytes: 0
Heap base addr: 1207959552
Heap end addr: 3355443200
Heartbeat: init...
Heartbeat: init done
Kernel initialization complete.
Boot successful!
StarForth v4: one host node, the F18-derived engine
V4: capsule v4:nucleus-64.f18 signature: missing (unsigned)
PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=0xe4f71542c2097e28 capsule_hash=0xe4f71542c2097e28 words=311
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0xe1367a919849c14b
PARITY:V4_POST tests=538 pass=538 fail=0
PARITY:V4_SYSTEM word_count=317 dict_hash=0x6a39c0bb9d183418
PARITY:OK
POST: PASSED
[HADES][INFO ] [KRELTSC: 0] blk: raw device LBN 2048..3071 (1024 blocks)
PCI: init...
virtio-blk: found device
[HADES][INFO ] [KRELTSC: 1229791] blk: disk 'StarForth Volume' v2 LBN 3072..789009 (785938 user b
Artemis: virtio-blk attached
ok> : SQ DUP * ;
ok
ok> 7 SQ . 3 4 U* . .
49 0 12 ok
ok> T{
UNKNOWN WORD: 'T{'
ERROR
ok> RS1
UNKNOWN WORD: 'RS1'
ERROR
ok> COLD
FORTH-79 Cold Start
System initialized.
ok
ok> 3 4 U* . .
0 12 ok
ok> FORGET U*
Protected word
ERROR
ok> 9 KERNEL-WORD ASK9 ASK9
Argument out of range
ERROR
ok> 1 BLOCK C@ . 2047 BLOCK C@ .
0 0 ok
ok> 2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 2100 BLOCK C@ .
65 ok
ok> 3072 BLOCK C@ .
0 ok
ok> 3072 BLOCK DROP UPDATE SAVE-BUFFERS
Storage refused
ERROR
ok> 9999999 BLOCK
Block out of range
ERROR
ok> 5 7 PORT!
No one on that port
ERROR
ok> 1 2 + .
3 ok
ok> 5 AWAIT
7 8 * .
Interrupted
ERROR
ok> 56 ok
ok> HERE .
8300 ok
ok> BYE
BYE: cold restart
@@ -0,0 +1,208 @@
OpenSBI v1.8
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|
Platform Name : riscv-virtio,qemu
Platform Features : medeleg
Platform HART Count : 1
Platform HART Protection : pmp
Platform IPI Device : aclint-mswi
Platform Timer Device : aclint-mtimer @ 10000000Hz
Platform Console Device : uart8250
Platform HSM Device : ---
Platform PMU Device : ---
Platform Reboot Device : syscon-reboot
Platform Shutdown Device : syscon-poweroff
Platform Suspend Device : ---
Platform CPPC Device : ---
Firmware Base : 0x80000000
Firmware Size : 321 KB
Firmware RW Offset : 0x40000
Firmware RW Size : 65 KB
Firmware Heap Offset : 0x47000
Firmware Heap Size : 37 KB (total), 0 KB (reserved), 12 KB (used), 23 KB (free)
Firmware Scratch Size : 4096 B (total), 1464 B (used), 2632 B (free)
Runtime SBI Version : 3.0
Standard SBI Extensions : ipi,pmu,srst,sse,hsm,rfnc,fwft,time,base,legacy,dbcn,dbtr
Experimental SBI Extensions : none
Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000080040000-0x000000008005ffff M: (F,R,W) S/U: ()
Domain0 Region01 : 0x0000000080000000-0x000000008003ffff M: (F,R,X) S/U: ()
Domain0 Region02 : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W)
Domain0 Region03 : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region04 : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region05 : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W)
Domain0 Region06 : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W)
Domain0 Region07 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address : 0x0000000020000000
Domain0 Next Arg1 : 0x00000000bfe00000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes
Domain0 SysSuspend : yes
Boot HART ID : 0
Boot HART Domain : root
Boot HART Priv Version : v1.12
Boot HART Base ISA : rv64imafdch
Boot HART ISA Extensions : sstc,zicntr,zihpm,zicboz,zicbom,sdtrig,svadu
Boot HART PMP Count : 16
Boot HART PMP Granularity : 2 bits
Boot HART PMP Address Bits : 54
Boot HART MHPM Info : 16 (0x0007fff8)
Boot HART Debug Triggers : 2 triggers
Boot HART MIDELEG : 0x0000000000001666
Boot HART MEDELEG : 0x0000000000f4b509
[=3hRISC-V EDK2 firmware version 2025.11-3ubuntu7.3
Press ESCAPE within 5 seconds for boot options ERROR: C40000002:V03051002 I0 6D33944A-EC75-4855-A54D-809C75241F6C 83FFF850
BdsDxe: failed to load Boot0001 "UEFI Misc Device" fr
om PciRoot(0x0)/Pci(0x1,0x0): Not Found
[=3hStarKernel UEFI Loader
Loading kernel from ESP...
[CKPT 001] Entered efi_main - ConOut live
Monolithic build - kernel linked directly
Collecting boot information...
CmdLine: parsed OK
[CKPT 004] Command line parsed
[CKPT 005] Kernel stack allocation decided
[CKPT 006] Boot info collected (ACPI table located)
GOP: linear framebuffer found
[CKPT 007] GOP: linear framebuffer found
[CKPT 008] About to enter ExitBootServices retry loop
Calling kernel_main (monolithic)...
riscv64 item 4.3.5a: satp state at kernel entry (before switch)
satp.MODE = 0x000000000000000a
satp.PPN = 0x00000000000bf868
__kernel_start = 0x00000000bdc9baaa
riscv64: satp cleared -- Bare mode, explicit (item 4.3.5a)
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0
Architecture: riscv64
Build: Oct 8 2026 01:21:21
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 100
Total memory: 1020 MB
Usable memory: 975 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 250269
Free pages : 249617
Used pages : 652
Total MB : 977
Free MB : 975
Used MB : 2
VMM initialized (mapped RAM, CR3 switched)
VMM initialized (mapped RAM, CR3 switched)
VMM self-test: mapped OK at 0xffff800000000000
VMM self-test complete.
IDT installed.
APIC: init...
PLIC: no DTB PLIC node -- using QEMU virt-machine default (base=0x0c000000)
PLIC: init (S-mode context 1, threshold=0)
APIC: init done
Timer: init...
Timer: RISC-V time CSR @ 10000000 Hz (FALLBACK, no devicetree)
Timer: init done
Kernel heap initialized.
Heap statistics:
Total bytes: 536870872
Free bytes: 536870872
Used bytes: 0
Peak bytes: 0
Heap base addr: 2214588416
Heap end addr: 2751459328
Heartbeat: init...
Heartbeat: init done
Kernel initialization complete.
Boot successful!
StarForth v4: one host node, the F18-derived engine
V4: capsule v4:nucleus-64.f18 signature: missing (unsigned)
PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=0xe4f71542c2097e28 capsule_hash=0xe4f71542c2097e28 words=311
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0xe1367a919849c14b
PARITY:V4_POST tests=538 pass=538 fail=0
PARITY:V4_SYSTEM word_count=317 dict_hash=0x6a39c0bb9d183418
PARITY:OK
POST: PASSED
[HADES][INFO ] [KRELTSC: 0] blk: raw device LBN 2048..3071 (1024 blocks)
PCI: init...
virtio-blk: found device
[HADES][INFO ] [KRELTSC: 18926187] blk: disk 'StarForth Volume' v2 LBN 3072..789009 (785938 user b
Artemis: virtio-blk attached
ok> : SQ DUP * ;
ok
ok> 7 SQ . 3 4 U* . .
49 0 12 ok
ok> T{
UNKNOWN WORD: 'T{'
ERROR
ok> RS1
UNKNOWN WORD: 'RS1'
ERROR
ok> COLD
FORTH-79 Cold Start
System initialized.
ok
ok> 3 4 U* . .
0 12 ok
ok> FORGET U*
Protected word
ERROR
ok> 9 KERNEL-WORD ASK9 ASK9
Argument out of range
ERROR
ok> 1 BLOCK C@ . 2047 BLOCK C@ .
0 0 ok
ok> 2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 2100 BLOCK C@ .
65 ok
ok> 3072 BLOCK C@ .
0 ok
ok> 3072 BLOCK DROP UPDATE SAVE-BUFFERS
Storage refused
ERROR
ok> 9999999 BLOCK
Block out of range
ERROR
ok> 5 7 PORT!
No one on that port
ERROR
ok> 1 2 + .
3 ok
ok> 5 AWAIT
7 8 * .
Interrupted
ERROR
ok> 56 ok
ok> HERE .
8300 ok
ok> BYE
BYE: cold restart
+8 -2
View File
@@ -141,8 +141,14 @@ 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.
is gone; a node believes that only from its centre.
**The wait** (`MESH.md` 7c, step 6d). A node does not write the first word
of a message: it offers it, and sleeps until it is taken or a word comes
for it. What a node has to pass on, the answers it has to give and the
refusals it owes all wait with it, offered, and go when they can. So a
node that is stuck holds up no other: only text that must itself send to
the stuck node waits, and on Hera a typed line ends that.
**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
-15
View File
@@ -26,7 +26,6 @@ typedef struct {
int (*take)(void *self, v4_cell value); /* the node wrote `value`: 1 if it is taken, 0 if not yet */
int (*give)(void *self, v4_cell *value); /* the node is reading: 1 with a word in *value, 0 if not yet */
void *self;
int (*pending)(void *self); /* 1 if it has a word to give that has not been asked for; may be 0: it never says */
} v4_device;
/* One end of a wire. */
@@ -56,7 +55,6 @@ typedef struct {
v4_place *place;
unsigned capacity;
v4_cell gone_error; /* see v4_fabric_gone_error; 0: none */
v4_cell interrupt_error; /* see v4_fabric_interrupt_error; 0: none */
} v4_fabric;
/* An empty fabric with room for `capacity` nodes, in `places`. */
@@ -73,19 +71,6 @@ void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity);
* is how a fabric begins, nothing is raised. */
void v4_fabric_gone_error(v4_fabric *f, v4_cell code);
/* A NODE BLOCKED WRITING TO A NODE THAT DOES NOT READ. Of two neighbours
* one may write to the other without looking (docs/v4.0.0/MESH.md 7a), and
* is then blocked until the other reads; if the other never does, it is
* blocked for ever, and if it is the node the console is on, nothing can be
* typed to it again. So: when a node that can take an error is blocked
* writing to another node, on the first word of a message (node.h,
* v4_node_words_since_look: nothing of the message has gone), and a device
* on one of its own ports says it has something pending for it, the error
* `code` is raised on the node: it gives up that message and goes on, to
* read what the device has. Only a device with a `pending` function can
* do this. With `code` 0, which is how a fabric begins, it never happens. */
void v4_fabric_interrupt_error(v4_fabric *f, v4_cell code);
/* More room: `places`, of `capacity` no less than before, takes over; every
* node keeps its number and its wiring. */
void v4_fabric_grow(v4_fabric *f, v4_place *places, unsigned capacity);
-5
View File
@@ -65,11 +65,6 @@ typedef struct {
* port". */
#define V4_ERROR_NO_ONE 18
/* The nucleus's error for a node let go of a message it was blocked on the
* first word of, because its console has a line for it (fabric.h,
* v4_fabric_interrupt_error): "Interrupted". */
#define V4_ERROR_INTERRUPTED 21
/* The description of the nucleus built from v4/capsule (the generated file
* defines it). */
extern const v4_image v4_capsule_image;
-8
View File
@@ -113,7 +113,6 @@ typedef struct {
unsigned last_from; /* the port the last read from "any" took its word from */
unsigned writers; /* bit k: the neighbour on port k is blocked writing to this node */
unsigned readers; /* bit k: the neighbour on port k is blocked reading from this node */
unsigned since_look; /* words taken from this node since it last fetched either of those */
unsigned offers; /* bit k: a word is offered on port k (the wait: docs/v4.0.0/MESH.md 7c) */
v4_cell offer[V4_PORTS]; /* the words offered */
int waiting; /* non-zero: the read it is blocked in is the wait */
@@ -225,13 +224,6 @@ void v4_node_port_attach(v4_node *n, v4_cell base);
/* The neighbour has taken what the node wrote. */
void v4_node_port_served(v4_node *n);
/* How many words the node has had taken since it last looked at its
* neighbours (a fetch of either of the two status addresses). A node that
* looks before it begins a message (MESH.md 7a) and is blocked writing with
* this at 0 is blocked on the first word of the message: nothing of it has
* gone. */
unsigned v4_node_words_since_look(const v4_node *n);
/* THERE IS NO ONE ON THE PORT the node is blocked at -- writing to it, or
* reading from that one port -- and there will not be. A node that can
* take an error (it has an error register and a fault table: node.h,
-23
View File
@@ -18,7 +18,6 @@ void v4_fabric_init(v4_fabric *f, v4_place *places, unsigned capacity)
f->place = places;
f->capacity = capacity;
f->gone_error = 0;
f->interrupt_error = 0;
for (i = 0; i < capacity; i++) clear_place(&places[i]);
}
@@ -27,10 +26,6 @@ void v4_fabric_gone_error(v4_fabric *f, v4_cell code)
f->gone_error = code;
}
void v4_fabric_interrupt_error(v4_fabric *f, v4_cell code)
{
f->interrupt_error = code;
}
void v4_fabric_grow(v4_fabric *f, v4_place *places, unsigned capacity)
{
@@ -239,24 +234,6 @@ unsigned v4_fabric_step(v4_fabric *f)
}
}
/* a node blocked on the first word of a message to a node that is not
* reading, while a device of its own has something for it */
if (f->interrupt_error != 0)
for (i = 0; i < f->capacity; i++) {
v4_node *n;
unsigned k;
if (!awake(f, i)) continue;
n = &f->place[i].node->n;
if (!n->asking || f->place[i].wire[n->ask_port].kind != V4_WIRE_NODE || v4_node_words_since_look(n) != 0) continue;
for (k = 0; k < V4_PORTS; k++) {
const v4_wire *w = &f->place[i].wire[k];
if (w->kind == V4_WIRE_DEVICE && w->device->pending && w->device->pending(w->device->self)) {
done += (unsigned)v4_node_port_gone(n, f->interrupt_error);
break;
}
}
}
/* a node blocked at one port that nothing is wired to */
if (f->gone_error != 0)
for (i = 0; i < f->capacity; i++) {
+2 -8
View File
@@ -126,8 +126,8 @@ v4_cell v4_node_fetch(v4_node *n, v4_cell addr)
{
int port = v4_node_port_index(n, addr);
if (port == (int)V4_PORTS + 1) return (v4_cell)n->last_from;
if (port == (int)V4_PORTS + 2) { n->since_look = 0; return (v4_cell)n->writers; }
if (port == (int)V4_PORTS + 3) { n->since_look = 0; return (v4_cell)n->readers; }
if (port == (int)V4_PORTS + 2) return (v4_cell)n->writers;
if (port == (int)V4_PORTS + 3) return (v4_cell)n->readers;
if (port == (int)V4_PORT_WAIT) {
/* the wait: the word that came, or 0 for an offer taken */
if (!n->given) return 0;
@@ -193,7 +193,6 @@ void v4_node_port_attach(v4_node *n, v4_cell base)
n->last_from = 0;
n->writers = 0;
n->readers = 0;
n->since_look = 0;
n->offers = 0;
n->waiting = 0;
n->offer_asked = 0;
@@ -273,17 +272,12 @@ void v4_node_born(v4_node *n, v4_cell base)
void v4_node_port_served(v4_node *n)
{
n->asking = 0;
n->since_look++;
if (n->offer_asked) { /* what was served was an offer: the wait is over */
n->offer_asked = 0;
v4_node_port_give(n, V4_PORTS + n->ask_port, 0);
}
}
unsigned v4_node_words_since_look(const v4_node *n)
{
return n->since_look;
}
int v4_node_port_gone(v4_node *n, v4_cell code)
{
-60
View File
@@ -70,10 +70,6 @@ static void count_for_ever(void)
v4_asm_branch(&as, V4_OP_JUMP, again);
}
/* a device that says whether it has something pending */
static int *dev_has;
static int device_has(void *self) { (void)self; return *dev_has; }
/* Let time pass until nothing more can happen, or `max` steps. */
static unsigned settle(unsigned max)
{
@@ -392,62 +388,6 @@ int main(void)
CHECK(ND(a)->n.asking && ND(a)->n.request == 5 && ND(c)->n.reading && ND(c)->n.mem[OUT] == 0, "looking takes nothing and gives nothing: both wait as they did");
CHECK(!ND(b)->n.stopped && ND(b)->n.reading, "and the one that looked did not wait to look");
/* ---- a node blocked on the first word of a message, while a device of its own has something for it ---- */
{
static int has; /* what the device says it has pending */
static stream none;
static v4_device dev;
v4_fabric_init(&f, places, 4);
for (i = 0; i < 3; i++) v4_node_reset(&pool[i].n);
a = loaded(0); /* looks about, then writes two words to its port 0 */
LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); O(DROP);
LIT(PB + 0); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); wait_for_ever();
b = loaded(1); /* never reads */
LIT(OUT); O(BANG_A);
{
v4_cell again = v4_asm_label(&as);
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
v4_asm_branch(&as, V4_OP_JUMP, again);
}
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3), "a writer that looks first, wired to a node that never reads");
dev.take = stream_take; dev.give = 0; dev.self = &none; dev.pending = device_has; has = 1; dev_has = &has;
CHECK(v4_fabric_wire_device(&f, a, 5, &dev), "and to a device, which has something for it");
for (i = 0; i < 40; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.asking && ND(a)->n.request == 1 && v4_node_words_since_look(&ND(a)->n) == 0, "the writer is blocked on its first word, and waits: the fabric has no error for it");
v4_fabric_interrupt_error(&f, 21);
for (i = 0; i < 40; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.asking && ND(a)->n.faults == 0, "nor is a bare node let go when it has: it cannot take an error");
v4_node_error_attach(&ND(a)->n, OUT + 20);
v4_node_fault_attach(&ND(a)->n, 900);
has = 0;
for (i = 0; i < 40; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.asking && ND(a)->n.faults == 0, "a node that can take one waits all the same while the device has nothing for it");
has = 1;
(void)v4_fabric_step(&f);
CHECK(!ND(a)->n.asking && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 21 && ND(a)->n.mem[OUT + 20] == 21,
"when it has, the writer is let go and the error raised on it");
/* once the first word has gone, it is not let go: the message must be finished */
v4_fabric_init(&f, places, 4);
for (i = 0; i < 3; i++) v4_node_reset(&pool[i].n);
a = loaded(0);
LIT(PB + (v4_cell)V4_PORTS + 3); O(BANG_B); O(FETCH_B); O(DROP);
LIT(PB + 0); O(BANG_B); LIT(1); O(STORE_B); LIT(2); O(STORE_B); wait_for_ever();
b = loaded(1); /* reads one word, and no more */
LIT(PB + 3); O(BANG_B); O(FETCH_B); LIT(OUT); O(BANG_A);
{
v4_cell again = v4_asm_label(&as);
O(FETCH_A); LIT(1); O(ADD); O(STORE_A);
v4_asm_branch(&as, V4_OP_JUMP, again);
}
CHECK(v4_asm_ok(&as) && v4_fabric_wire(&f, a, 0, b, 3) && v4_fabric_wire_device(&f, a, 5, &dev), "the same, to a node that reads one word and no more");
v4_node_error_attach(&ND(a)->n, OUT + 20);
v4_node_fault_attach(&ND(a)->n, 900);
v4_fabric_interrupt_error(&f, 21);
for (i = 0; i < 60; i++) (void)v4_fabric_step(&f);
CHECK(ND(a)->n.asking && ND(a)->n.request == 2 && ND(a)->n.faults == 0, "blocked on its second word with the device still pending, the writer waits");
}
/* ---- the wait (MESH.md 7c): a node offers a word and sleeps ---- */
{
static stream none;
+1 -2
View File
@@ -71,8 +71,7 @@ static int console_take(void *self, v4_cell value)
}
return 1;
}
static int console_pending(void *self) { (void)self; return going_at < going.count; }
static const v4_device console = { console_take, console_give, 0, console_pending };
static const v4_device console = { console_take, console_give, 0 };
static int is_empty(const char *s) { return s[0] == 0; }
+3 -5
View File
@@ -140,7 +140,7 @@ static int host_born(void *self)
v4_node_fault_attach(&pool[k].n, w_fault);
v4_blocks_init(&node_blocks[k], BUF0_W);
if (k != 0) { /* Hera's kernel is wired where she is made */
node_kernel[k].take = node_kernel_take; node_kernel[k].give = 0; node_kernel[k].self = &pool[k]; node_kernel[k].pending = 0;
node_kernel[k].take = node_kernel_take; node_kernel[k].give = 0; node_kernel[k].self = &pool[k];
if (!v4_fabric_wire_device(&f, (unsigned)place, 0, &node_kernel[k])) return -1;
}
return place;
@@ -194,7 +194,7 @@ static int kernel_take(void *self, v4_cell value) /* a request */
if (value < FIRST_REQUEST || !v4_manage_serve(&manage, hera_place, (unsigned)(value - FIRST_REQUEST))) refused++;
return 1;
}
static const v4_device kernel = { kernel_take, kernel_give, 0, 0 };
static const v4_device kernel = { kernel_take, kernel_give, 0 };
/* ---- the console: a device on Hera's port 1 ---- */
#define CONSOLE_ID 1
@@ -237,8 +237,7 @@ static int console_take(void *self, v4_cell value)
}
return 1;
}
static int console_pending(void *self) { (void)self; return going_at < going.count; }
static const v4_device console = { console_take, console_give, 0, console_pending };
static const v4_device console = { console_take, console_give, 0 };
/* Is there a node that will execute at the next step? A step in which a
* node only faulted counts nothing done (exec.h), and it goes on from its
@@ -384,7 +383,6 @@ int main(void)
/* ---- Hera is born empty, and takes in the nucleus ---- */
v4_fabric_init(&f, places, PLACES);
v4_fabric_gone_error(&f, V4_ERROR_NO_ONE);
v4_fabric_interrupt_error(&f, V4_ERROR_INTERRUPTED);
place = host_born(0);
CHECK(place == 0, "a node is born");
hera_place = (unsigned)place;