feat(v4.0.0): the nucleus is a capsule, sent to a node born empty

MESH.md step 2.  A capsule of F18 code is the words a neighbour writes to a
node's port: for each stretch of memory, "@p a! @p push", the address and
count, "@p !+ unext" and the words; then a jump to the start.  A node born
empty executes that from its port, so it needs nothing in it beforehand.

- capsule.h/.c: v4_capsule_write, any node's memory as such a capsule
- mkimage writes the nucleus so, to capsules/v4/nucleus-64.f18, and the
  addresses a host needs as a C file; the memory image is no longer linked
  into either product
- mkcapsule is unchanged: the nucleus capsule is a built file kept under
  capsules/, as BLOCK_MAP.md is, and is baked, hashed and signed with the
  rest
- boot: the node is born empty (v4_image_born); the nucleus capsule is
  found, its hash and signature checked, and given to the node a word at a
  time as it reads its port; PARITY:V4_NUCLEUS carries its name and hash

Verified: test_fabric.c (59 checks, both widths, and under ASan and UBSan):
a memory with a programme and scattered words arrives word for word in an
empty node and runs.  The nucleus capsule rebuilds byte for byte.
hosted-check passes on three ISAs; clean qemu with STARFORTH_V4=1 on amd64,
aarch64 and riscv64 takes the nucleus in, passes POST (550 of 550) and
answers lines typed at each prompt (logs/20261006-102421, -102706,
-103048).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-06 10:33:15 -04:00
co-authored by Claude Opus 5.5
parent 9af442f793
commit 42610e844f
17 changed files with 718 additions and 88 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-06T11:54:53Z --> <!-- Generated by mkcapsule --manifest 2026-10-06T14:30:09Z -->
<!-- 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. -->
Binary file not shown.
+29 -8
View File
@@ -149,14 +149,15 @@ to 8 and are made of capsule code and of the host that owns the devices.
## 5. A capsule of F18 code, and how an empty node takes it in ## 5. A capsule of F18 code, and how an empty node takes it in
**Proposal.** A capsule of F18 code is words of memory and where they go: **As built.** A capsule of F18 code is not a format a node has to
runs of `(address, count, the words)`, and the address to start at. The understand. It is the words a neighbour writes to the node's port, in the
nucleus image `mkimage` writes today is this already, as a C file linked order they are written: for each stretch of memory that is not zero the
into the binary; it becomes a capsule in the capsule directory, with a two instruction words below with their address and count and then the
name, a hash and a signature like any other. (`mkcapsule` takes what is words themselves, and at the end a jump to where to start. The file is
under `capsules/`; the nucleus is built, not written. How a built file those words, each in a cell's bytes, low byte first, and its hash is the
enters the directory is to be settled when this is reached, and reported hash of exactly what is sent. The nucleus is one, in the capsule directory
before it is done, since `mkcapsule` is v3's tool too.) with a name, a hash and a signature like any other. `mkcapsule` was not
changed: the nucleus capsule is a built file kept under `capsules/v4/`.
A neighbour puts it into an empty node by writing to the port between A neighbour puts it into an empty node by writing to the port between
them, and the node executes what arrives (4.1). For each run it sends them, and the node executes what arrives (4.1). For each run it sends
@@ -264,6 +265,26 @@ Each is tested, committed and pushed before the next.
(`logs/20261006-074907`, `-075150`, `-075532`). (`logs/20261006-074907`, `-075150`, `-075532`).
2. **The nucleus as a capsule, and an empty node made a StarForth node 2. **The nucleus as a capsule, and an empty node made a StarForth node
through its port (section 5).** through its port (section 5).**
**Done 2026-10-06.** `v4/src/capsule.c`, `v4_capsule_write`: any
node's memory as the words to send an empty node. `mkimage` writes the
nucleus so, to `capsules/v4/nucleus-64.f18`, a built file kept in the
tree as `BLOCK_MAP.md` is; `mkcapsule` is unchanged and bakes, hashes
and signs it with the rest. The boot's node is born empty
(`v4_image_born`) and is given the nucleus a word at a time as it reads
its port, after the capsule's hash and signature are checked
(`v4/system/boot.c`). Nothing of the nucleus is linked into either
product any more. `test_fabric.c`: a memory with a programme and words
here and there arrives word for word and runs. Both products boot so:
`hosted-check` on three ISAs; `logs/20261006-102421`, `-102706`,
`-103048`.
**What a newborn node still has from its host:** the registers the
nucleus expects — the console's three, the two stack registers, the
error register, the fault table's address, the storage registers — are
attached by the host when the node is born, from the description
`mkimage` writes. They are the node's hardware as the golden model has
it, at addresses the memory map (D-4, still open) will fix. The console
and storage ones go when those become devices on ports (steps 3 and
6).
3. **Messages (section 6):** a StarForth node that reads messages when 3. **Messages (section 6):** a StarForth node that reads messages when
idle; the console as a device; a line typed is a message. idle; the console as a device; a line typed is a message.
4. **Finding the way (section 7).** 4. **Finding the way (section 7).**
+4 -1
View File
@@ -613,7 +613,7 @@ V4_DEFS := -DSTARFORTH_V4=1 -Iv4/include \
KERNEL_CFLAGS += $(V4_DEFS) KERNEL_CFLAGS += $(V4_DEFS)
LOADER_CFLAGS += $(V4_DEFS) LOADER_CFLAGS += $(V4_DEFS)
V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c) V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c)
V4_IMAGE_C := $(BUILD_DIR)/v4_image_64.c V4_IMAGE_C := $(BUILD_DIR)/v4_image_64.c
LOADER_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c LOADER_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c
@@ -765,6 +765,9 @@ endif
# StarForth v4: the capsule image, built on this machine by v4's Makefile, and # StarForth v4: the capsule image, built on this machine by v4's Makefile, and
# the engine, compiled like any other kernel source. # the engine, compiled like any other kernel source.
ifeq ($(STARFORTH_V4),1) ifeq ($(STARFORTH_V4),1)
# The nucleus is a capsule (capsules/v4/nucleus-64.f18), written by the same
# step that writes its description, so the capsule directory waits for it.
$(CAPSULE_GENERATED): $(V4_IMAGE_C)
$(V4_IMAGE_C): FORCE $(V4_IMAGE_C): FORCE
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
@echo " V4IMG v4/capsule -> $@" @echo " V4IMG v4/capsule -> $@"
@@ -0,0 +1,130 @@
[=3hBdsDxe: loading Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0)
BdsDxe: starting Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0)
[=3hStarKernel UEFI Loader
Loading kernel from ESP...
[CKPT 001] Entered efi_main - ConOut live
RAW SERIAL UP
[CKPT 002] Serial (COM1) initialized
Monolithic build - kernel linked directly
Collecting boot information...
CmdLine: parsed OK
[CKPT 004] Command line parsed
[CKPT 005] Kernel stack allocation decided
[CKPT 006] Boot info collected (ACPI table located)
GOP: linear framebuffer found
[CKPT 007] GOP: linear framebuffer found
[CKPT 008] About to enter ExitBootServices retry loop
EBS...
EBS OK
Calling kernel_main (monolithic)...
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0
Architecture: amd64
Build: Oct 6 2026 10:23:55
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 131
Total memory: 1023 MB
Usable memory: 968 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 249253
Free pages : 247730
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=2097406618 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=1009199508, tick_hz=100, initial_count=10091995
APIC Timer: configured (masked, ready to start)
Heartbeat: init done
Kernel initialization complete.
Boot successful!
StarForth v4: one host node, the F18-derived engine
V4: capsule v4:nucleus-64.f18 signature: missing (unsigned)
PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=0xbcc90cc5a638a299 capsule_hash=0xbcc90cc5a638a299 words=298
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x88fb8dcbb051f7b9
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=0x843716689a31b60a
PARITY:OK
POST: PASSED
ok> : SQ DUP * ;
ok
ok> 7 SQ . 3 4 U* . .
49 0 12 ok
ok> COLD
FORTH-79 Cold Start
System initialized.
ok
ok> 3 4 U* . .
0 12 ok
ok> FORGET U*
Protected word
ERROR
ok> 9 KERNEL-WORD ASK9 ASK9
Argument out of range
ERROR
ok> BYE
BYE: cold restart
@@ -0,0 +1,104 @@
UEFI firmware (version 2025.11-3ubuntu7.3 built at 15:40:26 on Sep 23 2026)
[=3hBdsDxe: failed to load Boot0002 "UEFI Misc Device" from PciRoot(0x0)/Pci(0x2,0x0): Not Found
BdsDxe: failed to load Boot0003 "UEFI QEMU QEMU USB HARDDRIVE 1-0000:00:04.0-1" from PciRoot(0x0)/Pci(0x4,0x0)/USB(0x0,0x0): Not Found
BdsDxe: loading Boot0004 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x6,0x0)
BdsDxe: starting Boot0004 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x6,0x0)
[=3hStarKernel UEFI Loader
Loading kernel from ESP...
[CKPT 001] Entered efi_main - ConOut live
Collecting boot information...
[CKPT 004] Command line parsed
[CKPT 005] Kernel stack allocation decided
[CKPT 006] Boot info collected (ACPI table located)
[CKPT 007] GOP: linear framebuffer found
[CKPT 008] About to enter ExitBootServices retry loop
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0
Architecture: aarch64
Build: Oct 6 2026 10:26:41
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 108
Total memory: 4093 MB
Usable memory: 4056 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 1039869
Free pages : 1038458
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=0xbcc90cc5a638a299 capsule_hash=0xbcc90cc5a638a299 words=298
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x88fb8dcbb051f7b9
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=0x843716689a31b60a
PARITY:OK
POST: PASSED
ok> : SQ DUP * ;
ok
ok> 7 SQ . 3 4 U* . .
49 0 12 ok
ok> COLD
FORTH-79 Cold Start
System initialized.
ok
ok> 3 4 U* . .
0 12 ok
ok> FORGET U*
Protected word
ERROR
ok> 9 KERNEL-WORD ASK9 ASK9
Argument out of range
ERROR
ok> BYE
BYE: cold restart
@@ -0,0 +1,174 @@
OpenSBI v1.8
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|
Platform Name : riscv-virtio,qemu
Platform Features : medeleg
Platform HART Count : 1
Platform HART Protection : pmp
Platform IPI Device : aclint-mswi
Platform Timer Device : aclint-mtimer @ 10000000Hz
Platform Console Device : uart8250
Platform HSM Device : ---
Platform PMU Device : ---
Platform Reboot Device : syscon-reboot
Platform Shutdown Device : syscon-poweroff
Platform Suspend Device : ---
Platform CPPC Device : ---
Firmware Base : 0x80000000
Firmware Size : 321 KB
Firmware RW Offset : 0x40000
Firmware RW Size : 65 KB
Firmware Heap Offset : 0x47000
Firmware Heap Size : 37 KB (total), 0 KB (reserved), 12 KB (used), 23 KB (free)
Firmware Scratch Size : 4096 B (total), 1464 B (used), 2632 B (free)
Runtime SBI Version : 3.0
Standard SBI Extensions : ipi,pmu,srst,sse,hsm,rfnc,fwft,time,base,legacy,dbcn,dbtr
Experimental SBI Extensions : none
Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000080040000-0x000000008005ffff M: (F,R,W) S/U: ()
Domain0 Region01 : 0x0000000080000000-0x000000008003ffff M: (F,R,X) S/U: ()
Domain0 Region02 : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W)
Domain0 Region03 : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region04 : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region05 : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W)
Domain0 Region06 : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W)
Domain0 Region07 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address : 0x0000000020000000
Domain0 Next Arg1 : 0x00000000bfe00000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes
Domain0 SysSuspend : yes
Boot HART ID : 0
Boot HART Domain : root
Boot HART Priv Version : v1.12
Boot HART Base ISA : rv64imafdch
Boot HART ISA Extensions : sstc,zicntr,zihpm,zicboz,zicbom,sdtrig,svadu
Boot HART PMP Count : 16
Boot HART PMP Granularity : 2 bits
Boot HART PMP Address Bits : 54
Boot HART MHPM Info : 16 (0x0007fff8)
Boot HART Debug Triggers : 2 triggers
Boot HART MIDELEG : 0x0000000000001666
Boot HART MEDELEG : 0x0000000000f4b509
[=3hRISC-V EDK2 firmware version 2025.11-3ubuntu7.3
Press ESCAPE within 5 seconds for boot options ERROR: C40000002:V03051002 I0 6D33944A-EC75-4855-A54D-809C75241F6C 83FFF850
BdsDxe: failed to load Boot0001 "UEFI Misc Device" fr
om PciRoot(0x0)/Pci(0x1,0x0): Not Found
[=3hStarKernel UEFI Loader
Loading kernel from ESP...
[CKPT 001] Entered efi_main - ConOut live
Monolithic build - kernel linked directly
Collecting boot information...
CmdLine: parsed OK
[CKPT 004] Command line parsed
[CKPT 005] Kernel stack allocation decided
[CKPT 006] Boot info collected (ACPI table located)
GOP: linear framebuffer found
[CKPT 007] GOP: linear framebuffer found
[CKPT 008] About to enter ExitBootServices retry loop
Calling kernel_main (monolithic)...
riscv64 item 4.3.5a: satp state at kernel entry (before switch)
satp.MODE = 0x000000000000000a
satp.PPN = 0x00000000000bf868
__kernel_start = 0x00000000bdea88f8
riscv64: satp cleared -- Bare mode, explicit (item 4.3.5a)
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0
Architecture: riscv64
Build: Oct 6 2026 10:30:10
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 100
Total memory: 1020 MB
Usable memory: 977 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 250783
Free pages : 250131
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=0xbcc90cc5a638a299 capsule_hash=0xbcc90cc5a638a299 words=298
V4: capsule v4:forth79.4th signature: missing (unsigned)
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x88fb8dcbb051f7b9
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=0x843716689a31b60a
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
+11 -4
View File
@@ -60,8 +60,16 @@ capsule_dir := -DV4_CAPSULE_DIR='"$(HERE)/capsule"'
# capsule/*.v4 -- and writes it as a C file, $(BINDIR)/v4_image_<width>.c. # capsule/*.v4 -- and writes it as a C file, $(BINDIR)/v4_image_<width>.c.
# The hosted system below and the bare-metal kernel (kernel/Makefile, # The hosted system below and the bare-metal kernel (kernel/Makefile,
# STARFORTH_V4=1) link the 64-bit one. docs/v4.0.0/NUCLEUS.md. # STARFORTH_V4=1) link the 64-bit one. docs/v4.0.0/NUCLEUS.md.
# The nucleus as a capsule of F18 code (include/v4/capsule.h). The 64-bit one
# is a product's: it goes into the capsule directory, ../capsules/v4, where
# the repository's mkcapsule finds it, hashes it and signs it with every
# other capsule. It is a built file kept in the tree, as
# ../capsules/BLOCK_MAP.md is, and is to be committed when it changes.
ROOT := $(abspath $(HERE)/..)
NUCLEUS_64 := $(ROOT)/capsules/v4/nucleus-64.f18
NUCLEUS_32 := $(BINDIR)/nucleus-32.f18
HOST_DEFS := -DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING) HOST_DEFS := -DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING)
ENGINE_SRCS := $(addprefix $(HERE)/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c) ENGINE_SRCS := $(addprefix $(HERE)/src/,node.c exec.c stack.c iword.c heat.c guard.c image.c fabric.c capsule.c)
define IMAGE_RULE define IMAGE_RULE
$(BINDIR)/mkimage-$(1): $(HERE)/tools/mkimage.c $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/tests/host_map.h $(HERE)/Makefile $(BINDIR)/mkimage-$(1): $(HERE)/tools/mkimage.c $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/tests/host_map.h $(HERE)/Makefile
@@ -69,7 +77,7 @@ $(BINDIR)/mkimage-$(1): $(HERE)/tools/mkimage.c $$(SRCS) $$(wildcard $(HERE)/inc
$$(CC) $$(CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=$(1) $(HOST_DEFS) $(capsule_dir) $(HERE)/tools/mkimage.c $$(SRCS) -o $$@ $$(CC) $$(CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=$(1) $(HOST_DEFS) $(capsule_dir) $(HERE)/tools/mkimage.c $$(SRCS) -o $$@
$(BINDIR)/v4_image_$(1).c: $(BINDIR)/mkimage-$(1) $$(CAPSULES) $(BINDIR)/v4_image_$(1).c: $(BINDIR)/mkimage-$(1) $$(CAPSULES)
$(BINDIR)/mkimage-$(1) $$@ $(BINDIR)/mkimage-$(1) $$@ $$(NUCLEUS_$(1))
endef endef
$(foreach w,$(WIDTHS),$(eval $(call IMAGE_RULE,$(w)))) $(foreach w,$(WIDTHS),$(eval $(call IMAGE_RULE,$(w))))
@@ -85,7 +93,6 @@ image: $(foreach w,$(WIDTHS),$(BINDIR)/v4_image_$(w).c)
# SIGN_KEY), and the code that reads it is the kernel's, compiled here as it # SIGN_KEY), and the code that reads it is the kernel's, compiled here as it
# is there. The binaries are static, so the two foreign ones run under # is there. The binaries are static, so the two foreign ones run under
# user-mode QEMU with nothing else installed. # user-mode QEMU with nothing else installed.
ROOT := $(abspath $(HERE)/..)
HOSTED_ISAS := amd64 aarch64 riscv64 HOSTED_ISAS := amd64 aarch64 riscv64
CC_amd64 ?= cc CC_amd64 ?= cc
CC_aarch64 ?= aarch64-linux-gnu-gcc CC_aarch64 ?= aarch64-linux-gnu-gcc
@@ -114,7 +121,7 @@ $(BINDIR)/mkcapsule: $(MKCAPSULE_SRCS)
@mkdir -p $(BINDIR) @mkdir -p $(BINDIR)
cc -std=c99 -Wall -Wextra -O2 -I$(ROOT)/v3/include -I$(ROOT)/kernel/include -I$(ROOT)/tools -o $@ $(MKCAPSULE_SRCS) cc -std=c99 -Wall -Wextra -O2 -I$(ROOT)/v3/include -I$(ROOT)/kernel/include -I$(ROOT)/tools -o $@ $(MKCAPSULE_SRCS)
$(CAPSULE_DIR_C): $(BINDIR)/mkcapsule $(CAPSULE_FILES) $(CAPSULE_DIR_C): $(BINDIR)/mkcapsule $(CAPSULE_FILES) $(BINDIR)/v4_image_64.c
$(BINDIR)/mkcapsule $(SIGN_KEY_ARGS) $(ROOT)/capsules $@ $(BINDIR)/mkcapsule $(SIGN_KEY_ARGS) $(ROOT)/capsules $@
define HOSTED_RULE define HOSTED_RULE
+6 -5
View File
@@ -29,7 +29,7 @@ under ASan and UBSan. There is no POST and no K measurement yet.
| Part | Where | What it is | | Part | Where | What it is |
|---|---|---| |---|---|---|
| Engine | `v4/src` | The golden model of the 32-opcode node | | Engine | `v4/src` | The golden model of the 32-opcode node |
| Nucleus | `v4/capsule/*.v4`, built by `v4/tools/mkimage.c` | The assembled words, as a memory image linked into the binary | | Nucleus | `v4/capsule/*.v4`, built by `v4/tools/mkimage.c` into `capsules/v4/nucleus-64.f18` | The assembled words, as a capsule of F18 code: sent through its port to a node born empty |
| Capsules | `capsules/v4/*.4th`, baked by `tools/mkcapsule.c` | FORTH source, loaded when the system comes up | | Capsules | `capsules/v4/*.4th`, baked by `tools/mkcapsule.c` | FORTH source, loaded when the system comes up |
| Boot | `v4/system/boot.c` | Starts the nucleus, checks and loads each capsule, prints the parity lines, gives the prompt | | Boot | `v4/system/boot.c` | Starts the nucleus, checks and loads each capsule, prints the parity lines, gives the prompt |
@@ -59,7 +59,7 @@ 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=298 image_hash=0xd982cf093aadb8b9 PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=... capsule_hash=0xbcc90cc5a638a299 words=298
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=... dict_hash=... 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=... PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=... dict_hash=...
@@ -68,8 +68,8 @@ POST: PASSED
ok> ok>
``` ```
Bare-metal logs: `logs/20261006-074907/amd64/`, `logs/20261006-075150/aarch64/`, Bare-metal logs: `logs/20261006-102421/amd64/`, `logs/20261006-102706/aarch64/`,
`logs/20261006-075532/riscv64/`. On each, seven lines were then typed at the `logs/20261006-103048/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*`, `COLD`, the capsule word again, a `FORGET` of it (refused), a kernel `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 word no one serves, and `BYE` — and each was answered as the hosted binary
@@ -105,7 +105,8 @@ everything about word records, the fleet and identity is its alone.
making v4 equal v3 on bare metal. Its first part is built and tested in making v4 equal v3 on bare metal. Its first part is built and tested in
the engine: a node has `V4_PORTS` ports; a read from one blocks until the the engine: a node has `V4_PORTS` ports; a read from one blocks until the
neighbour writes and a write until the neighbour reads; a node is born neighbour writes and a write until the neighbour reads; a node is born
empty with `P` at its ports and executes what a neighbour sends it; and empty with `P` at its ports and executes what a neighbour sends it, which
is how each product's node now gets its nucleus, as a capsule; and
the fabric (`v4/src/fabric.c`) is the nodes there are and the table of how the fabric (`v4/src/fabric.c`) is the nodes there are and the table of how
their ports are wired, both of which change while they run. The products their ports are wired, both of which change while they run. The products
below are still one node each; they become the five nodes at `MESH.md` below are still one node each; they become the five nodes at `MESH.md`
+3 -3
View File
@@ -5,7 +5,7 @@
* (kernel/src/v4/sk_v4.c) both call v4_boot_run and differ only in where * (kernel/src/v4/sk_v4.c) both call v4_boot_run and differ only in where
* the text goes. So the two start the same way and print the same lines: * the text goes. So the two start the same way and print the same lines:
* *
* PARITY:V4_NUCLEUS words=N image_hash=0x... * PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=0x... capsule_hash=0x... words=N
* PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x... capsule_hash=0x... dict_hash=0x... * PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x... capsule_hash=0x... dict_hash=0x...
* PARITY:V4_POST tests=N pass=N fail=N (printed by the POST capsule) * PARITY:V4_POST tests=N pass=N fail=N (printed by the POST capsule)
* PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0x... capsule_hash=0x... dict_hash=0x... * PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0x... capsule_hash=0x... dict_hash=0x...
@@ -59,8 +59,8 @@ typedef struct {
#define V4_BOOT_KEY_NONE (-1) /* not yet: it is asked again */ #define V4_BOOT_KEY_NONE (-1) /* not yet: it is asked again */
#define V4_BOOT_KEY_END (-2) /* and there never will be */ #define V4_BOOT_KEY_END (-2) /* and there never will be */
/* Start the node from the nucleus image, with `disk` as its block storage /* A node is born empty, with `disk` as its block storage (v4_image_born);
* (see v4_image_boot), and load the capsules. Returns 1 with the node idle, * it is sent the nucleus through its port; and the capsules are loaded. Returns 1 with the node idle,
* waiting to be handed a line; or 0, the failure printed. It prints no * waiting to be handed a line; or 0, the failure printed. It prints no
* prompt: that is the host's. */ * prompt: that is the host's. */
int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks); int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks);
+36
View File
@@ -0,0 +1,36 @@
/* capsule.h -- a capsule of F18 code: what a neighbour writes to a node's
* port to put a programme into it and start it. docs/v4.0.0/MESH.md
* section 5.
*
* A node newly born has nothing in it and executes what arrives at its port
* (node.h). So a capsule of F18 code is not a format a node has to
* understand: it is the words themselves, in the order they are sent. For
* each stretch of memory that is not zero,
*
* @p a! @p push the node executes this from its port,
* address takes these two as its literals,
* count - 1
* @p !+ unext and then this: each word that follows is fetched
* the words ... from the port and stored
*
* and at the end
*
* jump start
*
* This is the F18's own way of loading a node through a port. Nothing here
* knows what the programme is: StarForth's nucleus is one such capsule
* (v4/tools/mkimage.c), and anything written for the node is another.
*/
#ifndef V4_CAPSULE_H
#define V4_CAPSULE_H
#include "v4/node.h"
/* Write the capsule that makes a node's memory what `n`'s is, and starts it
* at `start`: `out` is called with each word in turn. Words of `n` that
* are zero are not sent -- a node is born with its memory zero. Returns
* how many words were written. */
unsigned v4_capsule_write(const v4_node *n, v4_cell start,
void (*out)(void *self, v4_cell word), void *self);
#endif /* V4_CAPSULE_H */
+21 -25
View File
@@ -1,31 +1,24 @@
/* image.h -- a capsule image: the host node's memory with the vocabulary in /* image.h -- the StarForth nucleus, as its host knows it: where things are
* it, and what a loader needs to know to start it. * in a node that has taken it in, and how a line is handed to that node.
* *
* The compiler capsule is text (capsule/ *.v4) and FORTH source (capsule/ * The nucleus is text (capsule/ *.v4). tools/mkimage.c assembles it on the
* *.fth). tools/mkimage.c assembles the one and has a node compile the * build machine and writes two things:
* other, on the build machine, and writes what the node's memory then holds
* as a C file. A system that is to run the vocabulary -- the hosted binary,
* the bare-metal kernel -- links that file and calls v4_image_boot: it needs
* no assembler, no files and no C library.
* *
* An image is for one cell width, one node size and one pair of stack sizes; * - the nucleus as a capsule of F18 code (capsule.h), which is put into
* v4_image_boot refuses an image the engine was not built for. * an empty node through its port. It is a capsule like any other: in
* the capsule directory, with a name, a hash and a signature
* (capsules/v4/nucleus-<width>.f18). docs/v4.0.0/MESH.md section 5.
* - this description of it, a C file: the addresses a host needs.
*
* A description is for one cell width, one node size and one pair of stack
* sizes; v4_image_born refuses one the engine was not built for.
*/ */
#ifndef V4_IMAGE_H #ifndef V4_IMAGE_H
#define V4_IMAGE_H #define V4_IMAGE_H
#include "v4/exec.h" #include "v4/exec.h"
/* one word of memory that is not zero */
typedef struct { typedef struct {
v4_cell addr;
v4_cell value;
} v4_image_cell;
typedef struct {
const v4_image_cell *cells; /* every non-zero word of memory, in address order */
unsigned count;
/* what the image was built for */ /* what the image was built for */
unsigned cell_bits, node_words, data_ring, ret_ring; unsigned cell_bits, node_words, data_ring, ret_ring;
@@ -66,7 +59,8 @@ typedef struct {
v4_cell boot_cells, fence; v4_cell boot_cells, fence;
} v4_image; } v4_image;
/* The image built from v4/capsule (the generated file defines it). */ /* The description of the nucleus 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 /* The system is complete: what is in the dictionary now is what COLD comes
@@ -78,11 +72,13 @@ void v4_image_seal(v4_node *n, const v4_image *im);
#define V4_LINE_COMPLETED 1 /* the host says " ok" */ #define V4_LINE_COMPLETED 1 /* the host says " ok" */
#define V4_LINE_ERROR 2 /* the message is printed; the host says " ERROR" */ #define V4_LINE_ERROR 2 /* the message is printed; the host says " ERROR" */
/* Reset `n`, load the image into it, attach its registers -- with `disk`, /* A node newly born (node.h), with the registers the nucleus expects of the
* `blocks` blocks of 1024 bytes, as its block storage, or none if `disk` is * node it runs on -- and `disk`, `blocks` blocks of 1024 bytes, as its block
* 0 -- and leave it idle, waiting to be handed a line. Returns 1, or 0 if * storage, or none if `disk` is 0. Its memory is empty and it is blocked
* the image is not for this build of the engine (nothing is then done). */ * reading its ports: the nucleus is yet to be sent to it. Returns 1, or 0
int v4_image_boot(v4_node *n, v4_exec_state *es, v4_heat *h, const v4_image *im, * if the description is not for this build of the engine (nothing is then
* done). */
int v4_image_born(v4_node *n, v4_exec_state *es, v4_heat *h, const v4_image *im,
unsigned char *disk, unsigned blocks); unsigned char *disk, unsigned blocks);
/* Hand the node `len` characters of text to interpret. Returns 1, or 0 if /* Hand the node `len` characters of text to interpret. Returns 1, or 0 if
+55
View File
@@ -0,0 +1,55 @@
/* capsule.c -- a capsule of F18 code. See capsule.h.
*
* Nothing here uses the C library.
*/
#include "v4/capsule.h"
#include "v4/iword.h"
#include "v4/opcode.h"
/* A stretch of zeros shorter than this, between words that are not zero, is
* sent with them: starting a new stretch costs four words. */
#define GAP 4u
static v4_cell words_of(unsigned a, unsigned b, unsigned c, unsigned d)
{
unsigned op[V4_SLOT_COUNT];
unsigned k;
op[0] = a; op[1] = b; op[2] = c; op[3] = d;
for (k = 4; k < V4_SLOT_COUNT; k++) op[k] = V4_OP_NOP;
return (v4_cell)v4_iword_assemble(op, 0);
}
/* `jump addr`, the jump in the first slot so that its address is as wide as
* an address can be */
static v4_cell jump_to(v4_cell addr)
{
v4_ucell mask = (v4_ucell)v4_iword_slot_mask(0);
v4_ucell word = (v4_ucell)words_of(V4_OP_JUMP, V4_OP_NOP, V4_OP_NOP, V4_OP_NOP);
return (v4_cell)((word & ~mask) | ((v4_ucell)addr & mask));
}
unsigned v4_capsule_write(const v4_node *n, v4_cell start,
void (*out)(void *self, v4_cell word), void *self)
{
unsigned k = 0, written = 0;
while (k < V4_NODE_WORDS) {
unsigned first, last, zeros, i;
if (n->mem[k] == 0) { k++; continue; }
first = last = k;
zeros = 0;
for (i = k + 1; i < V4_NODE_WORDS && zeros < GAP; i++) {
if (n->mem[i] != 0) { last = i; zeros = 0; } else zeros++;
}
out(self, words_of(V4_OP_FETCH_P, V4_OP_BANG_A, V4_OP_FETCH_P, V4_OP_PUSH));
out(self, (v4_cell)first);
out(self, (v4_cell)(last - first));
out(self, words_of(V4_OP_FETCH_P, V4_OP_STORE_INC, V4_OP_UNEXT, V4_OP_NOP));
for (i = first; i <= last; i++) out(self, n->mem[i]);
written += 4u + (last - first + 1u);
k = last + 1;
}
out(self, jump_to(start));
return written + 1u;
}
+3 -11
View File
@@ -1,34 +1,26 @@
/* image.c -- start a node from a capsule image. See image.h. /* image.c -- the StarForth nucleus, as its host knows it. See image.h.
* *
* Nothing here uses the C library: the bare-metal kernel links this file. * Nothing here uses the C library: the bare-metal kernel links this file.
*/ */
#include "v4/image.h" #include "v4/image.h"
int v4_image_boot(v4_node *n, v4_exec_state *es, v4_heat *h, const v4_image *im, int v4_image_born(v4_node *n, v4_exec_state *es, v4_heat *h, const v4_image *im,
unsigned char *disk, unsigned blocks) unsigned char *disk, unsigned blocks)
{ {
unsigned i;
if (im->cell_bits != (unsigned)V4_CELL_BITS || im->node_words != (unsigned)V4_NODE_WORDS if (im->cell_bits != (unsigned)V4_CELL_BITS || im->node_words != (unsigned)V4_NODE_WORDS
|| im->data_ring != (unsigned)V4_DATA_RING || im->ret_ring != (unsigned)V4_RET_RING) || im->data_ring != (unsigned)V4_DATA_RING || im->ret_ring != (unsigned)V4_RET_RING)
return 0; return 0;
v4_node_reset(n); v4_node_born(n, im->port);
v4_exec_reset(es); v4_exec_reset(es);
v4_heat_reset(h); v4_heat_reset(h);
for (i = 0; i < im->count; i++)
if (v4_node_addr_ok(im->cells[i].addr)) n->mem[im->cells[i].addr] = im->cells[i].value;
v4_node_console_attach(n, im->console_tx); v4_node_console_attach(n, im->console_tx);
v4_node_console_input_attach(n, im->console_rx, im->console_status); v4_node_console_input_attach(n, im->console_rx, im->console_status);
v4_node_stack_regs_attach(n, im->dstack_reg, im->rstack_reg); v4_node_stack_regs_attach(n, im->dstack_reg, im->rstack_reg);
v4_node_error_attach(n, im->node_error); v4_node_error_attach(n, im->node_error);
v4_node_fault_attach(n, im->fault_table); v4_node_fault_attach(n, im->fault_table);
v4_node_port_attach(n, im->port);
if (disk && blocks) v4_node_storage_attach(n, im->storage_reg, disk, blocks); if (disk && blocks) v4_node_storage_attach(n, im->storage_reg, disk, blocks);
n->mem[im->line_status] = V4_LINE_COMPLETED;
n->p = im->idle;
return 1; return 1;
} }
+70 -16
View File
@@ -80,17 +80,6 @@ uint64_t v4_boot_dict_hash(const v4_node *n, const v4_image *im)
return hash_cell(h, n->mem[im->latest]); return hash_cell(h, n->mem[im->latest]);
} }
static uint64_t image_hash(const v4_image *im)
{
uint64_t h = FNV_OFFSET;
unsigned i;
for (i = 0; i < im->count; i++) {
h = hash_cell(h, im->cells[i].addr);
h = hash_cell(h, im->cells[i].value);
}
return h;
}
/* how many words FORTH holds: the list from LATEST, each entry's link in the /* how many words FORTH holds: the list from LATEST, each entry's link in the
* cell before its code */ * cell before its code */
static uint32_t word_count(const v4_node *n, const v4_image *im) static uint32_t word_count(const v4_node *n, const v4_image *im)
@@ -258,6 +247,72 @@ static int load_capsule(const v4_boot *b, const char *name)
return 1; return 1;
} }
/* ---- the nucleus ----------------------------------------------------------- */
/* The nucleus is a capsule of F18 code (v4/include/v4/capsule.h): the words
* themselves, to be written to the port of a node that has nothing in it.
* The boot is that node's neighbour. It finds the capsule, checks its hash
* and its signature as it does any capsule's, and gives the node a word
* each time it reads its port, until the node has taken them all and is
* where the nucleus waits to be handed a line. */
#define NUCLEUS_NAME_(bits) "v4:nucleus-" #bits ".f18"
#define NUCLEUS_NAME__(bits) NUCLEUS_NAME_(bits)
#define NUCLEUS_NAME NUCLEUS_NAME__(V4_CELL_BITS)
#define CELL_BYTES (V4_CELL_BITS / 8)
static int load_nucleus(const v4_boot *b)
{
const CapsuleDirHeader *dir = capsule_get_directory();
const CapsuleDesc *descs = capsule_get_descriptors();
const CapsuleNameEntry *names = capsule_get_names();
const uint8_t *arena = capsule_get_arena();
const CapsuleDesc *cap;
const uint8_t *p;
CapsuleValidateResult vr;
CapsuleSigResult sr;
v4_node *n = b->n;
uint64_t words, at = 0, steps = 0;
cap = capsule_find_by_name(dir, descs, names, NUCLEUS_NAME);
if (!cap) { say(b, "V4: capsule " NUCLEUS_NAME " is not in this binary\n"); return 0; }
vr = capsule_validate(cap, arena, dir->arena_size, 1);
if (vr != CAPSULE_VALID) {
say(b, "V4: capsule " NUCLEUS_NAME ": "); say(b, capsule_validate_result_str(vr)); say(b, "\n");
return 0;
}
sr = capsule_verify_signature(descs, names, capsule_get_signatures(), arena, dir->desc_count, (int)(cap - descs));
if (sr != CAPSULE_SIG_OK) {
say(b, "V4: capsule " NUCLEUS_NAME " signature: "); say(b, capsule_sig_result_str(sr)); say(b, "\n");
if (sr == CAPSULE_SIG_INVALID) return 0;
}
p = capsule_get_payload(cap, arena);
if (!p || cap->length % CELL_BYTES != 0) { say(b, "V4: capsule " NUCLEUS_NAME " is not whole words\n"); return 0; }
words = cap->length / CELL_BYTES;
while (at < words || !v4_line_done(n, b->im)) {
(void)v4_exec_step_word(n, b->es, b->h);
if (n->stopped || n->asking || ++steps > STEP_LIMIT) {
say(b, "V4: the node did not take the nucleus in\n");
return 0;
}
if (n->reading && !n->given) {
v4_ucell word = 0;
unsigned i;
if (at >= words) { say(b, "V4: the node wants more than the nucleus holds\n"); return 0; }
for (i = 0; i < CELL_BYTES; i++) word |= (v4_ucell)p[at * CELL_BYTES + i] << (8 * i);
v4_node_port_give(n, 0, (v4_cell)word);
at++;
}
}
say(b, "PARITY:V4_NUCLEUS name=" NUCLEUS_NAME);
say(b, " capsule_id="); say_hex(b, cap->capsule_id);
say(b, " capsule_hash="); say_hex(b, cap->content_hash);
say(b, " words="); say_dec(b, word_count(n, b->im));
say(b, "\n");
return 1;
}
/* ---- the whole boot ------------------------------------------------------ */ /* ---- the whole boot ------------------------------------------------------ */
int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks) int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks)
@@ -265,13 +320,12 @@ int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks)
unsigned i; unsigned i;
post_len = 0; post_seen = 0; post_clean = 0; post_len = 0; post_seen = 0; post_clean = 0;
if (!v4_image_boot(b->n, b->es, b->h, b->im, disk, blocks)) { /* a node is born with nothing in it, and is sent the nucleus */
say(b, "V4: the nucleus image is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n"); if (!v4_image_born(b->n, b->es, b->h, b->im, disk, blocks)) {
say(b, "V4: the nucleus is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n");
return 0; return 0;
} }
say(b, "PARITY:V4_NUCLEUS words="); say_dec(b, word_count(b->n, b->im)); if (!load_nucleus(b)) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
say(b, " image_hash="); say_hex(b, image_hash(b->im));
say(b, "\n");
/* the kernel's words, before any capsule that might use one */ /* the kernel's words, before any capsule that might use one */
for (i = 0; i < b->word_count; i++) { for (i = 0; i < b->word_count; i++) {
+44
View File
@@ -6,6 +6,7 @@
* At the level of opcodes: no word of any vocabulary is defined here. * At the level of opcodes: no word of any vocabulary is defined here.
*/ */
#include "v4/asm.h" #include "v4/asm.h"
#include "v4/capsule.h"
#include "v4/fabric.h" #include "v4/fabric.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -69,6 +70,13 @@ static int stream_take(void *self, v4_cell value)
return 1; return 1;
} }
/* for v4_capsule_write: each word of the capsule, into the stream's own list */
static void collect(void *self, v4_cell word)
{
stream *s = (stream *)self;
((v4_cell *)s->words)[s->count++] = word;
}
/* What a neighbour sends an empty node to put `count` words at `addr` and /* What a neighbour sends an empty node to put `count` words at `addr` and
* start there (MESH.md section 5): for the F18, and for this node, code a * start there (MESH.md section 5): for the F18, and for this node, code a
* node executes straight from its port. */ * node executes straight from its port. */
@@ -187,6 +195,42 @@ int main(void)
CHECK(ND(b)->n.last_from == 0, "it was listening to any port, and knows which it heard from: %u", ND(b)->n.last_from); CHECK(ND(b)->n.last_from == 0, "it was listening to any port, and knows which it heard from: %u", ND(b)->n.last_from);
} }
/* ---- a capsule of F18 code: any node's memory, sent to an empty node (capsule.h) ---- */
{
static v4_node src;
static v4_cell sent[400];
static stream s;
static v4_device dev;
unsigned k;
v4_node_reset(&src);
v4_asm_begin(&as, &src, 40); /* a programme, not at the start of memory */
LIT(123); LIT(OUT); O(BANG_A); O(STORE_A);
wait_for_ever();
CHECK(v4_asm_ok(&as), "a programme for the capsule");
src.mem[3] = 5; /* and words here and there: the first of memory, */
src.mem[200] = -1; src.mem[202] = 7; /* two with one zero between them, */
src.mem[300] = 9; src.mem[310] = 11; /* two with many, */
src.mem[PB - 1] = 13; /* and the last before the ports */
memset(&s, 0, sizeof s);
s.words = sent;
dev.give = stream_give; dev.take = stream_take; dev.self = &s;
s.count = 0;
s.count = v4_capsule_write(&src, 40, collect, &s);
CHECK(s.count > 0 && s.count < 400, "the capsule is %u words for a memory of %u", s.count, (unsigned)V4_NODE_WORDS);
s.at = 0;
v4_fabric_init(&f, places, 4);
b = (unsigned)v4_fabric_add(&f, &pool[0], PB);
CHECK(v4_fabric_wire_device(&f, b, 5, &dev), "it is sent to a node newly born");
steps = settle(5000);
CHECK(s.at == s.count, "which takes all of it: %u of %u", s.at, s.count);
for (k = 0; k < V4_NODE_WORDS && ND(b)->n.mem[k] == (k == (unsigned)OUT ? 123 : src.mem[k]); k++) { }
CHECK(k == V4_NODE_WORDS, "its memory is then the same, word for word (first difference at %u)", k);
CHECK(ND(b)->n.mem[OUT] == 123 && ND(b)->n.reading, "and it was started where the capsule says, and ran");
}
/* ---- a word is passed on by a node in between ---- */ /* ---- a word is passed on by a node in between ---- */
v4_fabric_init(&f, places, 4); v4_fabric_init(&f, places, 4);
a = loaded(0); a = loaded(0);
+27 -14
View File
@@ -1,19 +1,21 @@
/* mkimage.c -- build the capsule image (include/v4/image.h). /* mkimage.c -- build the capsule image (include/v4/image.h).
* *
* mkimage OUTPUT.c * mkimage DESCRIPTION.c CAPSULE.f18
* *
* Runs on the build machine. It assembles capsule/ *.v4 -- the nucleus, * Runs on the build machine. It assembles capsule/ *.v4 -- the nucleus,
* docs/v4.0.0/NUCLEUS.md -- onto a host node with the memory map of * docs/v4.0.0/NUCLEUS.md -- onto a host node with the memory map of
* tests/host_map.h. It runs nothing: it writes every * tests/host_map.h. It runs nothing. It writes the nucleus as a capsule
* word of the node's memory that is not zero, and the addresses a loader * of F18 code (v4/include/v4/capsule.h) -- what is sent to an empty node's
* needs, as a C file. No FORTH source is compiled here: what is not in the * port to make it a StarForth node -- and the addresses a host needs, as a
* nucleus is loaded from capsules when the system boots (v4/system/boot.c). * C file (v4/include/v4/image.h). No FORTH source is compiled here: what
* is not in the nucleus is loaded from capsules when the system boots.
* *
* The image is the same on every machine it is built on: nothing in it * The image is the same on every machine it is built on: nothing in it
* depends on the build machine but the cell width this tool was compiled * depends on the build machine but the cell width this tool was compiled
* for. * for.
*/ */
#include "v4/image.h" #include "v4/image.h"
#include "v4/capsule.h"
#include "v4/text.h" #include "v4/text.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -31,6 +33,14 @@ static void die(const char *what, const char *detail)
exit(1); exit(1);
} }
/* one word of the capsule, to the file */
static void put_word(void *self, v4_cell word)
{
v4_ucell u = (v4_ucell)word;
unsigned i;
for (i = 0; i < V4_CELL_BITS / 8; i++) fputc((int)((u >> (8 * i)) & 0xFFu), (FILE *)self);
}
/* every entry from xt back: no access control fields set */ /* every entry from xt back: no access control fields set */
static void clear_acl(v4_cell xt) static void clear_acl(v4_cell xt)
{ {
@@ -45,7 +55,7 @@ int main(int argc, char **argv)
v4_cell k, line, idle, faults, voc; v4_cell k, line, idle, faults, voc;
unsigned count = 0; unsigned count = 0;
if (argc != 2) die("usage: mkimage OUTPUT.c", NULL); if (argc != 3) die("usage: mkimage DESCRIPTION.c CAPSULE.f18", NULL);
if (!host_load(&tx, &n, files, (unsigned)(sizeof files / sizeof files[0]))) die("the capsule does not assemble", NULL); if (!host_load(&tx, &n, files, (unsigned)(sizeof files / sizeof files[0]))) die("the capsule does not assemble", NULL);
if (!v4_text_finish(&tx)) die("not everything is defined", v4_text_error(&tx)); if (!v4_text_finish(&tx)) die("not everything is defined", v4_text_error(&tx));
@@ -89,16 +99,19 @@ int main(int argc, char **argv)
for (voc = n.mem[VOC_LINK]; voc != 0; voc = n.mem[voc + 1]) clear_acl(n.mem[voc]); for (voc = n.mem[VOC_LINK]; voc != 0; voc = n.mem[voc + 1]) clear_acl(n.mem[voc]);
if (!v4_node_guards_intact(&n)) die("the node's guards are damaged", NULL); if (!v4_node_guards_intact(&n)) die("the node's guards are damaged", NULL);
n.mem[LINE_STATUS] = V4_LINE_COMPLETED; /* idle, with nothing to report */
/* the nucleus as a capsule of F18 code: the words a neighbour writes to
* an empty node's port, each in V4_CELL_BITS / 8 bytes, low byte first */
out = fopen(argv[2], "wb");
if (!out) die("cannot write", argv[2]);
count = v4_capsule_write(&n, idle, put_word, out);
if (fclose(out) != 0) die("cannot write", argv[2]);
out = fopen(argv[1], "w"); out = fopen(argv[1], "w");
if (!out) die("cannot write", argv[1]); if (!out) die("cannot write", argv[1]);
fprintf(out, "/* Generated by v4/tools/mkimage.c from v4/capsule -- do not edit. */\n#include \"v4/image.h\"\n\n"); fprintf(out, "/* Generated by v4/tools/mkimage.c from v4/capsule -- do not edit. */\n#include \"v4/image.h\"\n\n");
fprintf(out, "static const v4_image_cell cells[] = {\n"); fprintf(out, "const v4_image v4_capsule_image = {\n %uu, %uu, %uu, %uu,\n",
for (k = 0; k < (v4_cell)V4_NODE_WORDS; k++)
if (n.mem[k] != 0) {
fprintf(out, " { %ld, (v4_cell)0x%llxULL },\n", (long)k, (unsigned long long)(v4_ucell)n.mem[k]);
count++;
}
fprintf(out, "};\n\nconst v4_image v4_capsule_image = {\n cells, %uu,\n %uu, %uu, %uu, %uu,\n", count,
(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);
@@ -106,6 +119,6 @@ int main(int argc, char **argv)
(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); (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: a capsule of %u words, dictionary to word %ld, %d-bit cells -> %s, %s\n", count, (long)((n.mem[DP] + 3) / 4), V4_CELL_BITS, argv[2], argv[1]);
return 0; return 0;
} }