From 25fc5fd5e3201b7c4d3bf9f1b9508e1932a199a0 Mon Sep 17 00:00:00 2001 From: rajames Date: Mon, 5 Oct 2026 19:38:40 -0400 Subject: [PATCH] feat(v4.0.0): the node tells its kernel of its words; the boot seals the system ENGINE.md 3b, the node's side of ruling A (a word's code is the node's, its accounts the kernel's). - dict.v4, system.v4: (WORD-DEFINED) ( xt -- ) is run when an entry is made, (WORD-FORGOTTEN) ( w -- ) when FORGET or COLD removes entries; with 0 there no one is told, as on the hosted product - test_host_quit.c: a kernel that keeps the list of words and is checked to hold exactly the node's dictionary after definitions, a vocabulary, an abandoned definition, FORGET, a refused FORGET and COLD; KERNEL-WORD called from the prompt and from a definition Fixed, found while writing that test: since the capsules moved from build time to boot time (294e6946), what COLD returns to and FORGET protects was still the nucleus alone, so COLD lost U*, U/MOD and BYE and FORGET U* was allowed. The boot now seals the system when it has loaded it (v4_image_seal), and hosted-check checks COLD, the capsule word after it, the refused FORGET and BYE. Verified: make -C v4 test passes at both widths (1283 checks in test_host_quit.c); hosted-check passes on three ISAs; clean qemu with STARFORTH_V4=1 on amd64, aarch64 and riscv64 passes POST, and COLD, U* after it, FORGET U* (refused), an unserved kernel word and BYE typed at each prompt are answered correctly (logs/20261005-193045, -193307, -193636). Co-Authored-By: Claude Opus 5.5 --- capsules/BLOCK_MAP.md | 2 +- docs/v4.0.0/ENGINE.md | 17 +- .../amd64/qemu-amd64-20261005-193045.log | 129 +++++++++++++ .../aarch64/qemu-aarch64-20261005-193307.log | 103 +++++++++++ .../riscv64/qemu-riscv64-20261005-193636.log | 173 ++++++++++++++++++ v4/Makefile | 9 +- v4/README.md | 21 ++- v4/capsule/dict.v4 | 25 ++- v4/capsule/quit.v4 | 2 + v4/capsule/system.v4 | 6 +- v4/include/v4/image.h | 11 ++ v4/src/image.c | 7 + v4/system/boot.c | 4 + v4/tests/host_map.h | 4 + v4/tests/test_host_quit.c | 106 +++++++++++ v4/tools/mkimage.c | 5 +- 16 files changed, 609 insertions(+), 15 deletions(-) create mode 100644 logs/20261005-193045/amd64/qemu-amd64-20261005-193045.log create mode 100644 logs/20261005-193307/aarch64/qemu-aarch64-20261005-193307.log create mode 100644 logs/20261005-193636/riscv64/qemu-riscv64-20261005-193636.log diff --git a/capsules/BLOCK_MAP.md b/capsules/BLOCK_MAP.md index 1992f014..eb03f270 100644 --- a/capsules/BLOCK_MAP.md +++ b/capsules/BLOCK_MAP.md @@ -1,5 +1,5 @@ # Capsule Block Manifest — Auto-generated - + diff --git a/docs/v4.0.0/ENGINE.md b/docs/v4.0.0/ENGINE.md index ed3af66a..b65d7a62 100644 --- a/docs/v4.0.0/ENGINE.md +++ b/docs/v4.0.0/ENGINE.md @@ -289,9 +289,24 @@ its logs. `v4/tests/test_port.c`; six builds agree; `BYE` and an unserved request typed at each bare-metal prompt. `logs/20261005-185506`, `-185734`, `-190101`. + **The node tells its kernel of its words, 2026-10-05** (3b). An entry + is made in one place in the nucleus and entries go in two; each now + tells through a variable holding the xt of a word to run: + `(WORD-DEFINED)` ( xt -- ) from `(HEADER)`, `(WORD-FORGOTTEN)` ( w -- ) + from `FORGET` and `COLD` (`v4/capsule/dict.v4`, `system.v4`). With 0 + there, as on the hosted product, no one is told. `test_host_quit.c` is a + kernel that keeps the list and checks it is exactly the node's + dictionary after definitions, a vocabulary, an abandoned definition, + `FORGET`, a refused `FORGET` and `COLD`. + **Found and fixed with it:** since the capsules moved from build time to + boot time, what `COLD` returns to and `FORGET` protects was still the + nucleus alone, so `COLD` lost `U*`, `U/MOD` and `BYE`. The boot now + seals the system when it has loaded it (`v4_image_seal`); `hosted-check` + checks it. `logs/20261005-193045`, `-193307`, `-193636`. **Not done:** v3's own C functions serving a node. They take a `VM *` and use `vm_push`, `vm_pop` and, in places, the stack's fields directly; - a node has to stand behind that `VM` record first. + a node has to stand behind that `VM` record first. That, the records + themselves, and the kernel's interpreter file are step 4. 3. **A word is being executed (3.5).** The hook at `call`; the ACL's countdown and recheck; the word's count; `stadium_word_dispatch`. In-line words become calls. The per-call-target count goes. diff --git a/logs/20261005-193045/amd64/qemu-amd64-20261005-193045.log b/logs/20261005-193045/amd64/qemu-amd64-20261005-193045.log new file mode 100644 index 00000000..b503d12a --- /dev/null +++ b/logs/20261005-193045/amd64/qemu-amd64-20261005-193045.log @@ -0,0 +1,129 @@ +[=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 5 2026 19:30:20 + +UEFI BootServices: EXITED + +=== StarKernel Boot Information === +Memory map entries: 131 +Total memory: 1023 MB +Usable memory: 968 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 249244 + Free pages : 247721 + 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=1005926145, tick_hz=100, initial_count=10059261 +APIC Timer: configured (masked, ready to start) +Heartbeat: init done +Kernel initialization complete. +Boot successful! + +StarForth v4: one host node, the F18-derived engine +PARITY:V4_NUCLEUS words=298 image_hash=0x2e02147b54f9ff47 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x36b3a02224052315 +V4: capsule v4:post79.4th signature: missing (unsigned) +PARITY:V4_POST tests=550 pass=550 fail=0 +PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x46d5c264cb4dcfca +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/20261005-193307/aarch64/qemu-aarch64-20261005-193307.log b/logs/20261005-193307/aarch64/qemu-aarch64-20261005-193307.log new file mode 100644 index 00000000..952fa9f5 --- /dev/null +++ b/logs/20261005-193307/aarch64/qemu-aarch64-20261005-193307.log @@ -0,0 +1,103 @@ +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 5 2026 19:32:43 + +UEFI BootServices: EXITED + +=== StarKernel Boot Information === +Memory map entries: 108 +Total memory: 4093 MB +Usable memory: 4056 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 1039859 + Free pages : 1038448 + 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 +PARITY:V4_NUCLEUS words=298 image_hash=0x2e02147b54f9ff47 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x36b3a02224052315 +V4: capsule v4:post79.4th signature: missing (unsigned) +PARITY:V4_POST tests=550 pass=550 fail=0 +PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x46d5c264cb4dcfca +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/20261005-193636/riscv64/qemu-riscv64-20261005-193636.log b/logs/20261005-193636/riscv64/qemu-riscv64-20261005-193636.log new file mode 100644 index 00000000..01eee49b --- /dev/null +++ b/logs/20261005-193636/riscv64/qemu-riscv64-20261005-193636.log @@ -0,0 +1,173 @@ + +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 = 0x00000000bde958f8 +riscv64: satp cleared -- Bare mode, explicit (item 4.3.5a) + + + _____ _ _ __ _ + / ____| | | |/ / | | + | (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| | + \___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ | + ____) | || (_| | | | . \ __/ | | | | | __/ | + |_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_| + +LithosAnanke v2.1.0 +Architecture: riscv64 +Build: Oct 5 2026 19:36:00 + +UEFI BootServices: EXITED + +=== StarKernel Boot Information === +Memory map entries: 100 +Total memory: 1020 MB +Usable memory: 977 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 250773 + Free pages : 250121 + 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 +PARITY:V4_NUCLEUS words=298 image_hash=0x2e02147b54f9ff47 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x36b3a02224052315 +V4: capsule v4:post79.4th signature: missing (unsigned) +PARITY:V4_POST tests=550 pass=550 fail=0 +PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x46d5c264cb4dcfca +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/Makefile b/v4/Makefile index 51c26060..255c101b 100644 --- a/v4/Makefile +++ b/v4/Makefile @@ -142,13 +142,20 @@ post79: # `make hosted` builds the three. `make hosted-check` boots each with no # input and requires that all three print the same lines, ending in -# PARITY:OK, POST: PASSED and the prompt: the same hashes on every ISA. +# PARITY:OK, POST: PASSED and the prompt: the same hashes on every ISA. It +# also checks that what the boot loaded is part of the system: after COLD +# the capsule's words and the kernel's are still there, and FORGET refuses +# them. .PHONY: hosted hosted-check hosted: $(foreach i,$(HOSTED_ISAS),$(BINDIR)/starforth4-$(i)) hosted-check: $(foreach i,$(HOSTED_ISAS),$(BINDIR)/boot-$(i).txt) @grep -q '^POST: PASSED$$' $(BINDIR)/boot-amd64.txt || { echo "hosted-check: no POST: PASSED"; exit 1; } @cmp $(BINDIR)/boot-amd64.txt $(BINDIR)/boot-aarch64.txt @cmp $(BINDIR)/boot-amd64.txt $(BINDIR)/boot-riscv64.txt + @printf 'COLD\n3 4 U* . .\nFORGET U*\nBYE\n' | $(BINDIR)/starforth4-amd64 > $(BINDIR)/cold-amd64.txt + @grep -q '^ok> 0 12 ok$$' $(BINDIR)/cold-amd64.txt || { echo "hosted-check: COLD lost the capsule's words"; cat $(BINDIR)/cold-amd64.txt; exit 1; } + @grep -q '^ok> Protected word$$' $(BINDIR)/cold-amd64.txt || { echo "hosted-check: a capsule word could be forgotten"; exit 1; } + @grep -q '^ok> Goodbye!$$' $(BINDIR)/cold-amd64.txt || { echo "hosted-check: COLD lost BYE"; exit 1; } @echo "hosted-check: amd64, aarch64 and riscv64 boot identically" .PHONY: all test sanitize clean $(addprefix test-,$(WIDTHS)) diff --git a/v4/README.md b/v4/README.md index d449320e..ab28be40 100644 --- a/v4/README.md +++ b/v4/README.md @@ -59,20 +59,21 @@ State, 2026-10-05, after `ENGINE.md` step 1: all six builds start the nucleus, load `v4:forth79.4th`, pass POST and reach `ok>`, with the same lines: ``` -PARITY:V4_NUCLEUS words=296 image_hash=0x736921433a2e0e97 -PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=... dict_hash=0x01a3a48159f28a3e +PARITY:V4_NUCLEUS words=298 image_hash=0x2e02147b54f9ff47 +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=... dict_hash=... PARITY:V4_POST tests=550 pass=550 fail=0 -PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=... dict_hash=0x212b7b70848080e7 +PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=... dict_hash=... PARITY:OK POST: PASSED ok> ``` -Bare-metal logs: `logs/20261005-185506/amd64/`, `logs/20261005-185734/aarch64/`, -`logs/20261005-190101/riscv64/`. On each, six lines were then typed at the +Bare-metal logs: `logs/20261005-193045/amd64/`, `logs/20261005-193307/aarch64/`, +`logs/20261005-193636/riscv64/`. On each, seven lines were then typed at the prompt through the serial port — a definition, its use with the capsule's -`U*`, an unknown word, a kernel word no one serves, and `BYE` — and each -was answered as the hosted binary answers it. The +`U*`, `COLD`, the capsule word again, a `FORGET` of it (refused), a kernel +word no one serves, and `BYE` — and each was answered as the hosted binary +answers it. The capsules were unsigned (no signing key on this machine). **A node is handed a line** (`docs/v4.0.0/ENGINE.md` 3.1). It does not read @@ -94,6 +95,12 @@ words and is blocked while it waits at its port, which is what a system of many users and tasks, preemptive and cooperative, needs of it (`ENGINE.md` 3a). +**The two products have parted** (`ENGINE.md` 3c). They share the engine, +the FORTH-79 capsule and its POST; their nuclei may differ. The hosted +product is its own and may become a hosted SDK. The bare-metal product is +to be LithosAnanke with the node in the StarForth VM's place, and +everything about word records, the fleet and identity is its alone. + **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/dict.v4 b/v4/capsule/dict.v4 index 6b524cfb..7f35bbf8 100644 --- a/v4/capsule/dict.v4 +++ b/v4/capsule/dict.v4 @@ -38,6 +38,8 @@ \ DBASE DLIMIT byte addresses of the start and end of dictionary space \ PAD byte address of the scratch area for strings, 84 bytes or more \ (D) word address of five cells of scratch for this file +\ (WORD-DEFINED) (WORD-FORGOTTEN) word addresses of two variables: see +\ "telling the kernel" below \ PAD is in line. macro 4* 2* 2* endmacro @@ -123,6 +125,26 @@ header SMUDGE header HIDDEN : HIDDEN ( -- ) LATEST if NONE (FLAGS) a! @ 2 OR ! ; NONE: drop ; +\ ---- telling the kernel (docs/v4.0.0/ENGINE.md 3b) ------------------------------ +\ A word's code is here on the node; its accounts are a record its kernel +\ keeps. So the kernel has to be told of every entry that is made and of +\ every entry that goes. An entry is made in one place, (HEADER); entries go +\ in two, FORGET and COLD (system.v4). Each tells through a variable that +\ holds the xt of a word to run, or 0 for a node whose host keeps no records: +\ (WORD-DEFINED) ( xt -- ) an entry has been made; its code will be at xt +\ (WORD-FORGOTTEN) ( w -- ) every entry at word address w or above is gone +\ On the bare-metal system the kernel puts words of its own there. + +\ ( -- ) tell of the entry just made, whose code starts at HERE +: (DEFINED) + (WORD-DEFINED) b! @b if NONE HERE SWAP push ; + NONE: drop ; + +\ ( w -- ) tell that the entries from word address w up have gone +: (FORGOTTEN) + (WORD-FORGOTTEN) b! @b if NONE push ; + NONE: drop drop ; + \ ---- making an entry ------------------------------------------------------- \ (D)+0 where the name starts (D)+1 the name being looked up \ (D)+2 the name being copied, or the entry being looked at @@ -154,7 +176,8 @@ header HIDDEN P: DP b! @b 3 and if ALIGNED drop 0 C, jump P ALIGNED: drop 0 , (D) a! @ , LATEST , - HERE CURRENT a! @ a! ! ; + HERE CURRENT a! @ a! ! + jump (DEFINED) FULL: drop ; \ (DP+) has set NODE-ERROR EMPTY: drop NODE-ERROR b! 6 !b ; \ name missing diff --git a/v4/capsule/quit.v4 b/v4/capsule/quit.v4 index e2ea7c73..fa89762d 100644 --- a/v4/capsule/quit.v4 +++ b/v4/capsule/quit.v4 @@ -89,6 +89,8 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro \ its own) and the line ends with ERROR. The codes are listed in core.v4. header NODE-ERROR inline : NODE-ERROR' NODE-ERROR ; header (CATCH) inline : (CATCH)' (CATCH) ; +header (WORD-DEFINED) inline : (WORD-DEFINED)' (WORD-DEFINED) ; +header (WORD-FORGOTTEN) inline : (WORD-FORGOTTEN)' (WORD-FORGOTTEN) ; header (EMIT-HOOK) inline : (EMIT-HOOK)' (EMIT-HOOK) ; header QUIT diff --git a/v4/capsule/system.v4 b/v4/capsule/system.v4 index 38165340..aa48a364 100644 --- a/v4/capsule/system.v4 +++ b/v4/capsule/system.v4 @@ -102,8 +102,9 @@ header FORGET PRUNED: drop CONTEXT a! @ (Q) a! @ inv + 1 + -if C1 drop jump C2 C1: drop (LATEST) CONTEXT a! ! - C2: CURRENT a! @ (Q) a! @ inv + 1 + -if C3 drop ; - C3: drop (LATEST) CURRENT a! ! ; + C2: CURRENT a! @ (Q) a! @ inv + 1 + -if C3 drop jump TOLD + C3: drop (LATEST) CURRENT a! ! + TOLD: (Q) a! @ jump (FORGOTTEN) \ the kernel's records of them go too (dict.v4) MISSING: drop (UNKNOWN) drop ; \ ---- the system as a whole ------------------------------------------------------- @@ -142,6 +143,7 @@ header COLD (BOOT) a! @ 2/ 2/ FENCE a! ! (LATEST) CONTEXT a! ! (LATEST) CURRENT a! ! 0 VOC-LINK a! ! 10 BASE a! ! 0 SCR a! ! 2 (LOG-LEVEL) a! ! 0 (ACL-HOOK) a! ! EMPTY-BUFFERS + (BOOT) a! @ 2/ 2/ (FORGOTTEN) \ the kernel's records of what has gone go too (dict.v4) $54524F46 (EMIT4) $39372D48 (EMIT4) $6C6F4320 (EMIT4) $74532064 (EMIT4) $747261 (EMIT4) CR $74737953 (EMIT4) $69206D65 (EMIT4) $6974696E (EMIT4) $7A696C61 (EMIT4) $2E6465 (EMIT4) CR jump ABORT diff --git a/v4/include/v4/image.h b/v4/include/v4/image.h index f017ac97..95ecfac2 100644 --- a/v4/include/v4/image.h +++ b/v4/include/v4/image.h @@ -57,11 +57,22 @@ typedef struct { * LATEST the newest word of FORTH. What lies below HERE, and LATEST, * is what the dictionary hash covers (v4/include/v4/boot.h). */ v4_cell dp, latest; + + /* What COLD returns the system to and FORGET will not go below: two + * cells, DP and LATEST as they were when the system was complete, and + * the variable FENCE. The image has them as the nucleus alone; a system + * that loads capsules when it boots sets them again when it has + * (v4_image_seal). */ + v4_cell boot_cells, fence; } v4_image; /* The image built from v4/capsule (the generated file defines it). */ extern const v4_image v4_capsule_image; +/* The system is complete: what is in the dictionary now is what COLD comes + * back to, and FORGET will not go below it. */ +void v4_image_seal(v4_node *n, const v4_image *im); + /* How a line ended. */ #define V4_LINE_QUIT 0 /* QUIT: nothing is to be said */ #define V4_LINE_COMPLETED 1 /* the host says " ok" */ diff --git a/v4/src/image.c b/v4/src/image.c index 7517eff6..a9cd3b85 100644 --- a/v4/src/image.c +++ b/v4/src/image.c @@ -69,3 +69,10 @@ int v4_line_status(const v4_node *n, const v4_image *im) { return (int)n->mem[im->line_status]; } + +void v4_image_seal(v4_node *n, const v4_image *im) +{ + n->mem[im->boot_cells] = n->mem[im->dp]; + n->mem[im->boot_cells + 1] = n->mem[im->latest]; + n->mem[im->fence] = (n->mem[im->dp] + 3) / 4; +} diff --git a/v4/system/boot.c b/v4/system/boot.c index d9ee20be..d75f2cb5 100644 --- a/v4/system/boot.c +++ b/v4/system/boot.c @@ -297,6 +297,10 @@ int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks) } #endif + /* The kernel's words and the capsules are the system: COLD comes back + * to here, and FORGET will not go below it. */ + v4_image_seal(b->n, b->im); + #if V4_POST_AT_BOOT say(b, "PARITY:OK\nPOST: PASSED\n"); #else diff --git a/v4/tests/host_map.h b/v4/tests/host_map.h index 37470be3..3f2f33b8 100644 --- a/v4/tests/host_map.h +++ b/v4/tests/host_map.h @@ -69,6 +69,8 @@ #define CATCH (BUF0_W - 8) /* non-zero: a line's error is recorded here, -1, and the line ends " ok" */ #define LINE_STATUS (BUF0_W - 9) /* how the last line ended: 0 QUIT, 1 completed, 2 an error */ #define PORT (BUF0_W - 10) /* the node's port: a write to it blocks until its neighbour, the kernel, has served it */ +#define WORD_DEFINED (BUF0_W - 11) /* the xt of the word that is told of each new entry, or 0 */ +#define WORD_FORGOTTEN (BUF0_W - 12) /* the xt of the word that is told when entries go, or 0 */ #define LOG_LEVEL (BUF0_W - 5) /* the level at or below which a message is printed */ #define BOOT_CELLS (BVARS + 14) /* (BOOT): DP and LATEST as the loader left them, 2 cells */ #define BUF0_W (BVARS - 2 * 256) /* the two block buffers, 256 cells each */ @@ -146,6 +148,8 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned v4_text_constant(tx, "(CATCH)", CATCH); v4_text_constant(tx, "(LINE-STATUS)", LINE_STATUS); v4_text_constant(tx, "(PORT)", PORT); + v4_text_constant(tx, "(WORD-DEFINED)", WORD_DEFINED); + v4_text_constant(tx, "(WORD-FORGOTTEN)", WORD_FORGOTTEN); v4_text_constant(tx, "BLOCK-NUMBER", STORAGE_REG); v4_text_constant(tx, "BLOCK-ADDRESS", STORAGE_REG + 1); v4_text_constant(tx, "BLOCK-COMMAND", STORAGE_REG + 2); diff --git a/v4/tests/test_host_quit.c b/v4/tests/test_host_quit.c index 1441a420..6698386d 100644 --- a/v4/tests/test_host_quit.c +++ b/v4/tests/test_host_quit.c @@ -83,6 +83,72 @@ static char typed[8192]; /* typed and not yet taken */ static unsigned typed_len; static int line_open; /* a line has been handed over and has not ended */ +/* THE KERNEL. A node asks its kernel by writing to its port (node.h), and + * these tests are that too. Request 1 is "an entry has been made" ( xt -- ) + * and request 2 "the entries from here up have gone" ( w -- ): the kernel's + * record of the words on the node (ENGINE.md 3b), kept here as the list of + * their xts. Any other request is noted and nothing is done. */ +static v4_cell known[1024]; +static unsigned known_n; +static v4_cell last_request; + +static void kernel_serve(void) +{ + last_request = n.request; + if (n.request == 1) { + v4_cell xt = v4_dstack_pop(&n.ds); + if (known_n < 1024) known[known_n++] = xt; + } else if (n.request == 2) { + v4_cell w = v4_dstack_pop(&n.ds); + unsigned i, kept = 0; + for (i = 0; i < known_n; i++) if (known[i] < w) known[kept++] = known[i]; + known_n = kept; + } +} + +/* Every entry on the node above the capsule's own, in every vocabulary: + * FORTH's list from LATEST, and each vocabulary's from its head. */ +static unsigned node_words(v4_cell *out, unsigned max) +{ + unsigned count = 0; + v4_cell xt, voc; + for (xt = n.mem[LATEST]; xt >= DICT_W; xt = n.mem[xt - 1]) if (count < max) out[count++] = xt; + for (voc = n.mem[VOC_LINK]; voc != 0; voc = n.mem[voc + 1]) + for (xt = n.mem[voc]; xt >= DICT_W; xt = n.mem[xt - 1]) if (count < max) out[count++] = xt; + return count; +} + +/* True if the kernel's record is exactly the words that are on the node. */ +static int kernel_agrees(void) +{ + static v4_cell there[1024]; + unsigned count = node_words(there, 1024), i, k; + if (count != known_n) return 0; + for (i = 0; i < count; i++) { + for (k = 0; k < known_n && known[k] != there[i]; k++) { } + if (k == known_n) return 0; + } + return 1; +} + +/* The name of the entry whose code is at xt, as the kernel reads it: the + * word before the link holds the word address of the counted name, its + * bytes four to a cell. */ +static const char *node_name(v4_cell xt) +{ + static char name[40]; + v4_cell at = n.mem[xt - 2] * 4; + unsigned len, i; + len = (unsigned)(((v4_ucell)n.mem[at / 4] >> (8 * (at % 4))) & 0xFFu); + if (len > 31) len = 31; + for (i = 0; i < len; i++) { + v4_cell b = at + 1 + (v4_cell)i; + name[i] = (char)(((v4_ucell)n.mem[b / 4] >> (8 * (b % 4))) & 0xFFu); + } + name[len] = 0; + return name; +} + /* Run until the line ends, or until it has been inside KEY with nothing to * read for 64 instruction words; or until `max` words. 1: ended, 2: waiting * for a character, 0: neither. */ @@ -94,6 +160,7 @@ static int run_line(long max) if (v4_line_done(&n, &img)) { last_steps = steps; return 1; } (void)v4_exec_step_word(&n, &es, &h); steps++; + if (n.asking) { kernel_serve(); v4_node_port_served(&n); } /* blocked at its port: the kernel's turn */ if (n.input_pos == n.input_len && n.p >= w_key && n.p < w_key_end) idle++; else idle = 0; } last_steps = steps; @@ -136,6 +203,11 @@ static void boot_with(unsigned depth) v4_heat_reset(&h); for (i = 0; i < depth; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i)); v4_dstack_push(&n.ds, CANARY); + v4_node_port_attach(&n, PORT); + n.mem[WORD_DEFINED] = 0; /* no one is told of entries, until a test says so */ + n.mem[WORD_FORGOTTEN] = 0; + known_n = 0; + last_request = 0; n.mem[LINE_STATUS] = V4_LINE_COMPLETED; n.p = img.idle; /* idle, waiting to be handed a line */ typed_len = 0; @@ -1354,6 +1426,40 @@ int main(void) CHECK(v4_node_guards_intact(&n), "guards intact"); + /* ---- the kernel is told of every entry made and of every entry that goes (ENGINE.md 3b) ---- */ + boot(); + CHECK(is(say("5 KERNEL-WORD ASK5\n"), " ok\nok> "), "KERNEL-WORD makes a word"); + CHECK(is(say("ASK5\n"), " ok\nok> ") && last_request == 5, "whose body writes its number to the port: %ld", (long)last_request); + CHECK(is(say(": TWICE ASK5 ASK5 ; TWICE\n"), " ok\nok> "), "and which a definition can call, again and again"); + boot(); + CHECK(is(say(": Q1 ;\n"), " ok\nok> ") && known_n == 0, "with nothing in (WORD-DEFINED) no one is told of a new entry"); + CHECK(is(say("1 KERNEL-WORD KD 2 KERNEL-WORD KF\n"), " ok\nok> "), "the kernel's two words"); + CHECK(is(say("' KD (WORD-DEFINED) ! ' KF (WORD-FORGOTTEN) !\n"), " ok\nok> "), "are put where the node will run them"); + known_n = node_words(known, 1024); /* what was there before the kernel was listening */ + n.mem[BOOT_CELLS] = n.mem[DP]; /* and all of it is the system, which COLD keeps */ + n.mem[BOOT_CELLS + 1] = n.mem[LATEST]; + n.mem[FENCE] = (n.mem[DP] + 3) / 4; + { + unsigned before = known_n; + CHECK(kernel_agrees() && before == 3, "the kernel starts knowing the words that are there: %u", before); + CHECK(is(say(": A1 ;\n"), " ok\nok> ") && known_n == before + 1 && kernel_agrees(), "a colon definition is told of"); + CHECK(strcmp(node_name(known[known_n - 1]), "A1") == 0, "by its xt, from which its name is read: %s", node_name(known[known_n - 1])); + CHECK(is(say("VARIABLE V1 5 CONSTANT C1 CREATE D1\n"), " ok\nok> ") && known_n == before + 4 && kernel_agrees(), + "and a variable, a constant and a CREATEd word"); + CHECK(is(say("VOCABULARY VOC1 VOC1 DEFINITIONS : IN1 ; FORTH DEFINITIONS : A2 ;\n"), " ok\nok> ") + && known_n == before + 7 && kernel_agrees(), "a vocabulary, a word in it, and a word after it"); + CHECK(strstr(say(": BAD NOSUCH ;\n"), "ERROR") != NULL && known_n == before + 8 && kernel_agrees(), + "a definition that is abandoned is still an entry, and is told of"); + CHECK(is(say("FORGET C1\n"), " ok\nok> ") && known_n == before + 2 && kernel_agrees(), + "FORGET tells that every entry from that one up has gone: %u left of ours", known_n - before); + CHECK(is(say(": A3 ;\n"), " ok\nok> ") && known_n == before + 3 && kernel_agrees(), "and what is defined next is told of as before"); + CHECK(strstr(say("FORGET KD\n"), "Protected word") != NULL && known_n == before + 3 && kernel_agrees(), + "a FORGET that is refused tells nothing"); + (void)say("COLD\n"); + CHECK(known_n == before && kernel_agrees(), "COLD tells that everything above the system has gone: %u", known_n); + CHECK(is(say(": A4 ; A4\n"), " ok\nok> ") && known_n == before + 1 && kernel_agrees(), "and the node goes on telling"); + } + printf(" %d checks, %d failures\n", checks, failures); return failures ? 1 : 0; } diff --git a/v4/tools/mkimage.c b/v4/tools/mkimage.c index 983817af..934a6a3d 100644 --- a/v4/tools/mkimage.c +++ b/v4/tools/mkimage.c @@ -102,8 +102,9 @@ int main(int argc, char **argv) (unsigned)V4_CELL_BITS, (unsigned)V4_NODE_WORDS, (unsigned)V4_DATA_RING, (unsigned)V4_RET_RING); fprintf(out, " %ld, %ld, %ld,\n %ld, %uu, %ld,\n %ld, %ld,\n %ld, %ld,\n", (long)line, (long)idle, (long)LINE_STATUS, (long)TIB, 1025u, (long)SPAN, (long)faults, (long)PORT, (long)w_key, (long)w_key_end); - fprintf(out, " %ld, %ld, %ld,\n %ld, %ld,\n %ld,\n %ld,\n %ld, %ld\n};\n", (long)CONSOLE_TX, (long)CONSOLE_RX, (long)CONSOLE_ST, - (long)DSTACK_REG, (long)RSTACK_REG, (long)NODE_ERROR, (long)STORAGE_REG, (long)DP, (long)LATEST); + fprintf(out, " %ld, %ld, %ld,\n %ld, %ld,\n %ld,\n %ld,\n %ld, %ld,\n %ld, %ld\n};\n", (long)CONSOLE_TX, (long)CONSOLE_RX, (long)CONSOLE_ST, + (long)DSTACK_REG, (long)RSTACK_REG, (long)NODE_ERROR, (long)STORAGE_REG, (long)DP, (long)LATEST, + (long)BOOT_CELLS, (long)FENCE); if (fclose(out) != 0) die("cannot write", argv[1]); fprintf(stderr, "mkimage: %u words of memory, dictionary to word %ld, %d-bit cells -> %s\n", count, (long)((n.mem[DP] + 3) / 4), V4_CELL_BITS, argv[1]); return 0;