feat(v4.0.0): five nodes from nothing -- Hera asks for nodes, births and joins them

MESH.md step 5, in the fabric under test; the products are still one
node each until steps 8 and 9.

manage.c: what Hera asks of whoever holds the fabric, eleven requests by
KERNEL-WORD -- NODE-ME -BORN -WIRE -UNWIRE -SLEEP -WAKE -KILL -PARITY and
CAPSULE-OPEN -CELL -LINE. Nucleus: PORT!, SEND-ON, AWAIT, (SEAL).
capsules/v4/hera.4th: BIRTH and UNIT, the unit rule in FORTH and nowhere
else. The dictionary hash moves to the engine (v4_image_dict_hash).

test_host_unit.c, 34 checks, 64-bit: Hera is born empty, takes the
nucleus through her port, FORTH-79, POST and her capsule; 10 UNIT; four
nodes are born, each takes the nucleus and FORTH-79 through its port from
Hera and passes POST 538/538; four parities, one dictionary hash; they
talk, and a message between two corners not wired goes by Hera.

All v4 tests at both widths and under ASan+UBSan. hosted-check on three
ISAs. Bare metal: logs/20261006-143934 (amd64), -144204 (aarch64),
-144601 (riscv64).

Open, recorded in MESH.md: not run at 32 bits; a node that never answers
leaves Hera waiting; the capsules are read from files in the test, not
from the baked directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-06 14:48:23 -04:00
co-authored by Claude Opus 5.5
parent f9c034cadd
commit 348eed7fa1
18 changed files with 1250 additions and 40 deletions
+8 -2
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-10-06T17:54:02Z -->
<!-- Generated by mkcapsule --manifest 2026-10-06T18:45:22Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
@@ -30,6 +30,7 @@
| `turtle.4th` | 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5107, 5108 | `0x4d470418ca543365` | n/a |
| `user-font-demo.4th` | 4200, 4201, 4202 | `0xce1fd7d1b581a56d` | n/a |
| `v4:forth79.4th` | 6000, 6001, 6002 | `0x4055641ee17d176b` | n/a |
| `v4:hera.4th` | 8000, 8001, 8002, 8003, 8004 | `0x93a3160c447d7641` | n/a |
| `v4:post79.4th` | 7000, 7001, 7002, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7011, 7012, 7013, 7014, 7015, 7016, 7017, 7018, 7019, 7020, 7021, 7022, 7023, 7024, 7025, 7026, 7027, 7028, 7029, 7030, 7031, 7032, 7033, 7034, 7035, 7036, 7037, 7038, 7039, 7040, 7041, 7042, 7043, 7044, 7045, 7046, 7047, 7048, 7049, 7050, 7051, 7052, 7053, 7054, 7055, 7056, 7057, 7058, 7059, 7060, 7061, 7062, 7063 | `0xb26d1b974884179d` | n/a |
| `workload-0.4th` | 2200, 2201 | `0x93f86f60aeba8feb` | n/a |
| `workload-1-lite.4th` | 5058, 5059 | `0x44a7a7e3176dcc8d` | n/a |
@@ -435,10 +436,15 @@
| 7061 | `v4:post79.4th` | `0xb26d1b974884179d` | ok |
| 7062 | `v4:post79.4th` | `0xb26d1b974884179d` | ok |
| 7063 | `v4:post79.4th` | `0xb26d1b974884179d` | ok |
| 8000 | `v4:hera.4th` | `0x93a3160c447d7641` | ok |
| 8001 | `v4:hera.4th` | `0x93a3160c447d7641` | ok |
| 8002 | `v4:hera.4th` | `0x93a3160c447d7641` | ok |
| 8003 | `v4:hera.4th` | `0x93a3160c447d7641` | ok |
| 8004 | `v4:hera.4th` | `0x93a3160c447d7641` | ok |
## Conflicts
None.
---
*38 capsule(s) scanned. Re-run `mkcapsule --manifest <dir>` to refresh.*
*39 capsule(s) scanned. Re-run `mkcapsule --manifest <dir>` to refresh.*
+80
View File
@@ -0,0 +1,80 @@
Block 8000
( hera.4th -- Hera: the node that has the others born and )
( manages them. docs/v4.0.0/MESH.md sections 9 and 10. )
( She asks whoever holds the fabric for a node, wires it, )
( and sends it its capsules through the port that joins )
( them. The unit rule is here and nowhere else: four outer )
( nodes round a centre, an outer node wired only inside its )
( unit. )
( Text for another node is built in PAD. )
VARIABLE (T#)
: (T0) ( -- ) 0 (T#) ! ;
: (TC) ( c -- ) PAD (T#) @ + C! 1 (T#) +! ;
: (TS) ( baddr u -- )
DUP IF 0 DO DUP I + C@ (TC) LOOP DROP ELSE DROP DROP THEN ;
: (TN) ( n -- ) 0 <# #S #> (TS) 32 (TC) ;
: (T) ( -- baddr u ) PAD (T#) @ ;
Block 8001
( The node being born: its place in the fabric, the number )
( it is to have, and the port of this node that leads to it. )
VARIABLE (KID) VARIABLE (KID#) VARIABLE (KIDP)
: (FAIL) ( -- )
." BIRTH: node " (KID#) @ . ." did not do as it was told" CR
-1 NODE-ERROR ! ;
: (TOLD) ( how -- ) 1 - IF (FAIL) THEN ;
: (TELL) ( baddr u -- ) (KID#) @ SEND (KID#) @ AWAIT (TOLD) ;
( the nucleus, a capsule of F18 code, cell by cell to its port )
: (NUCLEUS) ( -- )
S" v4:nucleus-64.f18" CAPSULE-OPEN 0= IF (FAIL) THEN
BEGIN CAPSULE-CELL WHILE (KIDP) @ PORT! REPEAT DROP ;
( a capsule of FORTH blocks, a line at a time )
: (LINES) ( baddr u -- ) CAPSULE-OPEN 0= IF (FAIL) THEN
BEGIN PAD CAPSULE-LINE DUP 0< 0= WHILE PAD SWAP (TELL) REPEAT
DROP ;
Block 8002
( who it is, where its printing goes, and that everything )
( not told of goes by its port 2, where this node is )
: (WHO) ( -- )
(T0) (KID#) @ (TN) S" (ME) ! " (TS)
(CONSOLE) @ (TN) S" (CONSOLE) ! 2 DEFAULT-ROUTE " (TS)
(ME) @ (TN) S" 2 NEIGHBOUR" (TS)
(T) 0 (KIDP) @ SEND-ON (KID#) @ AWAIT (TOLD) ;
( number port -- place A NODE IS BORN on that port of )
( this one: empty, then the nucleus, then FORTH-79 and POST. )
: BIRTH ( number port -- place )
(KIDP) ! (KID#) !
NODE-BORN DUP 0< IF (FAIL) THEN (KID) !
NODE-ME (KIDP) @ (KID) @ 2 NODE-WIRE 0= IF (FAIL) THEN
(NUCLEUS)
(KID#) @ (KIDP) @ ROUTE (KID#) @ (KIDP) @ NEIGHBOUR (WHO)
S" v4:forth79.4th" (LINES) S" v4:post79.4th" (LINES)
Block 8003
S" (SEAL)" (TELL)
(KID) @ (KID#) @ NODE-PARITY
(KID) @ ;
( Two outer nodes are joined: port 3 of X to port 4 of Y, )
( and each is told the other is there. )
VARIABLE (XP) VARIABLE (XN) VARIABLE (YP) VARIABLE (YN)
: (X) ( place number -- ) (XN) ! (XP) ! ;
: (Y) ( place number -- ) (YN) ! (YP) ! ;
: (SAY) ( them port me -- ) (KID#) !
(T0) OVER (TN) DUP (TN) S" ROUTE " (TS)
SWAP (TN) (TN) S" NEIGHBOUR" (TS) (T) (TELL) ;
: (JOIN) ( -- )
(XP) @ 3 (YP) @ 4 NODE-WIRE 0= IF (FAIL) THEN
(YN) @ 3 (XN) @ (SAY) (XN) @ 4 (YN) @ (SAY) ;
Block 8004
( n -- A UNIT: four outer nodes, n+1 to n+4, on ports 2 )
( to 5 of this one, and joined in a square: 1-2, 2-4, 4-3, )
( 3-1. The corners across from each other are not wired: )
( what one sends the other goes by this node. )
VARIABLE (U)
VARIABLE (P1) VARIABLE (P2) VARIABLE (P3) VARIABLE (P4)
: (N) ( k -- number ) (U) @ + ;
: UNIT ( n -- ) (U) !
1 (N) 2 BIRTH (P1) ! 2 (N) 3 BIRTH (P2) !
3 (N) 4 BIRTH (P3) ! 4 (N) 5 BIRTH (P4) !
(P1) @ 1 (N) (X) (P2) @ 2 (N) (Y) (JOIN)
(P2) @ 2 (N) (X) (P4) @ 4 (N) (Y) (JOIN)
(P4) @ 4 (N) (X) (P3) @ 3 (N) (Y) (JOIN)
(P3) @ 3 (N) (X) (P1) @ 1 (N) (Y) (JOIN) ;
Binary file not shown.
+61 -5
View File
@@ -289,11 +289,11 @@ checked in this step.
## 9. Birth, and Hera
**Proposal.** Hera asks, through the port where her requests go
(`ENGINE.md` 3.3), for a node to be added and wired; she then sends the
newborn its capsules through the port that joins them. Putting a node to
sleep, waking it, and removing it are requests of the same kind. Only
Hera's requests are honoured.
**Proposal, built as step 5 (section 10).** Hera asks, through the port
where her requests go (`ENGINE.md` 3.3), for a node to be added and wired;
she then sends the newborn its capsules through the port that joins them.
Putting a node to sleep, waking it, and removing it are requests of the
same kind. Only Hera's requests are honoured.
The unit rule (ruling 5) is Hera's, in capsule code: it is what she does
with those requests. The engine and the host do not know it.
@@ -420,6 +420,62 @@ Each is tested, committed and pushed before the next.
`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.**
**Done 2026-10-06, in the fabric under test; the products are still one
node each until steps 8 and 9.** Section 9's proposal, as built:
- *What Hera asks* (`v4/include/v4/manage.h`, `v4/src/manage.c`). Eleven
requests, each a word on her made with `KERNEL-WORD`, written to the
port her requests go to: `NODE-ME`, `NODE-BORN`, `NODE-WIRE`,
`NODE-UNWIRE`, `NODE-SLEEP`, `NODE-WAKE`, `NODE-KILL`, `NODE-PARITY`;
and `CAPSULE-OPEN`, `CAPSULE-CELL`, `CAPSULE-LINE`, by which she reads
a capsule the host has found and checked. A node is named to the host
by its place in the fabric; its number, which messages go by, is the
nodes' own. Only the node the host has wired to this is answered.
- *What a node needs to do it* (`v4/capsule/quit.v4`): `PORT! ( w port
-- )`, a cell written to a port as it is; `SEND-ON ( baddr u node port
-- )`, text by a port named, for a neighbour with no number yet;
`AWAIT ( node -- how )`, blocked until that node's word of how text
ended comes, keeping every other message for later; `(SEAL)`, what is
in the dictionary now is the system.
- *Hera's capsule* (`capsules/v4/hera.4th`, blocks 8000 to 8004), FORTH.
`BIRTH ( number port -- place )`: a node is asked for and wired to
that port; the nucleus is sent it cell by cell; it is told its number,
its console and that Hera is on its port 2; FORTH-79 and POST are sent
it a line at a time, each waited for; it seals; its parity is
recorded. `UNIT ( n -- )`: four births, on ports 2 to 5, and the four
joined in a square, each told of the two beside it. The unit rule is
there and nowhere else.
`v4/tests/test_host_unit.c`, 34 checks, 64-bit: Hera is born empty and
takes the nucleus through her port, then FORTH-79, POST (538 of 538) and
her capsule from the console; `10 UNIT`; four nodes are born, each
passes POST, the host records four parities with one dictionary hash;
all five wait and execute nothing; text for each outer node is done
there and what it prints comes back; `COLD` on one comes back to the
nucleus and FORTH-79; one outer node sends text to the one beside it,
and to the one across from it by way of Hera. Acceptance 1 and 2, in
the fabric. All v4 tests at both widths and under ASan and UBSan. The
single-node products are unchanged but for the nucleus's new words:
`hosted-check` on three ISAs; bare metal `logs/20261006-143934`
(amd64), `-144204` (aarch64), `-144601` (riscv64).
**What stands in, in the test only:** the capsules are read from the
files the build bakes in, and the nucleus capsule is written from the
nucleus as the test assembles it, not found in the baked directory with
its hash and signature checked; that is the host's part and comes with
step 8. Each node has a disk of its own attached at birth, as the boot's
one node has; storage through the ports is step 6.
**Not done, and open:**
- The test is not run on 32-bit cells: the POST capsule's expected
results are 64-bit v3's and the nucleus capsule is `nucleus-64`. POST
has never been run at 32 bits.
- A node that never answers leaves Hera waiting for ever in `AWAIT`;
she cannot then kill it. What she does about a birth that does not
finish is not decided.
- What an outer node prints while Hera is giving birth waits in Hera's
400 cells until she is idle, and is lost if there is more than they
hold. POST prints one line.
- An outer node has no port to the kernel, so no kernel words: no
`BYE`, and nothing it asks is honoured, as ruled.
- The suite now takes about four minutes, eight under the sanitizers:
five POSTs.
6. **Storage (section 8).**
7. **A second unit; scaling while running; sleep, wake and kill by
command.**
@@ -0,0 +1,130 @@
[=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 6 2026 14:39:07
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 131
Total memory: 1023 MB
Usable memory: 968 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 249251
Free pages : 247728
Used pages : 1523
Total MB : 973
Free MB : 967
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=2100263096 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=1007167846, tick_hz=100, initial_count=10071678
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=0xdac6d69e81818b71 capsule_hash=0xdac6d69e81818b71 words=310
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x4abf568309282a76
V4: capsule v4:post79.4th signature: missing (unsigned)
PARITY:V4_POST tests=538 pass=538 fail=0
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb26d1b974884179d capsule_hash=0xb26d1b974884179d dict_hash=0xd110b71c6c09dd8e
PARITY:OK
POST: PASSED
ok> : SQ DUP * ;
ok
ok> 7 SQ . 3 4 U* . .
49 0 12 ok
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> BYE
BYE: cold restart
@@ -0,0 +1,104 @@
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 6 2026 14:41:37
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 108
Total memory: 4093 MB
Usable memory: 4056 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 1039866
Free pages : 1038455
Used pages : 1411
Total MB : 4061
Free MB : 4056
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=0xdac6d69e81818b71 capsule_hash=0xdac6d69e81818b71 words=310
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x4abf568309282a76
V4: capsule v4:post79.4th signature: missing (unsigned)
PARITY:V4_POST tests=538 pass=538 fail=0
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb26d1b974884179d capsule_hash=0xb26d1b974884179d dict_hash=0xd110b71c6c09dd8e
PARITY:OK
POST: PASSED
ok> : SQ DUP * ;
ok
ok> 7 SQ . 3 4 U* . .
49 0 12 ok
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> BYE
BYE: cold restart
@@ -0,0 +1,174 @@
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 = 0x00000000bdea38f8
riscv64: satp cleared -- Bare mode, explicit (item 4.3.5a)
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0
Architecture: riscv64
Build: Oct 6 2026 14:45:22
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 100
Total memory: 1020 MB
Usable memory: 977 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 250780
Free pages : 250128
Used pages : 652
Total MB : 979
Free MB : 977
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=0xdac6d69e81818b71 capsule_hash=0xdac6d69e81818b71 words=310
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x4abf568309282a76
V4: capsule v4:post79.4th signature: missing (unsigned)
PARITY:V4_POST tests=538 pass=538 fail=0
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb26d1b974884179d capsule_hash=0xb26d1b974884179d dict_hash=0xd110b71c6c09dd8e
PARITY:OK
POST: PASSED
ok> : SQ DUP * ;
ok
ok> 7 SQ . 3 4 U* . .
49 0 12 ok
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> BYE
BYE: cold restart
+9
View File
@@ -123,6 +123,15 @@ 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 and POST 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.
**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
+12 -4
View File
@@ -181,13 +181,18 @@ header CMOVE
SET: (MQ-HEAD) a! !
(MQ#) a! @ -1 + ! ;
\ ( port to -- ) take in a message. B is at the port, by its address,
\ and `to` is the message's first word, already read from it. A length
\ ( port to -- ) the seven words of a message being taken in, and the
\ port, go into (MQ-HDR). B is at the port, by its address, and `to` is
\ the message's first word, already read from it.
: (TAKE-HDR)
(MQ-HDR) a! !+ 5 FOR @b !+ UNEXT ! ;
\ ( -- ) the message whose seven words are in (MQ-HDR) is kept with the
\ messages waiting: its text is read from the port B is at. A length
\ below zero, or above what a message carries, is kept as -1 with no text:
\ a longer one is read to its end first, so that what follows it is not
\ taken for a message.
: (TAKE)
(MQ-HDR) a! !+ 5 FOR @b !+ UNEXT ! \ its seven words, and the port
: (TAKE-KEEP)
(MQ-HDR)+6 a! @ -if SIZED
drop -1 (MQ-HDR)+6 a! ! 0 jump COUNTED
SIZED: dup -1025 + -if LONG drop 3 + 2/ 2/ jump COUNTED
@@ -203,6 +208,9 @@ header CMOVE
GONE: drop
LOSE: (LOST) a! @ 1 + ! ;
\ ( port to -- ) take in a message and keep it
: (TAKE) (TAKE-HDR) jump (TAKE-KEEP)
\ ( port -- ) wait until a message may be begun on the port, by its
\ address, taking in whatever is being written to this node meanwhile; and
\ leave B at the port. The port is kept in (GATE-PORT), not on the stack:
+56 -7
View File
@@ -175,13 +175,11 @@ header DEFAULT-ROUTE
header NO-ROUTES
: NO-ROUTES 0 (ROUTE#) a! ! 0 (ROUTE-DEFAULT) a! ! ;
\ ( baddr u node -- ) send the node u characters of text to interpret. It
\ is on its way when SEND returns; what it prints there goes to that node's
\ console. With no way to the node it is error 12. The write waits for the
\ neighbour to read, and is not begun until it may be (core.v4, (GATE)).
header SEND
: SEND
dup (PORT-FOR) if NOWAY
\ ( baddr u node port -- ) send the node u characters of text to
\ interpret, by the port given, by its address. It is on its way when this
\ returns. The write waits for the neighbour to read, and is not begun
\ until it may be (core.v4, (GATE)).
: (SEND-ON)
(GATE) !b \ to
1 (HDR)
dup !b \ length
@@ -189,8 +187,59 @@ header SEND
FOR dup C@ over 1 + C@ 8* + over 2 + C@ 8* 8* + over 3 + C@ 8* 8* 8* + !b 4 + NEXT
drop ;
NONE: drop drop ;
\ ( baddr u node -- ) send the node u characters of text to interpret,
\ by the way this node knows to it. What it prints there goes to that
\ node's console. With no way to the node it is error 12.
header SEND
: SEND
dup (PORT-FOR) if NOWAY jump (SEND-ON)
NOWAY: drop drop drop drop NODE-ERROR b! 12 !b ;
\ ( baddr u node port -- ) the same by a port this node names, by its
\ number, whatever ways it knows: for a neighbour that has no number of
\ its own yet, to which text is sent as to node 0.
header SEND-ON
: SEND-ON
dup -if POS jump BAD
POS: -PORTS + -if BAD drop (PORT) + jump (SEND-ON)
BAD: drop drop drop drop drop NODE-ERROR b! 12 !b ;
\ ( node -- how ) WAIT FOR THE NODE'S WORD OF HOW TEXT ENDED: 0 QUIT,
\ 1 completed, 2 an error. This node is blocked reading its ports until a
\ message of type 3 comes for it from that node; every other message that
\ comes meanwhile is kept with the messages waiting. It waits for ever
\ for a node that never answers.
header AWAIT
: AWAIT
(AWAIT-FROM) a! !
L: (PORT)+8 b! @b (PORT)+9 b! @b (PORT) + dup b! SWAP (TAKE-HDR)
(MQ-HDR) a! @ (ME) a! @ xor if A1 drop jump KEEP
A1: drop (MQ-HDR)+2 a! @ -3 + if A2 drop jump KEEP
A2: drop (MQ-HDR)+1 a! @ (AWAIT-FROM) a! @ xor if A3 drop jump KEEP
A3: drop (MQ-HDR)+6 a! @ -4 + if A4 drop jump KEEP
A4: drop @b ; \ its one word
KEEP: (TAKE-KEEP) jump L
\ ( w port -- ) write a cell to the port, by its number. It waits until
\ the neighbour has taken it. This is how a node is sent a capsule of F18
\ code (docs/v4.0.0/MESH.md section 5): the cells go as they are, not as a
\ message, to a node that is executing from its ports.
header PORT!
: PORT!
dup -if POS jump BAD
POS: -PORTS + -if BAD drop (PORT) + b! !b ;
BAD: drop drop drop NODE-ERROR b! 12 !b ;
\ ( -- ) WHAT IS IN THE DICTIONARY NOW IS THE SYSTEM: COLD comes back to
\ it and FORGET will not go below it. Whoever has loaded a node's capsules
\ has it do this (system.v4, (BOOT) and FENCE).
header (SEAL)
: (SEAL)
DP a! @ (BOOT) a! !
(LATEST) a! @ (BOOT)+1 a! !
DP a! @ 3 + 2/ 2/ FENCE a! ! ;
\ FORTH-79: clear the return stack, set execution mode, return control to
\ the terminal; no message is given. The data stack is left as it is.
\ NODE-ERROR, by name: how a definition written in FORTH raises an error.
+6
View File
@@ -65,6 +65,12 @@ extern const v4_image v4_capsule_image;
* back to, and FORGET will not go below it. */
void v4_image_seal(v4_node *n, const v4_image *im);
/* The dictionary hash of a node started from `im`: FNV-1a over its memory
* below HERE, a cell at a time, low byte first, and then LATEST. It does
* not depend on the machine: every build of one commit, with one cell
* width, gives the same hash for the same dictionary. */
uint64_t v4_image_dict_hash(const v4_node *n, const v4_image *im);
/* A node newly born (node.h), with the registers the nucleus expects of the
* node it runs on -- and `disk`, `blocks` blocks of 1024 bytes, as its block
* storage, or none if `disk` is 0. Its memory is empty and it is blocked
+81
View File
@@ -0,0 +1,81 @@
/* manage.h -- what Hera asks of whoever holds the fabric.
* docs/v4.0.0/MESH.md section 9.
*
* Hera decides which nodes exist and are awake (MESH.md ruling 7). She is
* a node like any other and cannot make one: she asks, by a write to the
* port where her requests go (ENGINE.md 3.3), and the write blocks her until
* she is answered. Each request is a word on her, made with KERNEL-WORD
* (v4/capsule/compile.v4); its arguments are what it finds on her data
* stack and its results are what is left there.
*
* These are the requests, in order. The n-th is request `first + n`, where
* `first` is whatever number the host gives the first of them:
*
* NODE-ME ( -- place ) the place of the node that asks
* NODE-BORN ( -- place | -1 ) a node is born, empty, listening at
* its ports; -1: there is no room
* NODE-WIRE ( a pa b pb -- flag ) port pa of the node at a is wired
* to port pb of the node at b
* NODE-UNWIRE ( a pa -- ) what is on that port is cut
* NODE-SLEEP ( place -- ) it executes nothing until woken
* NODE-WAKE ( place -- )
* NODE-KILL ( place -- ) it is removed, whatever it was doing
* NODE-PARITY ( place number -- ) the host records the node's parity:
* its number and its dictionary hash
* CAPSULE-OPEN ( baddr u -- flag ) the capsule of that name is found
* and checked, and is read from its
* start; 0: there is none, or it is
* not what it says it is
* CAPSULE-CELL ( -- w flag ) the next cell of a capsule of F18
* code; 0 0 when there are no more
* CAPSULE-LINE ( baddr -- u | -1 ) the next line of a capsule of
* FORTH blocks, put at baddr: at
* most 64 characters; -1 at its end
*
* A place is where a node is in the fabric (fabric.h); it is the host's
* name for the node. A node's number, which messages are addressed by, is
* the nodes' own affair: Hera gives a newborn its number by telling it.
*
* Only the node the host has wired to this is answered, so only Hera's
* requests are honoured. The unit rule (MESH.md ruling 5) is not here: it
* is what Hera does with these, in her capsule.
*
* Nothing here uses the C library: the bare-metal kernel links this file.
*/
#ifndef V4_MANAGE_H
#define V4_MANAGE_H
#include "v4/fabric.h"
#define V4_MANAGE_WORDS 11
extern const char *const v4_manage_words[V4_MANAGE_WORDS];
#define V4_MANAGE_LINE 64 /* the most characters in a line of a block */
typedef struct {
v4_fabric *f;
/* A node is born: the host finds it room, adds it to the fabric and
* gives it what a node's hardware gives it. Its place, or -1. */
int (*born)(void *self);
/* The node that was at `place` has been removed: its room is free. */
void (*gone)(void *self, unsigned place, v4_fabric_node *was);
/* The capsule called `name`, `len` characters: found, its hash and
* signature checked, and to be read from its start. 1, or 0. */
int (*open)(void *self, const char *name, unsigned len);
/* The next cell of the open capsule: 1 with it in *w, 0 at its end. */
int (*cell)(void *self, v4_cell *w);
/* The next line of the open capsule that is part of a block, into
* `text`, at most V4_MANAGE_LINE characters: its length, or -1. */
int (*line)(void *self, char *text);
/* The node at `place`, which Hera calls `number`: record its parity. */
void (*parity)(void *self, unsigned place, v4_cell number, const v4_node *n);
void *self;
} v4_manage;
/* The node at `place` has written request `n` (0 is NODE-ME) to the port
* its requests go to. Its arguments are taken from its data stack and its
* results left there. Returns 1; 0 if `n` is not one of the requests, and
* then nothing has been done. */
int v4_manage_serve(const v4_manage *m, unsigned place, unsigned n);
#endif /* V4_MANAGE_H */
+6 -1
View File
@@ -180,8 +180,13 @@ unsigned v4_fabric_step(v4_fabric *f)
{
unsigned i, done = 0;
/* a node that is blocked fetches nothing, and is looked about for when
* it is next to execute */
for (i = 0; i < f->capacity; i++)
if (awake(f, i)) look_about(f, i);
if (awake(f, i)) {
const v4_node *n = &f->place[i].node->n;
if (!n->stopped && !n->asking && !(n->reading && !n->given)) look_about(f, i);
}
for (i = 0; i < f->capacity; i++)
if (awake(f, i)) done += v4_exec_step_word(&f->place[i].node->n, &f->place[i].node->es, &f->place[i].node->h);
+27
View File
@@ -35,3 +35,30 @@ void v4_image_seal(v4_node *n, const v4_image *im)
n->mem[im->boot_cells + 1] = n->mem[im->latest];
n->mem[im->fence] = (n->mem[im->dp] + 3) / 4;
}
/* FNV-1a over the node's memory below HERE, a cell at a time, low byte
* first, and then LATEST. */
#define FNV_OFFSET 0xcbf29ce484222325ULL
#define FNV_PRIME 0x00000100000001b3ULL
static uint64_t hash_cell(uint64_t h, v4_cell c)
{
v4_ucell u = (v4_ucell)c;
unsigned i;
for (i = 0; i < V4_CELL_BITS / 8; i++) {
h ^= (uint64_t)((u >> (8 * i)) & 0xffu);
h *= FNV_PRIME;
}
return h;
}
uint64_t v4_image_dict_hash(const v4_node *n, const v4_image *im)
{
uint64_t h = FNV_OFFSET;
v4_cell here = (n->mem[im->dp] + 3) / 4, k;
if (here < 0) here = 0;
if (here > (v4_cell)V4_NODE_WORDS) here = (v4_cell)V4_NODE_WORDS;
for (k = 0; k < here; k++) h = hash_cell(h, n->mem[k]);
return hash_cell(h, n->mem[im->latest]);
}
+107
View File
@@ -0,0 +1,107 @@
/* manage.c -- what Hera asks of whoever holds the fabric. See manage.h.
*
* Nothing here uses the C library: the bare-metal kernel links this file.
*/
#include "v4/manage.h"
const char *const v4_manage_words[V4_MANAGE_WORDS] = {
"NODE-ME", "NODE-BORN", "NODE-WIRE", "NODE-UNWIRE", "NODE-SLEEP", "NODE-WAKE", "NODE-KILL", "NODE-PARITY",
"CAPSULE-OPEN", "CAPSULE-CELL", "CAPSULE-LINE"
};
/* A character of the node's memory: four to a cell, the first lowest, as
* the nucleus keeps text (v4/capsule/core.v4). */
static int in_memory(v4_cell baddr) { return baddr >= 0 && (baddr >> 2) < (v4_cell)V4_NODE_WORDS; }
static char char_at(const v4_node *n, v4_cell baddr)
{
return (char)(((v4_ucell)n->mem[baddr >> 2] >> (8 * (unsigned)(baddr & 3))) & 0xFFu);
}
static void char_put(v4_node *n, v4_cell baddr, char c)
{
unsigned shift = 8 * (unsigned)(baddr & 3);
v4_ucell cell = (v4_ucell)n->mem[baddr >> 2];
cell = (cell & ~((v4_ucell)0xFFu << shift)) | ((v4_ucell)(unsigned char)c << shift);
n->mem[baddr >> 2] = (v4_cell)cell;
}
static unsigned place_of(v4_cell x) { return x < 0 ? ~0u : (unsigned)x; }
int v4_manage_serve(const v4_manage *m, unsigned place, unsigned request)
{
v4_fabric_node *asker = v4_fabric_node_at(m->f, place);
v4_node *n;
v4_cell a, b, c, d;
if (!asker || request >= V4_MANAGE_WORDS) return 0;
n = &asker->n;
switch (request) {
case 0: /* NODE-ME ( -- place ) */
v4_dstack_push(&n->ds, (v4_cell)place);
break;
case 1: /* NODE-BORN ( -- place | -1 ) */
v4_dstack_push(&n->ds, (v4_cell)(m->born ? m->born(m->self) : -1));
break;
case 2: /* NODE-WIRE ( a pa b pb -- flag ) */
d = v4_dstack_pop(&n->ds); c = v4_dstack_pop(&n->ds); b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds);
v4_dstack_push(&n->ds, v4_fabric_wire(m->f, place_of(a), place_of(b), place_of(c), place_of(d)) ? -1 : 0);
break;
case 3: /* NODE-UNWIRE ( a pa -- ) */
b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds);
(void)v4_fabric_unwire(m->f, place_of(a), place_of(b));
break;
case 4: /* NODE-SLEEP ( place -- ) */
a = v4_dstack_pop(&n->ds);
v4_fabric_sleep(m->f, place_of(a));
break;
case 5: /* NODE-WAKE ( place -- ) */
a = v4_dstack_pop(&n->ds);
v4_fabric_wake(m->f, place_of(a));
break;
case 6: { /* NODE-KILL ( place -- ) */
v4_fabric_node *was;
a = v4_dstack_pop(&n->ds);
if (place_of(a) == place) break; /* not itself: there would be no one to answer */
was = v4_fabric_remove(m->f, place_of(a));
if (was && m->gone) m->gone(m->self, place_of(a), was);
break;
}
case 7: { /* NODE-PARITY ( place number -- ) */
v4_fabric_node *it;
b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds);
it = v4_fabric_node_at(m->f, place_of(a));
if (it && m->parity) m->parity(m->self, place_of(a), b, &it->n);
break;
}
case 8: { /* CAPSULE-OPEN ( baddr u -- flag ) */
char name[64];
unsigned i, len;
b = v4_dstack_pop(&n->ds); a = v4_dstack_pop(&n->ds);
len = b < 0 ? 0u : (unsigned)b;
if (len == 0 || len > sizeof name || !in_memory(a) || !in_memory(a + (v4_cell)len - 1) || !m->open) {
v4_dstack_push(&n->ds, 0);
break;
}
for (i = 0; i < len; i++) name[i] = char_at(n, a + (v4_cell)i);
v4_dstack_push(&n->ds, m->open(m->self, name, len) ? -1 : 0);
break;
}
case 9: /* CAPSULE-CELL ( -- w flag ) */
a = 0;
if (m->cell && m->cell(m->self, &a)) { v4_dstack_push(&n->ds, a); v4_dstack_push(&n->ds, -1); }
else { v4_dstack_push(&n->ds, 0); v4_dstack_push(&n->ds, 0); }
break;
case 10: { /* CAPSULE-LINE ( baddr -- u | -1 ) */
char text[V4_MANAGE_LINE];
int len, i;
a = v4_dstack_pop(&n->ds);
len = m->line ? m->line(m->self, text) : -1;
if (len > V4_MANAGE_LINE) len = V4_MANAGE_LINE;
if (len > 0 && (!in_memory(a) || !in_memory(a + len - 1))) len = -1;
for (i = 0; i < len; i++) char_put(n, a + i, text[i]);
v4_dstack_push(&n->ds, (v4_cell)len);
break;
}
}
return 1;
}
+1 -21
View File
@@ -56,29 +56,9 @@ static void say_hex(const v4_boot *b, uint64_t v)
/* ---- hashing: FNV-1a, 64 bits, as kernel/src/vm/parity.c ----------------- */
#define FNV_OFFSET 0xcbf29ce484222325ULL
#define FNV_PRIME 0x00000100000001b3ULL
static uint64_t hash_cell(uint64_t h, v4_cell c)
{
v4_ucell u = (v4_ucell)c;
unsigned i;
for (i = 0; i < V4_CELL_BITS / 8; i++) {
h ^= (uint64_t)((u >> (8 * i)) & 0xffu);
h *= FNV_PRIME;
}
return h;
}
uint64_t v4_boot_dict_hash(const v4_node *n, const v4_image *im)
{
uint64_t h = FNV_OFFSET;
v4_cell here = (n->mem[im->dp] + 3) / 4, k;
if (here < 0) here = 0;
if (here > (v4_cell)V4_NODE_WORDS) here = (v4_cell)V4_NODE_WORDS;
for (k = 0; k < here; k++) h = hash_cell(h, n->mem[k]);
return hash_cell(h, n->mem[im->latest]);
return v4_image_dict_hash(n, im);
}
/* how many words FORTH holds: the list from LATEST, each entry's link in the
+2
View File
@@ -81,6 +81,7 @@
#define MQ_HEAD (BUF0_W - 35) /* the messages waiting to be dealt with: where the oldest begins, */
#define MQ_TAIL (BUF0_W - 36) /* where the next will go, */
#define MQ_COUNT (BUF0_W - 37) /* and how many cells they take */
#define AWAIT_FROM (BUF0_W - 39) /* the node whose word of how text ended is waited for */
#define GATE_PORT (BUF0_W - 38) /* the port address a message is about to be begun on */
#define NEAR (BUF0_W - 64) /* for each port, the number of the node on the other end; 0: not told */
#define MQ_HDR (BUF0_W - 56) /* a message being taken in: its seven words and the port it came on */
@@ -194,6 +195,7 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned
v4_text_constant(tx, "-PORTS", -(v4_cell)V4_PORTS);
v4_text_constant(tx, "(NEAR)", NEAR);
v4_text_constant(tx, "(GATE-PORT)", GATE_PORT);
v4_text_constant(tx, "(AWAIT-FROM)", AWAIT_FROM);
v4_text_constant(tx, "(MQ)", MQ_W);
v4_text_constant(tx, "(MQ-END)", MQ_W + MQ_CELLS);
v4_text_constant(tx, "-MQ-ROOM", -(v4_cell)(MQ_CELLS - 7)); /* cells taken + a text's words + this is below zero while the message fits */
+386
View File
@@ -0,0 +1,386 @@
/* test_host_unit.c -- five nodes come up from nothing.
* docs/v4.0.0/MESH.md section 9, step 5; acceptance 1 and 2.
*
* [11]---[12]
* | \ / | the four outer nodes are each on a
* console --1 [10] | port of Hera, 10, and joined in a
* kernel --0 | / \ | square; the corners across from each
* [13]---[14] other are not wired
*
* Hera is born empty and is given the nucleus through the port her requests
* go to, as the boot gives it (v4/system/boot.c). She is told who she is,
* given the words that ask for nodes (v4/include/v4/manage.h), FORTH-79,
* POST, and her own capsule, capsules/v4/hera.4th. Then she is told
* `10 UNIT`, and everything after that is hers: she asks for four nodes,
* wires them, sends each the nucleus cell by cell through the port that
* joins them and then FORTH-79 and POST a line at a time, and joins them.
*
* What stands in here for the host: the capsules are read from the files
* the build bakes in, and the nucleus is assembled as mkimage assembles it.
* Each node has a disk of its own, attached when it is born, as the boot's
* one node has; storage through the ports is step 6.
*/
#include "v4/capsule.h"
#include "v4/fabric.h"
#include "v4/image.h"
#include "v4/manage.h"
#include "v4/message.h"
#include "v4/text.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "host_map.h"
static int failures = 0, checks = 0;
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
#define PLACES 10u
#define DISK_BLOCKS 64u
static v4_text tx;
static v4_node nucleus; /* the nucleus, assembled: what the capsule of F18 code is written from */
static v4_cell w_idle, w_fault;
static v4_fabric_node pool[PLACES];
static int pool_used[PLACES];
static unsigned char disks[PLACES][DISK_BLOCKS * V4_BLOCK_BYTES];
static v4_place places[PLACES];
static v4_fabric f;
static v4_image im; /* only what the dictionary hash needs of it */
/* ---- the capsules ---- */
static v4_cell f18[2u * V4_NODE_WORDS + 64u]; /* the nucleus as a capsule of F18 code */
static unsigned f18_count;
static void f18_put(void *self, v4_cell w) { (void)self; if (f18_count < sizeof f18 / sizeof f18[0]) f18[f18_count++] = w; }
typedef struct { char *text; long len, at; int in_block; unsigned cells_at; int is_f18; } reader;
static int read_file(reader *r, const char *name)
{
char path[600];
FILE *in;
snprintf(path, sizeof path, "%s/../../capsules/v4/%s", V4_CAPSULE_DIR, name);
in = fopen(path, "rb");
if (!in) return 0;
free(r->text);
fseek(in, 0, SEEK_END); r->len = ftell(in); fseek(in, 0, SEEK_SET);
r->text = (char *)malloc((size_t)r->len + 1);
if (!r->text || fread(r->text, 1, (size_t)r->len, in) != (size_t)r->len) { fclose(in); return 0; }
fclose(in);
r->at = 0; r->in_block = 0; r->is_f18 = 0;
return 1;
}
/* the next line of a block that has anything on it: its length, or -1 */
static int next_line(reader *r, char *text)
{
while (r->at < r->len) {
const char *p = r->text + r->at;
long n = 0;
while (r->at + n < r->len && p[n] != '\n') n++;
r->at += n + 1;
if (n >= 7 && memcmp(p, "Block ", 6) == 0 && p[6] >= '0' && p[6] <= '9') { r->in_block = 1; continue; }
if (n && p[n - 1] == '\r') n--;
if (!r->in_block || n == 0) continue;
if (n > V4_MANAGE_LINE) return -1;
memcpy(text, p, (size_t)n);
return (int)n;
}
return -1;
}
/* ---- what Hera's requests are answered with ---- */
static reader hera_reads; /* the capsule Hera has open */
static unsigned born_count, parity_count;
static v4_cell parity_number[8];
static uint64_t parity_hash[8];
static int host_born(void *self)
{
unsigned k;
int place;
(void)self;
for (k = 0; k < PLACES && pool_used[k]; k++) { }
if (k == PLACES) return -1;
place = v4_fabric_add(&f, &pool[k], PORT);
if (place < 0) return -1;
pool_used[k] = 1;
born_count++;
/* what a node's hardware gives it; there is nothing in its memory */
v4_node_stack_regs_attach(&pool[k].n, DSTACK_REG, RSTACK_REG);
v4_node_error_attach(&pool[k].n, NODE_ERROR);
v4_node_fault_attach(&pool[k].n, w_fault);
memset(disks[k], 0, sizeof disks[k]);
v4_node_storage_attach(&pool[k].n, STORAGE_REG, disks[k], DISK_BLOCKS);
return place;
}
static void host_gone(void *self, unsigned place, v4_fabric_node *was)
{
(void)self; (void)place;
pool_used[was - pool] = 0;
}
static int host_open(void *self, const char *name, unsigned len)
{
(void)self;
if (len == 17 && memcmp(name, "v4:nucleus-64.f18", 17) == 0) { hera_reads.is_f18 = 1; hera_reads.cells_at = 0; return 1; }
if (len == 14 && memcmp(name, "v4:forth79.4th", 14) == 0) return read_file(&hera_reads, "forth79.4th");
if (len == 13 && memcmp(name, "v4:post79.4th", 13) == 0) return read_file(&hera_reads, "post79.4th");
return 0;
}
static int host_cell(void *self, v4_cell *w)
{
(void)self;
if (!hera_reads.is_f18 || hera_reads.cells_at >= f18_count) return 0;
*w = f18[hera_reads.cells_at++];
return 1;
}
static int host_line(void *self, char *text)
{
(void)self;
return hera_reads.is_f18 ? -1 : next_line(&hera_reads, text);
}
static void host_parity(void *self, unsigned place, v4_cell number, const v4_node *n)
{
(void)self; (void)place;
if (parity_count < 8) { parity_number[parity_count] = number; parity_hash[parity_count] = v4_image_dict_hash(n, &im); }
parity_count++;
}
static const v4_manage manage = { &f, host_born, host_gone, host_open, host_cell, host_line, host_parity, 0 };
/* ---- the kernel: a device on Hera's port 0 ---- */
#define FIRST_REQUEST 1 /* the number of NODE-ME on her */
static unsigned hera_place, given_cells, refused;
static int kernel_give(void *self, v4_cell *value) /* the nucleus, to a node that has nothing in it */
{
(void)self;
if (given_cells >= f18_count) return 0;
*value = f18[given_cells++];
return 1;
}
static int kernel_take(void *self, v4_cell value) /* a request */
{
(void)self;
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 };
/* ---- the console: a device on Hera's port 1 ---- */
#define CONSOLE_ID 1
static v4_message going, coming;
static unsigned going_at;
static char printed[8192]; /* what has come back since the console last sent */
static unsigned printed_len;
static v4_cell printed_from;
static int ended;
static v4_cell ended_from, ended_how;
static unsigned post_passed[32]; /* by node number: POST tallies with no failures seen from it */
static unsigned post_other; /* tallies with failures */
static int console_give(void *self, v4_cell *value)
{
(void)self;
if (going_at >= going.count) return 0;
*value = going.word[going_at++];
return 1;
}
static int console_take(void *self, v4_cell value)
{
(void)self;
if (v4_message_word(&coming, value)) {
if (v4_message_type(&coming) == V4_MSG_OUTPUT) {
unsigned i, chars = v4_message_length(&coming), start = printed_len;
for (i = 0; i < chars && printed_len + 1 < sizeof printed; i++) printed[printed_len++] = v4_message_char(&coming, i);
printed[printed_len] = 0;
printed_from = v4_message_from(&coming);
if (strstr(printed + start, "PARITY:V4_POST")) {
if (strstr(printed + start, " fail=0\n") && printed_from >= 0 && printed_from < 32) post_passed[printed_from]++;
else post_other++;
}
} else if (v4_message_type(&coming) == V4_MSG_DONE) {
ended++;
ended_from = v4_message_from(&coming);
ended_how = coming.word[V4_MSG_HEADER];
}
coming.count = 0;
}
return 1;
}
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
* fault handler at the next. */
static int any_running(void)
{
unsigned k;
for (k = 0; k < PLACES; k++) {
const v4_fabric_node *x = v4_fabric_node_at(&f, k);
if (x && !places[k].asleep && !x->n.stopped && !x->n.asking && !(x->n.reading && !x->n.given)) return 1;
}
return 0;
}
/* Send text to a node and let everything that follows from it happen. */
static const char *tell(v4_cell node, const char *text)
{
unsigned long steps = 0;
printed_len = 0; printed[0] = 0; printed_from = -1;
ended = 0; ended_from = -1; ended_how = -1;
if (!v4_message_text(&going, node, CONSOLE_ID, V4_MSG_TEXT, text, (unsigned)strlen(text))) return "(too long)";
going_at = 0;
while (steps < 4000000000ul && (v4_fabric_step(&f) != 0 || any_running())) steps++;
if (steps >= 4000000000ul) return "(still running)";
return printed;
}
static int told(v4_cell node, const char *text) { return tell(node, text)[0] == 0 && ended == 1 && ended_how == V4_TEXT_COMPLETED; }
/* Every line of a capsule of FORTH blocks, told to a node from the console:
* how many lines did not complete. */
static unsigned load(v4_cell node, const char *file)
{
static reader r;
char line[V4_MANAGE_LINE + 1];
unsigned bad = 0;
int n;
if (!read_file(&r, file)) return 1;
while ((n = next_line(&r, line)) >= 0) {
line[n] = 0;
(void)tell(node, line);
if (ended != 1 || ended_how != V4_TEXT_COMPLETED) { if (!bad) { const v4_node *q = &pool[0].n; printf(" %s: \"%s\" -> \"%s\" ended=%d how=%ld; stopped=%d asking=%d port=%u reading=%d rport=%u p=%ld fault=%u@%ld\n", file, line, printed, ended, (long)ended_how, q->stopped, q->asking, q->ask_port, q->reading, q->read_port, (long)q->p, q->fault_kind, (long)q->fault_addr); } bad++; }
}
return bad;
}
static v4_node *node_numbered(v4_cell number)
{
unsigned k;
for (k = 0; k < PLACES; k++) if (v4_fabric_node_at(&f, k) && v4_fabric_node_at(&f, k)->n.mem[ME] == number) return &v4_fabric_node_at(&f, k)->n;
return 0;
}
static v4_uheat_t clock_of(v4_cell number)
{
unsigned k;
for (k = 0; k < PLACES; k++) if (v4_fabric_node_at(&f, k) && v4_fabric_node_at(&f, k)->n.mem[ME] == number) return v4_fabric_node_at(&f, k)->es.anticlock;
return 0;
}
static int waiting(v4_cell number)
{
const v4_node *n = node_numbered(number);
return n && n->reading && !n->given && n->read_port == V4_PORT_ANY;
}
int main(void)
{
unsigned i;
int place;
printf("v4 unit tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
if (V4_CELL_BITS != 64) {
/* The capsules the unit is built from are the 64-bit products': the
* expected results in post79.4th are v3's, on 64-bit cells, and the
* nucleus capsule is nucleus-64. POST has never been run on 32-bit
* cells (docs/v4.0.0/MESH.md, step 5). */
printf(" not run at this width: the capsules are the 64-bit products'\n");
return 0;
}
{
static const char *const files[] = { "core.v4", "input.v4", "dict.v4", "codegen.v4", "compile.v4", "quit.v4", "forth.v4",
"numout.v4", "words.v4", "system.v4", "qmath.v4", "blocks.v4", "log.v4", "acl.v4" };
CHECK(host_load(&tx, &nucleus, files, 14), "the nucleus assembles");
}
CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx));
if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; }
w_idle = v4_text_word(&tx, "(IDLE)");
w_fault = v4_text_word(&tx, "(FAULTS)");
nucleus.mem[DP] = DICT_W * 4; /* the variables, as at switch-on (tools/mkimage.c) */
nucleus.mem[LATEST] = v4_text_latest(&tx);
nucleus.mem[CFP] = CFS_W;
nucleus.mem[BASE] = 10;
nucleus.mem[FENCE] = DICT_W;
nucleus.mem[BOOT_CELLS] = nucleus.mem[DP];
nucleus.mem[BOOT_CELLS + 1] = nucleus.mem[LATEST];
nucleus.mem[LOG_LEVEL] = 2;
nucleus.mem[CONTEXT] = LATEST;
nucleus.mem[CURRENT] = LATEST;
nucleus.mem[SRC] = TIB;
nucleus.mem[LINE_STATUS] = 1;
HOST_MESSAGES_EMPTY(&nucleus);
(void)v4_capsule_write(&nucleus, w_idle, f18_put, 0);
CHECK(f18_count > 1000 && f18_count < sizeof f18 / sizeof f18[0], "the nucleus is a capsule of F18 code: %u cells", f18_count);
memset(&im, 0, sizeof im);
im.dp = DP; im.latest = LATEST;
/* ---- Hera is born empty, and takes in the nucleus ---- */
v4_fabric_init(&f, places, PLACES);
place = host_born(0);
CHECK(place == 0, "a node is born");
hera_place = (unsigned)place;
for (i = 0; i < 4096 && pool[0].n.mem[i] == 0; i++) { }
CHECK(i == 4096 && pool[0].n.p == PORT + (v4_cell)V4_PORT_ANY, "with nothing in it, listening at its ports");
CHECK(v4_fabric_wire_device(&f, hera_place, 0, &kernel) && v4_fabric_wire_device(&f, hera_place, 1, &console), "the kernel is on its port 0 and the console on its port 1");
going.count = 0; going_at = 0;
for (i = 0; i < 400000 && v4_fabric_step(&f) != 0; i++) { }
CHECK(given_cells == f18_count && pool[0].n.reading && pool[0].n.read_port == V4_PORT_ANY && !pool[0].n.stopped,
"it takes in the nucleus through its port and waits for a message: %u of %u cells", given_cells, f18_count);
CHECK(told(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE") && ended_from == 10, "it is told it is 10, and where the console is");
{
char text[600];
size_t at = 0;
for (i = 0; i < V4_MANAGE_WORDS; i++) at += (size_t)snprintf(text + at, sizeof text - at, "%u KERNEL-WORD %s ", FIRST_REQUEST + i, v4_manage_words[i]);
CHECK(told(10, text), "and given the words that ask for nodes and capsules: \"%s\"", printed);
}
CHECK(strcmp(tell(10, "NODE-ME ."), "0 ") == 0 && refused == 0, "it asks where it is, and is answered");
CHECK(load(10, "forth79.4th") == 0, "it takes in FORTH-79");
CHECK(load(10, "post79.4th") == 0 && post_passed[10] == 1 && post_other == 0, "and passes POST");
CHECK(load(10, "hera.4th") == 0, "and its own capsule, which makes it Hera");
CHECK(told(10, "(SEAL)"), "that is the system it comes back to");
/* ---- she births the unit ---- */
born_count = 0;
{
const char *r = tell(10, "10 UNIT");
CHECK(ended == 1 && ended_how == V4_TEXT_COMPLETED && ended_from == 10, "Hera births four nodes and joins them: \"%s\"", r);
}
CHECK(born_count == 4 && refused == 0, "four nodes were asked for: %u", born_count);
CHECK(post_passed[11] == 1 && post_passed[12] == 1 && post_passed[13] == 1 && post_passed[14] == 1 && post_other == 0,
"each took in the nucleus and FORTH-79 through its port from Hera, and passed POST: %u %u %u %u, %u failed",
post_passed[11], post_passed[12], post_passed[13], post_passed[14], post_other);
CHECK(parity_count == 4 && parity_number[0] == 11 && parity_number[1] == 12 && parity_number[2] == 13 && parity_number[3] == 14, "the parity of each was recorded: %u", parity_count);
CHECK(parity_hash[0] == parity_hash[1] && parity_hash[1] == parity_hash[2] && parity_hash[2] == parity_hash[3],
"and their dictionary hashes are the same: %016llx %016llx %016llx %016llx",
(unsigned long long)parity_hash[0], (unsigned long long)parity_hash[1], (unsigned long long)parity_hash[2], (unsigned long long)parity_hash[3]);
printf(" the four outer nodes: dict_hash=0x%016llx\n", (unsigned long long)parity_hash[0]);
CHECK(parity_hash[0] != v4_image_dict_hash(node_numbered(10), &im), "Hera's is not: she has her own capsule");
CHECK(waiting(10) && waiting(11) && waiting(12) && waiting(13) && waiting(14), "all five are waiting at their ports");
{
v4_uheat_t c[5];
for (i = 0; i < 5; i++) c[i] = clock_of(10 + (v4_cell)i);
for (i = 0; i < 200; i++) (void)v4_fabric_step(&f);
for (i = 0; i < 5 && clock_of(10 + (v4_cell)i) == c[i]; i++) { }
CHECK(i == 5, "and execute nothing while they wait");
}
/* ---- they talk ---- */
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && printed_from == 10, "a line typed at the console is done by Hera");
for (i = 11; i <= 14; i++) {
char text[32], want[16];
snprintf(text, sizeof text, "%u 100 * .", i);
snprintf(want, sizeof want, "%u ", i * 100);
CHECK(strcmp(tell((v4_cell)i, text), want) == 0 && printed_from == (v4_cell)i && ended_from == (v4_cell)i, "text for node %u is done there and what it prints comes back: \"%s\"", i, printed);
}
CHECK(strcmp(tell(13, "3 4 U* . ."), "0 12 ") == 0, "an outer node has FORTH-79: U* is a word of the capsule");
CHECK(strcmp(tell(13, ": SQ DUP * ; 7 SQ ."), "49 ") == 0 && strstr(tell(12, "7 SQ ."), "UNKNOWN WORD") != NULL, "each has its own dictionary");
CHECK(strstr(tell(13, "COLD"), "Cold Start") != NULL && strcmp(tell(13, "3 4 U* . ."), "0 12 ") == 0 && strstr(tell(13, "7 SQ ."), "UNKNOWN WORD") != NULL,
"COLD on it comes back to the nucleus and FORTH-79");
{
v4_uheat_t hera = clock_of(10);
CHECK(strcmp(tell(11, ": T S\" 65 EMIT\" 12 SEND ; T"), "A") == 0 && printed_from == 12, "an outer node sends text to the one beside it: \"%s\"", printed);
hera = clock_of(10);
CHECK(strcmp(tell(11, ": X S\" 66 EMIT\" 14 SEND ; X"), "B") == 0 && printed_from == 14, "and to the one across from it, which it is not wired to: \"%s\"", printed);
CHECK(clock_of(10) > hera, "that went by a node in between");
}
CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(11, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(14, "(LOST) @ ."), "0 ") == 0, "nothing was let go");
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}