From 348eed7fa15b484b7262e5f64a7b8be788581c2a Mon Sep 17 00:00:00 2001 From: rajames Date: Tue, 6 Oct 2026 14:48:23 -0400 Subject: [PATCH] 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 --- capsules/BLOCK_MAP.md | 10 +- capsules/v4/hera.4th | 80 ++++ capsules/v4/nucleus-64.f18 | Bin 52328 -> 53032 bytes docs/v4.0.0/MESH.md | 66 ++- .../amd64/qemu-amd64-20261006-143934.log | 130 ++++++ .../aarch64/qemu-aarch64-20261006-144204.log | 104 +++++ .../riscv64/qemu-riscv64-20261006-144601.log | 174 ++++++++ v4/README.md | 9 + v4/capsule/core.v4 | 16 +- v4/capsule/quit.v4 | 63 ++- v4/include/v4/image.h | 6 + v4/include/v4/manage.h | 81 ++++ v4/src/fabric.c | 7 +- v4/src/image.c | 27 ++ v4/src/manage.c | 107 +++++ v4/system/boot.c | 22 +- v4/tests/host_map.h | 2 + v4/tests/test_host_unit.c | 386 ++++++++++++++++++ 18 files changed, 1250 insertions(+), 40 deletions(-) create mode 100644 capsules/v4/hera.4th create mode 100644 logs/20261006-143934/amd64/qemu-amd64-20261006-143934.log create mode 100644 logs/20261006-144204/aarch64/qemu-aarch64-20261006-144204.log create mode 100644 logs/20261006-144601/riscv64/qemu-riscv64-20261006-144601.log create mode 100644 v4/include/v4/manage.h create mode 100644 v4/src/manage.c create mode 100644 v4/tests/test_host_unit.c 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 84d109da9742ca6a15256dba206a9cf052e90261..3034f2bffca3dea105438f680369403ff63035f3 100644 GIT binary patch literal 53032 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 : 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; +}