diff --git a/capsules/BLOCK_MAP.md b/capsules/BLOCK_MAP.md index d36fc6c4..4e491847 100644 --- a/capsules/BLOCK_MAP.md +++ b/capsules/BLOCK_MAP.md @@ -1,5 +1,5 @@ # Capsule Block Manifest — Auto-generated - + @@ -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 ` to refresh.* +*39 capsule(s) scanned. Re-run `mkcapsule --manifest ` to refresh.* diff --git a/capsules/v4/hera.4th b/capsules/v4/hera.4th new file mode 100644 index 00000000..adc3a8d8 --- /dev/null +++ b/capsules/v4/hera.4th @@ -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) ; diff --git a/capsules/v4/nucleus-64.f18 b/capsules/v4/nucleus-64.f18 index 84d109da..3034f2bf 100644 Binary files a/capsules/v4/nucleus-64.f18 and b/capsules/v4/nucleus-64.f18 differ diff --git a/docs/v4.0.0/MESH.md b/docs/v4.0.0/MESH.md index ec36bd20..f38ec5be 100644 --- a/docs/v4.0.0/MESH.md +++ b/docs/v4.0.0/MESH.md @@ -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.** diff --git a/logs/20261006-143934/amd64/qemu-amd64-20261006-143934.log b/logs/20261006-143934/amd64/qemu-amd64-20261006-143934.log new file mode 100644 index 00000000..0ef01733 --- /dev/null +++ b/logs/20261006-143934/amd64/qemu-amd64-20261006-143934.log @@ -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 diff --git a/logs/20261006-144204/aarch64/qemu-aarch64-20261006-144204.log b/logs/20261006-144204/aarch64/qemu-aarch64-20261006-144204.log new file mode 100644 index 00000000..9acbad0e --- /dev/null +++ b/logs/20261006-144204/aarch64/qemu-aarch64-20261006-144204.log @@ -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 diff --git a/logs/20261006-144601/riscv64/qemu-riscv64-20261006-144601.log b/logs/20261006-144601/riscv64/qemu-riscv64-20261006-144601.log new file mode 100644 index 00000000..14e7db9d --- /dev/null +++ b/logs/20261006-144601/riscv64/qemu-riscv64-20261006-144601.log @@ -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 diff --git a/v4/README.md b/v4/README.md index 23e16d5c..2e0a58e7 100644 --- a/v4/README.md +++ b/v4/README.md @@ -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 diff --git a/v4/capsule/core.v4 b/v4/capsule/core.v4 index 48ca494e..de1a18bd 100644 --- a/v4/capsule/core.v4 +++ b/v4/capsule/core.v4 @@ -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: diff --git a/v4/capsule/quit.v4 b/v4/capsule/quit.v4 index dc8510ee..43a31a38 100644 --- a/v4/capsule/quit.v4 +++ b/v4/capsule/quit.v4 @@ -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. diff --git a/v4/include/v4/image.h b/v4/include/v4/image.h index 7c145c8e..0038a663 100644 --- a/v4/include/v4/image.h +++ b/v4/include/v4/image.h @@ -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 diff --git a/v4/include/v4/manage.h b/v4/include/v4/manage.h new file mode 100644 index 00000000..93ff4110 --- /dev/null +++ b/v4/include/v4/manage.h @@ -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 */ diff --git a/v4/src/fabric.c b/v4/src/fabric.c index 8eb8423b..92f05329 100644 --- a/v4/src/fabric.c +++ b/v4/src/fabric.c @@ -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); diff --git a/v4/src/image.c b/v4/src/image.c index 9b079d42..c3f60414 100644 --- a/v4/src/image.c +++ b/v4/src/image.c @@ -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]); +} diff --git a/v4/src/manage.c b/v4/src/manage.c new file mode 100644 index 00000000..9b44639f --- /dev/null +++ b/v4/src/manage.c @@ -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; +} diff --git a/v4/system/boot.c b/v4/system/boot.c index fe8c9e24..e3b9fe88 100644 --- a/v4/system/boot.c +++ b/v4/system/boot.c @@ -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 diff --git a/v4/tests/host_map.h b/v4/tests/host_map.h index e695a344..7ee2e18c 100644 --- a/v4/tests/host_map.h +++ b/v4/tests/host_map.h @@ -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 */ diff --git a/v4/tests/test_host_unit.c b/v4/tests/test_host_unit.c new file mode 100644 index 00000000..6b2126bf --- /dev/null +++ b/v4/tests/test_host_unit.c @@ -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 +#include +#include + +#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; +}