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 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-05 19:38:40 -04:00
co-authored by Claude Opus 5.5
parent e8e8ea13ea
commit 25fc5fd5e3
16 changed files with 609 additions and 15 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated # Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-10-05T23:00:23Z --> <!-- Generated by mkcapsule --manifest 2026-10-05T23:35:59Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. --> <!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live --> <!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. --> <!-- in MANIFEST.md alongside this auto-generated index. -->
+16 -1
View File
@@ -289,9 +289,24 @@ its logs.
`v4/tests/test_port.c`; six builds agree; `BYE` and an unserved request `v4/tests/test_port.c`; six builds agree; `BYE` and an unserved request
typed at each bare-metal prompt. `logs/20261005-185506`, `-185734`, typed at each bare-metal prompt. `logs/20261005-185506`, `-185734`,
`-190101`. `-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 *` **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; 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 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`. countdown and recheck; the word's count; `stadium_word_dispatch`.
In-line words become calls. The per-call-target count goes. In-line words become calls. The per-call-target count goes.
@@ -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
@@ -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
@@ -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
+8 -1
View File
@@ -142,13 +142,20 @@ post79:
# `make hosted` builds the three. `make hosted-check` boots each with no # `make hosted` builds the three. `make hosted-check` boots each with no
# input and requires that all three print the same lines, ending in # 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 .PHONY: hosted hosted-check
hosted: $(foreach i,$(HOSTED_ISAS),$(BINDIR)/starforth4-$(i)) hosted: $(foreach i,$(HOSTED_ISAS),$(BINDIR)/starforth4-$(i))
hosted-check: $(foreach i,$(HOSTED_ISAS),$(BINDIR)/boot-$(i).txt) 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; } @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-aarch64.txt
@cmp $(BINDIR)/boot-amd64.txt $(BINDIR)/boot-riscv64.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" @echo "hosted-check: amd64, aarch64 and riscv64 boot identically"
.PHONY: all test sanitize clean $(addprefix test-,$(WIDTHS)) .PHONY: all test sanitize clean $(addprefix test-,$(WIDTHS))
+14 -7
View File
@@ -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: load `v4:forth79.4th`, pass POST and reach `ok>`, with the same lines:
``` ```
PARITY:V4_NUCLEUS words=296 image_hash=0x736921433a2e0e97 PARITY:V4_NUCLEUS words=298 image_hash=0x2e02147b54f9ff47
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=... dict_hash=0x01a3a48159f28a3e PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=... dict_hash=...
PARITY:V4_POST tests=550 pass=550 fail=0 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 PARITY:OK
POST: PASSED POST: PASSED
ok> ok>
``` ```
Bare-metal logs: `logs/20261005-185506/amd64/`, `logs/20261005-185734/aarch64/`, Bare-metal logs: `logs/20261005-193045/amd64/`, `logs/20261005-193307/aarch64/`,
`logs/20261005-190101/riscv64/`. On each, six lines were then typed at the `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 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 `U*`, `COLD`, the capsule word again, a `FORGET` of it (refused), a kernel
was answered as the hosted binary answers it. The 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). 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 **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 many users and tasks, preemptive and cooperative, needs of it
(`ENGINE.md` 3a). (`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 **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 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 place. `ENGINE.md` sets out the steps from here; step 1 is done and step 2 is
+24 -1
View File
@@ -38,6 +38,8 @@
\ DBASE DLIMIT byte addresses of the start and end of dictionary space \ 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 \ PAD byte address of the scratch area for strings, 84 bytes or more
\ (D) word address of five cells of scratch for this file \ (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. \ PAD is in line.
macro 4* 2* 2* endmacro macro 4* 2* 2* endmacro
@@ -123,6 +125,26 @@ header SMUDGE
header HIDDEN header HIDDEN
: HIDDEN ( -- ) LATEST if NONE (FLAGS) a! @ 2 OR ! ; NONE: drop ; : 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 ------------------------------------------------------- \ ---- making an entry -------------------------------------------------------
\ (D)+0 where the name starts (D)+1 the name being looked up \ (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 \ (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 P: DP b! @b 3 and if ALIGNED drop 0 C, jump P
ALIGNED: drop ALIGNED: drop
0 , (D) a! @ , LATEST , 0 , (D) a! @ , LATEST ,
HERE CURRENT a! @ a! ! ; HERE CURRENT a! @ a! !
jump (DEFINED)
FULL: drop ; \ (DP+) has set NODE-ERROR FULL: drop ; \ (DP+) has set NODE-ERROR
EMPTY: drop NODE-ERROR b! 6 !b ; \ name missing EMPTY: drop NODE-ERROR b! 6 !b ; \ name missing
+2
View File
@@ -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. \ its own) and the line ends with ERROR. The codes are listed in core.v4.
header NODE-ERROR inline : NODE-ERROR' NODE-ERROR ; header NODE-ERROR inline : NODE-ERROR' NODE-ERROR ;
header (CATCH) inline : (CATCH)' (CATCH) ; 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 (EMIT-HOOK) inline : (EMIT-HOOK)' (EMIT-HOOK) ;
header QUIT header QUIT
+4 -2
View File
@@ -102,8 +102,9 @@ header FORGET
PRUNED: drop PRUNED: drop
CONTEXT a! @ (Q) a! @ inv + 1 + -if C1 drop jump C2 CONTEXT a! @ (Q) a! @ inv + 1 + -if C1 drop jump C2
C1: drop (LATEST) CONTEXT a! ! C1: drop (LATEST) CONTEXT a! !
C2: CURRENT a! @ (Q) a! @ inv + 1 + -if C3 drop ; C2: CURRENT a! @ (Q) a! @ inv + 1 + -if C3 drop jump TOLD
C3: drop (LATEST) CURRENT a! ! ; C3: drop (LATEST) CURRENT a! !
TOLD: (Q) a! @ jump (FORGOTTEN) \ the kernel's records of them go too (dict.v4)
MISSING: drop (UNKNOWN) drop ; MISSING: drop (UNKNOWN) drop ;
\ ---- the system as a whole ------------------------------------------------------- \ ---- the system as a whole -------------------------------------------------------
@@ -142,6 +143,7 @@ header COLD
(BOOT) a! @ 2/ 2/ FENCE a! ! (BOOT) a! @ 2/ 2/ FENCE a! !
(LATEST) CONTEXT a! ! (LATEST) CURRENT a! ! 0 VOC-LINK 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 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 $54524F46 (EMIT4) $39372D48 (EMIT4) $6C6F4320 (EMIT4) $74532064 (EMIT4) $747261 (EMIT4) CR
$74737953 (EMIT4) $69206D65 (EMIT4) $6974696E (EMIT4) $7A696C61 (EMIT4) $2E6465 (EMIT4) CR $74737953 (EMIT4) $69206D65 (EMIT4) $6974696E (EMIT4) $7A696C61 (EMIT4) $2E6465 (EMIT4) CR
jump ABORT jump ABORT
+11
View File
@@ -57,11 +57,22 @@ typedef struct {
* LATEST the newest word of FORTH. What lies below HERE, and LATEST, * LATEST the newest word of FORTH. What lies below HERE, and LATEST,
* is what the dictionary hash covers (v4/include/v4/boot.h). */ * is what the dictionary hash covers (v4/include/v4/boot.h). */
v4_cell dp, latest; 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; } v4_image;
/* The image built from v4/capsule (the generated file defines it). */ /* The image built from v4/capsule (the generated file defines it). */
extern const v4_image v4_capsule_image; 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. */ /* How a line ended. */
#define V4_LINE_QUIT 0 /* QUIT: nothing is to be said */ #define V4_LINE_QUIT 0 /* QUIT: nothing is to be said */
#define V4_LINE_COMPLETED 1 /* the host says " ok" */ #define V4_LINE_COMPLETED 1 /* the host says " ok" */
+7
View File
@@ -69,3 +69,10 @@ int v4_line_status(const v4_node *n, const v4_image *im)
{ {
return (int)n->mem[im->line_status]; 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;
}
+4
View File
@@ -297,6 +297,10 @@ int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks)
} }
#endif #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 #if V4_POST_AT_BOOT
say(b, "PARITY:OK\nPOST: PASSED\n"); say(b, "PARITY:OK\nPOST: PASSED\n");
#else #else
+4
View File
@@ -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 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 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 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 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 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 */ #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, "(CATCH)", CATCH);
v4_text_constant(tx, "(LINE-STATUS)", LINE_STATUS); v4_text_constant(tx, "(LINE-STATUS)", LINE_STATUS);
v4_text_constant(tx, "(PORT)", PORT); 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-NUMBER", STORAGE_REG);
v4_text_constant(tx, "BLOCK-ADDRESS", STORAGE_REG + 1); v4_text_constant(tx, "BLOCK-ADDRESS", STORAGE_REG + 1);
v4_text_constant(tx, "BLOCK-COMMAND", STORAGE_REG + 2); v4_text_constant(tx, "BLOCK-COMMAND", STORAGE_REG + 2);
+106
View File
@@ -83,6 +83,72 @@ static char typed[8192]; /* typed and not yet taken */
static unsigned typed_len; static unsigned typed_len;
static int line_open; /* a line has been handed over and has not ended */ 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 /* 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 * read for 64 instruction words; or until `max` words. 1: ended, 2: waiting
* for a character, 0: neither. */ * 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; } if (v4_line_done(&n, &img)) { last_steps = steps; return 1; }
(void)v4_exec_step_word(&n, &es, &h); (void)v4_exec_step_word(&n, &es, &h);
steps++; 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; if (n.input_pos == n.input_len && n.p >= w_key && n.p < w_key_end) idle++; else idle = 0;
} }
last_steps = steps; last_steps = steps;
@@ -136,6 +203,11 @@ static void boot_with(unsigned depth)
v4_heat_reset(&h); v4_heat_reset(&h);
for (i = 0; i < depth; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i)); for (i = 0; i < depth; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i));
v4_dstack_push(&n.ds, CANARY); 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.mem[LINE_STATUS] = V4_LINE_COMPLETED;
n.p = img.idle; /* idle, waiting to be handed a line */ n.p = img.idle; /* idle, waiting to be handed a line */
typed_len = 0; typed_len = 0;
@@ -1354,6 +1426,40 @@ int main(void)
CHECK(v4_node_guards_intact(&n), "guards intact"); 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); printf(" %d checks, %d failures\n", checks, failures);
return failures ? 1 : 0; return failures ? 1 : 0;
} }
+3 -2
View File
@@ -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); (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, 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); (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, 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)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]); 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]); 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; return 0;