feat(v4.0.0): the system boots from a nucleus and loads its capsules

One boot, v4/system/boot.c, for both products: it starts the nucleus image,
finds each capsule in the baked capsule directory, recomputes its hash,
checks its signature, gives its blocks to the node a line at a time, and
prints PARITY:V4_NUCLEUS, PARITY:V4_CAPSULE and PARITY:OK before the prompt.
A line the node does not accept ends the boot with the capsule, block and
line named.  docs/v4.0.0/NUCLEUS.md.

- hosted Linux product for amd64, aarch64 and riscv64 (make -C v4 hosted);
  make -C v4 hosted-check boots all three and requires identical output
- the kernel's v4 entry (STARFORTH_V4=1) calls the same boot
- capsules/v4/forth79.4th, block 6000: no definitions yet
- mkimage builds the nucleus only; no FORTH source is compiled at build time
- capsule_blocks.c: the Block-header parse, free of any VM, for every loader

Verified: make -C v4 test passes; hosted-check passes on the three ISAs with
the same hashes; the kernel compiles with STARFORTH_V4=1 on the three.
Not verified: no bare-metal boot of v4 has been run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-05 15:28:30 -04:00
co-authored by Claude Opus 5.5
parent 903321e548
commit 294e69463a
18 changed files with 968 additions and 8 deletions
+11
View File
@@ -13,6 +13,17 @@ config STARFORTH_ENABLE_VM
no capsules, no "ok" REPL. Gates a large source-file selection
block in kernel/Makefile, not just a handful of -D flags.
config STARFORTH_V4
bool "Boot StarForth v4, the F18-derived engine, at a single prompt (STARFORTH_V4)"
default n
help
Instead of the v3 VM and its fleet, the kernel starts one StarForth
v4 host node after the M0-M6 hardware milestones: the golden model
of the 32-opcode engine (v4/src) running the capsule image built
from v4/capsule, with its console on the kernel's serial console.
It reaches v4's "ok> " prompt and stays there. The v3 VM is still
compiled in but is not started. See docs/v4.0.0/DECOMPOSITION.md.
config PARITY_MODE
bool "Deterministic parity harness mode (PARITY_MODE)"
default n
+4 -2
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-10-01T19:21:21Z -->
<!-- Generated by mkcapsule --manifest 2026-10-05T19:24:40Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
@@ -29,6 +29,7 @@
| `sdk.4th` | 5109, 5110, 5111, 5112, 5113, 5114, 5115 | `0x008fdbbb62c94a3a` | n/a |
| `turtle.4th` | 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5107, 5108 | `0x4d470418ca543365` | n/a |
| `user-font-demo.4th` | 4200, 4201, 4202 | `0xce1fd7d1b581a56d` | n/a |
| `v4:forth79.4th` | 6000 | `0xa4b74bdc7deaf204` | n/a |
| `workload-0.4th` | 2200, 2201 | `0x93f86f60aeba8feb` | n/a |
| `workload-1-lite.4th` | 5058, 5059 | `0x44a7a7e3176dcc8d` | n/a |
| `workload-1.4th` | 4406, 4415, 4425, 4435 | `0x63e251adb0a03613` | n/a |
@@ -366,10 +367,11 @@
| 5113 | `sdk.4th` | `0x008fdbbb62c94a3a` | ok |
| 5114 | `sdk.4th` | `0x008fdbbb62c94a3a` | ok |
| 5115 | `sdk.4th` | `0x008fdbbb62c94a3a` | ok |
| 6000 | `v4:forth79.4th` | `0xa4b74bdc7deaf204` | ok |
## Conflicts
None.
---
*36 capsule(s) scanned. Re-run `mkcapsule --manifest <dir>` to refresh.*
*37 capsule(s) scanned. Re-run `mkcapsule --manifest <dir>` to refresh.*
+6
View File
@@ -0,0 +1,6 @@
Block 6000
( forth79.4th -- the FORTH-79 Required Word Set for v4, )
( as colon definitions, loaded when the system boots. )
( docs/v4.0.0/NUCLEUS.md. Blocks 6000 up. )
( A word moves here from the assembled nucleus, v4/capsule, )
( once its colon definition passes POST. None has moved yet. )
+65
View File
@@ -60,6 +60,9 @@ $(eval $(call kconfig_bool,PARITY_MODE,0))
# StarForth VM integration (default: enabled)
$(eval $(call kconfig_bool,STARFORTH_ENABLE_VM,1))
# StarForth v4 at a single prompt in place of the v3 VM (default: off)
$(eval $(call kconfig_bool,STARFORTH_V4,0))
# Monolithic build — loader + kernel compiled together (default: enabled)
MONOLITHIC ?= 1
@@ -597,6 +600,30 @@ LOADER_VM_OBJS := $(patsubst v3/src/%.c,$(LOADER_OBJ_DIR)/vmcore/%.o,$(VM_CORE_S
KERNEL_VM_OBJS := $(patsubst v3/src/%.c,$(KERNEL_OBJ_DIR)/vmcore/%.o,$(VM_CORE_SRCS))
endif
# ------------------------------------------------------------------------------
# StarForth v4 (STARFORTH_V4=1): the golden model of the F18-derived engine,
# v4/src, and the nucleus image v4's own Makefile builds on this machine from
# v4/capsule (docs/v4.0.0/NUCLEUS.md). The node is the host node's size, with 64-bit cells, as every
# ISA this kernel boots on has (DECOMPOSITION.md D-5), so the image is the
# same file for all three.
# ------------------------------------------------------------------------------
ifeq ($(STARFORTH_V4),1)
V4_DEFS := -DSTARFORTH_V4=1 -Iv4/include \
-DV4_CELL_BITS=64 -DV4_NODE_WORDS=16384 -DV4_DATA_RING=30 -DV4_RET_RING=31
KERNEL_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)
V4_IMAGE_C := $(BUILD_DIR)/v4_image_64.c
LOADER_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c
KERNEL_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c
# v4/system/boot.c is the boot the hosted v4 system runs too: nucleus, then
# the capsules from this kernel's own capsule directory, then the prompt.
LOADER_V4_OBJS := $(patsubst v4/src/%.c,$(LOADER_OBJ_DIR)/v4engine/%.o,$(V4_ENGINE_SRCS)) $(LOADER_OBJ_DIR)/v4engine/v4_image.o $(LOADER_OBJ_DIR)/v4system/boot.o
KERNEL_V4_OBJS := $(patsubst v4/src/%.c,$(KERNEL_OBJ_DIR)/v4engine/%.o,$(V4_ENGINE_SRCS)) $(KERNEL_OBJ_DIR)/v4engine/v4_image.o $(KERNEL_OBJ_DIR)/v4system/boot.o
endif
LOADER_SRCS := $(LOADER_SRCS_BASE) $(LOADER_EXTRA_SRCS)
KERNEL_SRCS := $(KERNEL_SRCS_BASE) $(KERNEL_EXTRA_SRCS)
@@ -605,12 +632,14 @@ LOADER_OBJS := \
$(patsubst $(KERNEL_SRC)/%.c,$(LOADER_OBJ_DIR)/%.o,$(LOADER_ARCH_SRCS)) \
$(patsubst $(KERNEL_SRC)/%.S,$(LOADER_OBJ_DIR)/%.o,$(LOADER_ASM)) \
$(LOADER_VM_OBJS) \
$(LOADER_V4_OBJS) \
$(CAPSULE_GENERATED_OBJ)
KERNEL_OBJS := \
$(patsubst $(KERNEL_SRC)/%.c,$(KERNEL_OBJ_DIR)/%.o,$(KERNEL_SRCS)) \
$(patsubst $(KERNEL_SRC)/%.S,$(KERNEL_OBJ_DIR)/%.o,$(KERNEL_ASM)) \
$(KERNEL_VM_OBJS) \
$(KERNEL_V4_OBJS) \
$(CAPSULE_GENERATED_KOBJ)
# ==============================================================================
@@ -733,6 +762,41 @@ else
@$(LOADER_CC) $(VMCORE_CFLAGS_COMMON) $(filter-out -I$(KERNEL_INC),$(LOADER_CFLAGS)) -c $< -o $@
endif
# StarForth v4: the capsule image, built on this machine by v4's Makefile, and
# the engine, compiled like any other kernel source.
ifeq ($(STARFORTH_V4),1)
$(V4_IMAGE_C): FORCE
@mkdir -p $(dir $@)
@echo " V4IMG v4/capsule -> $@"
@$(MAKE) --no-print-directory -C v4 CC=cc build/v4_image_64.c
@cmp -s v4/build/v4_image_64.c $@ || cp v4/build/v4_image_64.c $@
$(LOADER_OBJ_DIR)/v4engine/v4_image.o: $(V4_IMAGE_C) | $(LOADER_OBJ_DIR)
@mkdir -p $(dir $@)
@echo "CC (loader) $<"
@$(LOADER_CC) $(LOADER_CFLAGS) -c $< -o $@
$(KERNEL_OBJ_DIR)/v4engine/v4_image.o: $(V4_IMAGE_C) | $(KERNEL_OBJ_DIR)
@mkdir -p $(dir $@)
@echo "CC (kernel) $<"
@$(CC) $(KERNEL_CFLAGS) -c $< -o $@
$(LOADER_OBJ_DIR)/v4system/%.o: v4/system/%.c | $(LOADER_OBJ_DIR)
@mkdir -p $(dir $@)
@echo "CC (loader) $<"
@$(LOADER_CC) $(LOADER_CFLAGS) -c $< -o $@
$(KERNEL_OBJ_DIR)/v4system/%.o: v4/system/%.c | $(KERNEL_OBJ_DIR)
@mkdir -p $(dir $@)
@echo "CC (kernel) $<"
@$(CC) $(KERNEL_CFLAGS) -c $< -o $@
$(LOADER_OBJ_DIR)/v4engine/%.o: v4/src/%.c | $(LOADER_OBJ_DIR)
@mkdir -p $(dir $@)
@echo "CC (loader) $<"
@$(LOADER_CC) $(LOADER_CFLAGS) -c $< -o $@
$(KERNEL_OBJ_DIR)/v4engine/%.o: v4/src/%.c | $(KERNEL_OBJ_DIR)
@mkdir -p $(dir $@)
@echo "CC (kernel) $<"
@$(CC) $(KERNEL_CFLAGS) -c $< -o $@
endif
# Compile loader assembly sources
$(LOADER_OBJ_DIR)/%.o: $(KERNEL_SRC)/%.S | $(LOADER_OBJ_DIR)
@mkdir -p $(dir $@)
@@ -1337,6 +1401,7 @@ info:
@echo "Loader output: $(LOADER_EFI)"
@echo "Kernel output: $(KERNEL_ELF)"
@echo "VM integration: $(STARFORTH_ENABLE_VM)"
@echo "StarForth v4: $(STARFORTH_V4)"
@echo "Monolithic: $(MONOLITHIC)"
help:
@@ -0,0 +1,21 @@
/* capsule_blocks.h -- the block format of a .4th capsule payload.
*
* A .4th capsule is text: "Block <num>" header lines, each followed by that
* block's content lines (tools/mkcapsule.c, validate_forth_blocks). This is
* the one place that says what a header line is. It depends on no VM, so
* every loader of capsules can use it: the kernel's and the hosted v4
* system's (docs/v4.0.0/NUCLEUS.md 5.3).
*/
#ifndef STARKERNEL_CAPSULE_BLOCKS_H
#define STARKERNEL_CAPSULE_BLOCKS_H
#include <stdint.h>
/* Is the line that starts at p a "Block <num>" header? If so, returns 1
* with the number in *out_num and the start of the next line in *out_after;
* otherwise returns 0 and changes nothing. `end` is one past the payload's
* last byte. */
int capsule_block_header(const uint8_t *p, const uint8_t *end,
uint32_t *out_num, const uint8_t **out_after);
#endif /* STARKERNEL_CAPSULE_BLOCKS_H */
+13
View File
@@ -0,0 +1,13 @@
/* sk_v4.h -- StarForth v4 on bare metal: one host node at a prompt.
*
* Built only with STARFORTH_V4=1 (Kconfig.kernel).
*/
#ifndef STARKERNEL_V4_SK_V4_H
#define STARKERNEL_V4_SK_V4_H
/* Start one StarForth v4 host node from the capsule image linked into the
* kernel, with its console on the kernel's console, and run it. Does not
* return. */
void sk_v4_run(void);
#endif /* STARKERNEL_V4_SK_V4_H */
+29
View File
@@ -0,0 +1,29 @@
/* capsule_blocks.c -- the block format of a .4th capsule payload.
* See capsule_blocks.h. Nothing here uses the C library or a VM.
*/
#include "starkernel/capsule_blocks.h"
int capsule_block_header(const uint8_t *p, const uint8_t *end,
uint32_t *out_num, const uint8_t **out_after)
{
const uint8_t *q;
uint32_t num = 0;
if ((uint64_t)(end - p) < 7u) return 0;
if (p[0] != 'B' || p[1] != 'l' || p[2] != 'o' || p[3] != 'c' || p[4] != 'k' || p[5] != ' ')
return 0;
q = p + 6;
if (q >= end || *q < '0' || *q > '9') return 0;
while (q < end && *q >= '0' && *q <= '9') {
num = num * 10u + (uint32_t)(*q - '0');
q++;
}
while (q < end && *q != '\n') q++;
if (q < end) q++;
*out_num = num;
*out_after = q;
return 1;
}
+11
View File
@@ -81,6 +81,9 @@ EFI_RUNTIME_SERVICES *g_sk_runtime_services = NULL;
log.h's line length (LOG_MSG_LINE_MAX, 256)
are distinct names */
#include "version.h"
#ifdef STARFORTH_V4
#include "starkernel/v4/sk_v4.h"
#endif
#endif
/* Forward declaration — kernel_main_deep contains everything from heartbeat
@@ -512,6 +515,14 @@ static void kernel_main_deep(BootInfo *boot_info) {
console_println("Kernel initialization complete.");
console_println("Boot successful!\n");
#ifdef STARFORTH_V4
/* StarForth v4 at a single prompt (Kconfig STARFORTH_V4): one host node
* of the F18-derived engine, in place of the v3 VM and everything below.
* It does not return. */
(void)boot_info;
sk_v4_run();
#endif
#ifdef STARFORTH_ENABLE_VM
/* Stadium: boot-time allocation (FABRIC-0.md item 3.2), before any VM
* exists (§6). Soft failure -- nothing downstream consumes the Stadium
+86
View File
@@ -0,0 +1,86 @@
/* sk_v4.c -- StarForth v4 on bare metal: one host node at a prompt.
*
* The node is the golden model of the F18-derived engine (v4/src). It comes
* up as the hosted v4 system does, by v4_boot_run (v4/include/v4/boot.h):
* the nucleus image, then the capsules from the directory baked into this
* kernel, each checked and its parity line printed, then the prompt.
* docs/v4.0.0/NUCLEUS.md. This file is all the kernel adds:
*
* - what the node prints goes to the kernel's console;
* - what is typed is given to the node a line at a time. The node does
* not send back what it reads -- on the mesh that is the console node's
* job -- so the line is edited here: characters are echoed, backspace
* rubs one out, and Enter hands the line over;
* - blocks are kept in memory, and are gone at power-off.
*
* Nothing of the v3 VM is started. docs/v4.0.0/DECOMPOSITION.md.
*/
#include "starkernel/v4/sk_v4.h"
#include "starkernel/console.h"
#include "v4/boot.h"
#define SK_V4_BLOCKS 64u
#define SK_V4_LINE 79u /* QUERY takes 80 characters: 79 and the new-line */
static v4_node sk_v4_node;
static v4_exec_state sk_v4_es;
static v4_heat sk_v4_heat;
static unsigned char sk_v4_disk[SK_V4_BLOCKS * V4_BLOCK_BYTES];
static char sk_v4_line[SK_V4_LINE + 1u];
static unsigned sk_v4_len;
/* One character from the keyboard. A finished line goes to the node. */
static void sk_v4_key(int c)
{
if (c == '\r' || c == '\n') {
console_putc('\n');
sk_v4_line[sk_v4_len++] = '\n';
(void)v4_node_console_feed(&sk_v4_node, sk_v4_line, sk_v4_len);
sk_v4_len = 0;
} else if (c == 8 || c == 127) {
if (sk_v4_len > 0) {
sk_v4_len--;
console_putc(8); console_putc(' '); console_putc(8);
}
} else if (c >= 32 && c < 127 && sk_v4_len < SK_V4_LINE) {
sk_v4_line[sk_v4_len++] = (char)c;
console_putc((char)c);
}
}
static void sk_v4_out(const char *text, unsigned len)
{
unsigned i;
for (i = 0; i < len; i++) console_putc(text[i]);
}
void sk_v4_run(void)
{
const v4_image *im = &v4_capsule_image;
v4_boot boot;
unsigned i;
console_println("StarForth v4: one host node, the F18-derived engine");
boot.n = &sk_v4_node; boot.es = &sk_v4_es; boot.h = &sk_v4_heat; boot.im = im; boot.out = sk_v4_out;
if (!v4_boot_run(&boot, sk_v4_disk, SK_V4_BLOCKS)) {
console_println("StarForth v4: not started");
for (;;) { }
}
for (;;) {
(void)v4_exec_step_word(&sk_v4_node, &sk_v4_es, &sk_v4_heat);
if (sk_v4_node.console_len) {
for (i = 0; i < sk_v4_node.console_len; i++) console_putc((char)sk_v4_node.console[i]);
sk_v4_node.console_len = 0;
}
if (sk_v4_node.stopped) {
console_println("StarForth v4: the node stopped on a fault");
for (;;) { }
}
if (v4_image_waiting(&sk_v4_node, im)) {
int c = console_getc();
if (c >= 0) sk_v4_key(c);
}
}
}
+85
View File
@@ -55,6 +55,91 @@ node_size = $(if $(findstring /test_host_,$(1)),-DV4_NODE_WORDS=$(HOST_WORDS) -D
CAPSULES := $(wildcard $(HERE)/capsule/*.v4)
capsule_dir := -DV4_CAPSULE_DIR='"$(HERE)/capsule"'
# THE NUCLEUS IMAGE. tools/mkimage.c, built for the host node (HOST_WORDS
# and the host stack sizes) at one cell width, assembles the nucleus --
# 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,
# STARFORTH_V4=1) link the 64-bit one. docs/v4.0.0/NUCLEUS.md.
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)
define IMAGE_RULE
$(BINDIR)/mkimage-$(1): $(HERE)/tools/mkimage.c $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/tests/host_map.h $(HERE)/Makefile
@mkdir -p $(BINDIR)
$$(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)/mkimage-$(1) $$@
endef
$(foreach w,$(WIDTHS),$(eval $(call IMAGE_RULE,$(w))))
.PHONY: image
image: $(foreach w,$(WIDTHS),$(BINDIR)/v4_image_$(w).c)
# THE HOSTED SYSTEM: StarForth v4 as a Linux program, a product, for amd64,
# aarch64 and riscv64 -- $(BINDIR)/starforth4-<isa>. It is the engine, the
# 64-bit nucleus image, the capsule directory and the boot (system/boot.c)
# that loads the capsules from it: the same four the kernel links. The
# directory is made by the repository's own tools/mkcapsule.c from
# ../capsules, signed if the key is on this machine (kernel/Makefile,
# 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
# user-mode QEMU with nothing else installed.
ROOT := $(abspath $(HERE)/..)
HOSTED_ISAS := amd64 aarch64 riscv64
CC_amd64 ?= cc
CC_aarch64 ?= aarch64-linux-gnu-gcc
CC_riscv64 ?= riscv64-linux-gnu-gcc
RUN_amd64 ?=
RUN_aarch64 ?= qemu-aarch64
RUN_riscv64 ?= qemu-riscv64
SIGN_KEY ?= /home/rajames/CLionProjects/lithosananke-ca/intermediate/snakeoil-intermediate.key
SIGN_KEY_ARGS = $(if $(wildcard $(SIGN_KEY)),--sign-key $(SIGN_KEY),)
CRYPTO_SRCS := $(addprefix $(ROOT)/kernel/src/crypto/,ed25519.c fe25519.c scalar25519.c sha512.c)
MKCAPSULE_SRCS := $(ROOT)/tools/mkcapsule.c $(ROOT)/tools/pkcs8_ed25519.c $(CRYPTO_SRCS)
CAPSULE_FILES := $(shell find $(ROOT)/capsules -type f ! -name '.*' 2>/dev/null)
CAPSULE_DIR_C := $(BINDIR)/capsule_generated.c
# the kernel's capsule code: find, check the hash, verify the signature,
# split a payload into blocks
SYSTEM_SRCS := $(HERE)/system/boot.c \
$(addprefix $(ROOT)/kernel/src/capsule/,capsule_find.c capsule_validate.c capsule_sig.c capsule_blocks.c) \
$(ROOT)/kernel/src/hash/xxhash64.c $(ROOT)/kernel/src/crypto/x509_ed25519.c $(CRYPTO_SRCS)
# The kernel's sources are held to the kernel's warnings, not this model's.
SYSTEM_CFLAGS := $(CSTD) -Wall -Wextra $(OPT) -I$(HERE)/include -I$(ROOT)/kernel/include -I$(ROOT)/v3/include \
-DV4_CELL_BITS=64 $(HOST_DEFS)
$(BINDIR)/mkcapsule: $(MKCAPSULE_SRCS)
@mkdir -p $(BINDIR)
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)
$(BINDIR)/mkcapsule $(SIGN_KEY_ARGS) $(ROOT)/capsules $@
define HOSTED_RULE
$(BINDIR)/starforth4-$(1): $(HERE)/tools/hosted.c $(BINDIR)/v4_image_64.c $(CAPSULE_DIR_C) $$(ENGINE_SRCS) $$(SYSTEM_SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/Makefile
$$(CC_$(1)) $$(CFLAGS) -D_POSIX_C_SOURCE=200809L -I$(HERE)/include -DV4_CELL_BITS=64 $(HOST_DEFS) -c $(HERE)/tools/hosted.c -o $(BINDIR)/hosted-$(1).o
$$(CC_$(1)) $$(SYSTEM_CFLAGS) -static $(BINDIR)/hosted-$(1).o $(BINDIR)/v4_image_64.c $(CAPSULE_DIR_C) $$(ENGINE_SRCS) $$(SYSTEM_SRCS) -o $$@
$(BINDIR)/boot-$(1).txt: $(BINDIR)/starforth4-$(1)
@echo " [hosted $(1)]"
@$$(RUN_$(1)) $(BINDIR)/starforth4-$(1) < /dev/null > $$@ || { cat $$@; rm -f $$@; exit 1; }
@cat $$@
endef
$(foreach i,$(HOSTED_ISAS),$(eval $(call HOSTED_RULE,$(i))))
# `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 and the prompt: the same hashes on every ISA.
.PHONY: hosted hosted-check
hosted: $(foreach i,$(HOSTED_ISAS),$(BINDIR)/starforth4-$(i))
hosted-check: $(foreach i,$(HOSTED_ISAS),$(BINDIR)/boot-$(i).txt)
@grep -q '^PARITY:OK$$' $(BINDIR)/boot-amd64.txt || { echo "hosted-check: no PARITY:OK"; exit 1; }
@cmp $(BINDIR)/boot-amd64.txt $(BINDIR)/boot-aarch64.txt
@cmp $(BINDIR)/boot-amd64.txt $(BINDIR)/boot-riscv64.txt
@echo "hosted-check: amd64, aarch64 and riscv64 boot identically"
.PHONY: all test sanitize clean $(addprefix test-,$(WIDTHS))
all: test
+39 -2
View File
@@ -20,5 +20,42 @@ input a test feeds) standing in for the console node until the mesh exists. The
onto a node either opcode by opcode (`v4/include/v4/asm.h`) or as text in the notation `DECOMPOSITION.md`
uses (`v4/include/v4/text.h`). `make -C v4 test` builds
and runs the tests at both cell widths; `make -C v4 sanitize` repeats them
under ASan and UBSan. There is no compiler capsule, no POST and no K
measurement yet.
under ASan and UBSan. There is no POST and no K measurement yet.
## The system: nucleus, capsules, prompt
`docs/v4.0.0/NUCLEUS.md` is the design. A v4 system is four things:
| Part | Where | What it is |
|---|---|---|
| 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 |
| 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 |
Two products link the same four and differ only in the console:
- **Hosted Linux**, `v4/tools/hosted.c`: `make -C v4 hosted` builds
`v4/build/starforth4-amd64`, `-aarch64` and `-riscv64`, static, 64-bit cells.
- **Bare metal**, `kernel/src/v4/sk_v4.c`: `make -f kernel/Makefile ARCH=<arch> STARFORTH_V4=1`.
A boot prints:
```
PARITY:V4_NUCLEUS words=292 image_hash=0x...
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x... capsule_hash=0x... dict_hash=0x...
PARITY:OK
ok>
```
Every build of one commit prints the same hashes. `make -C v4 hosted-check`
boots the three hosted binaries (the two foreign ones under user-mode QEMU)
and fails unless their output is identical and ends in `PARITY:OK`.
A capsule line the node does not answer ` ok` to ends the boot, naming the
capsule, block and line, with `PARITY:FAIL` and `POST: FAILED`.
State, 2026-10-05: capsule loading works hosted on all three ISAs, and the
kernel compiles with `STARFORTH_V4=1` on all three. `forth79.4th` holds no
definitions yet: all 292 words are still in the nucleus. There is no POST
yet, and no bare-metal boot of v4 has been run.
+50
View File
@@ -0,0 +1,50 @@
/* boot.h -- how a StarForth v4 system comes up: the nucleus, then its
* capsules, then the prompt. docs/v4.0.0/NUCLEUS.md.
*
* The hosted binary (tools/hosted.c) and the bare-metal kernel
* (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:
*
* PARITY:V4_NUCLEUS words=N image_hash=0x...
* PARITY:V4_CAPSULE name=... capsule_id=0x... capsule_hash=0x... dict_hash=0x...
* PARITY:OK
* ok>
*
* or, if anything is wrong, what was wrong and then
*
* PARITY:FAIL
* POST: FAILED
*
* A capsule is found by name in the directory tools/mkcapsule.c baked into
* the binary, its hash is recomputed, its signature is checked -- one that
* does not verify refuses the capsule, a missing one is only reported, as
* for v3's capsules -- and its blocks are given to the node a line at a
* time as if typed. The node must answer " ok" to every line; the first
* line it does not accept ends the boot. What a line prints is shown.
*
* The dictionary hash is FNV-1a over the node's memory below HERE, a cell at
* a time, low byte first, then LATEST. It does not depend on the machine:
* every build of one commit, with one cell width, prints the same hashes.
*/
#ifndef V4_BOOT_H
#define V4_BOOT_H
#include "v4/image.h"
typedef struct {
v4_node *n;
v4_exec_state *es;
v4_heat *h;
const v4_image *im; /* the nucleus */
void (*out)(const char *text, unsigned len); /* the console */
} v4_boot;
/* Start the node from the nucleus image, with `disk` as its block storage
* (see v4_image_boot), and load the capsules. Returns 1 with the node
* waiting at its prompt, the prompt printed; or 0, the failure printed. */
int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks);
/* The dictionary hash of a node started from `im`. */
uint64_t v4_boot_dict_hash(const v4_node *n, const v4_image *im);
#endif /* V4_BOOT_H */
+71
View File
@@ -0,0 +1,71 @@
/* image.h -- a capsule image: the host node's memory with the vocabulary in
* it, and what a loader needs to know to start it.
*
* The compiler capsule is text (capsule/ *.v4) and FORTH source (capsule/
* *.fth). tools/mkimage.c assembles the one and has a node compile the
* 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;
* v4_image_boot refuses an image the engine was not built for.
*/
#ifndef V4_IMAGE_H
#define V4_IMAGE_H
#include "v4/exec.h"
/* one word of memory that is not zero */
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 */
unsigned cell_bits, node_words, data_ring, ret_ring;
/* where to start, and where a fault goes */
v4_cell entry; /* QUIT */
v4_cell fault_table; /* (FAULTS) */
/* KEY's code: a node whose P is in key_start .. key_end - 1 with no
* character pending is waiting for one */
v4_cell key_start, key_end;
/* the memory-mapped registers (DECOMPOSITION.md section 7; D-4 leaves
* the map to the loader, and this is the map the image was built with) */
v4_cell console_tx, console_rx, console_status;
v4_cell dstack_reg, rstack_reg;
v4_cell node_error;
v4_cell storage_reg;
/* the dictionary's two variables: DP holds HERE, a byte address, and
* 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;
} v4_image;
/* The image built from v4/capsule (the generated file defines it). */
extern const v4_image v4_capsule_image;
/* Reset `n`, load the image into it, attach its registers -- with `disk`,
* `blocks` blocks of 1024 bytes, as its block storage, or none if `disk` is
* 0 -- and leave it about to execute its first instruction. Returns 1, or 0
* if the image is not for this build of the engine (nothing is then done).
*
* After it, the caller runs the node: v4_exec_step_word again and again,
* taking what the node prints from n->console (and setting n->console_len
* back to 0) and giving it what is typed with v4_node_console_feed. */
int v4_image_boot(v4_node *n, v4_exec_state *es, v4_heat *h, const v4_image *im,
unsigned char *disk, unsigned blocks);
/* 1 if the node is waiting for a character: it is inside KEY and none is
* pending. */
int v4_image_waiting(const v4_node *n, const v4_image *im);
#endif /* V4_IMAGE_H */
+6 -4
View File
@@ -1,12 +1,14 @@
/* heat.c -- heat and anti-clock. See heat.h. */
#include "v4/heat.h"
#include <string.h>
void v4_heat_reset(v4_heat *h)
{
memset(h->op, 0, sizeof(h->op));
memset(h->call, 0, sizeof(h->call));
memset(h->call_freeze_mask, 0, sizeof(h->call_freeze_mask));
/* Loops, not memset: the engine uses nothing from the C library, so that
* the bare-metal kernel can link it as it is. */
unsigned i;
for (i = 0; i < sizeof h->op / sizeof h->op[0]; i++) h->op[i] = 0;
for (i = 0; i < sizeof h->call / sizeof h->call[0]; i++) h->call[i] = 0;
for (i = 0; i < sizeof h->call_freeze_mask / sizeof h->call_freeze_mask[0]; i++) h->call_freeze_mask[i] = 0;
}
void v4_heat_on_retire(v4_heat *h, unsigned op, v4_uheat_t *anticlock)
+36
View File
@@ -0,0 +1,36 @@
/* image.c -- start a node from a capsule image. See image.h.
*
* Nothing here uses the C library: the bare-metal kernel links this file.
*/
#include "v4/image.h"
int v4_image_boot(v4_node *n, v4_exec_state *es, v4_heat *h, const v4_image *im,
unsigned char *disk, unsigned blocks)
{
unsigned i;
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)
return 0;
v4_node_reset(n);
v4_exec_reset(es);
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_input_attach(n, im->console_rx, im->console_status);
v4_node_stack_regs_attach(n, im->dstack_reg, im->rstack_reg);
v4_node_error_attach(n, im->node_error);
v4_node_fault_attach(n, im->fault_table);
if (disk && blocks) v4_node_storage_attach(n, im->storage_reg, disk, blocks);
n->p = im->entry;
return 1;
}
int v4_image_waiting(const v4_node *n, const v4_image *im)
{
return n->input_pos == n->input_len && n->p >= im->key_start && n->p < im->key_end;
}
+253
View File
@@ -0,0 +1,253 @@
/* boot.c -- the nucleus, then its capsules, then the prompt. See boot.h.
*
* Nothing here uses the C library: the bare-metal kernel links this file.
* It is not part of the engine (v4/src): it needs the capsule directory,
* which only a whole system has.
*/
#include "v4/boot.h"
#include "starkernel/capsule.h"
#include "starkernel/capsule_generated.h"
#include "starkernel/capsule_sig.h"
#include "starkernel/capsule_blocks.h"
/* The capsules, in the order they are loaded. */
static const char *const boot_capsules[] = { "v4:forth79.4th" };
#define LINE_MAX 80u /* QUERY takes 80 characters, the new-line among them */
#define STEP_LIMIT 4000000000ULL /* instruction words one line may take */
/* ---- printing ------------------------------------------------------------ */
static void say(const v4_boot *b, const char *s)
{
unsigned len = 0;
while (s[len]) len++;
b->out(s, len);
}
static void say_dec(const v4_boot *b, uint32_t v)
{
char buf[10];
unsigned i = sizeof buf;
do { buf[--i] = (char)('0' + v % 10u); v /= 10u; } while (v);
b->out(buf + i, (unsigned)sizeof buf - i);
}
static void say_hex(const v4_boot *b, uint64_t v)
{
char buf[18];
unsigned i;
buf[0] = '0'; buf[1] = 'x';
for (i = 0; i < 16; i++) buf[2 + i] = "0123456789abcdef"[(v >> (60 - 4 * i)) & 15u];
b->out(buf, sizeof buf);
}
/* ---- hashing: FNV-1a, 64 bits, as kernel/src/vm/parity.c ----------------- */
#define FNV_OFFSET 0xcbf29ce484222325ULL
#define FNV_PRIME 0x00000100000001b3ULL
static uint64_t hash_cell(uint64_t h, v4_cell c)
{
v4_ucell u = (v4_ucell)c;
unsigned i;
for (i = 0; i < V4_CELL_BITS / 8; i++) {
h ^= (uint64_t)((u >> (8 * i)) & 0xffu);
h *= FNV_PRIME;
}
return h;
}
uint64_t v4_boot_dict_hash(const v4_node *n, const v4_image *im)
{
uint64_t h = FNV_OFFSET;
v4_cell here = (n->mem[im->dp] + 3) / 4, k;
if (here < 0) here = 0;
if (here > (v4_cell)V4_NODE_WORDS) here = (v4_cell)V4_NODE_WORDS;
for (k = 0; k < here; k++) h = hash_cell(h, n->mem[k]);
return hash_cell(h, n->mem[im->latest]);
}
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
* cell before its code */
static uint32_t word_count(const v4_node *n, const v4_image *im)
{
v4_cell xt = n->mem[im->latest];
uint32_t count = 0;
while (xt > 0 && xt < (v4_cell)V4_NODE_WORDS && count < (uint32_t)V4_NODE_WORDS) {
count++;
xt = n->mem[xt - 1];
}
return count;
}
/* ---- running the node ---------------------------------------------------- */
/* The node ends every line it has taken with " ok" and the next prompt.
* Those eight characters are held back from the console, so that what is
* shown is only what the line itself printed. */
static const char accepted[8] = { ' ', 'o', 'k', '\n', 'o', 'k', '>', ' ' };
typedef struct {
char held[8];
unsigned len;
} tail;
static void tail_put(const v4_boot *b, tail *t, int show, char c)
{
unsigned i;
if (t->len == sizeof t->held) {
if (show) b->out(t->held, 1);
for (i = 1; i < sizeof t->held; i++) t->held[i - 1] = t->held[i];
t->len--;
}
t->held[t->len++] = c;
}
/* Run until the node waits for a character. Returns 1 if what it printed
* ended with " ok" and the prompt; 0 if not -- the rest is then shown too --
* or if the node stopped or never came back. */
static int run_to_prompt(const v4_boot *b, int show)
{
v4_node *n = b->n;
uint64_t steps = 0;
unsigned i;
tail t;
t.len = 0;
for (;;) {
(void)v4_exec_step_word(n, b->es, b->h);
if (n->console_len) {
for (i = 0; i < n->console_len; i++) tail_put(b, &t, show, (char)n->console[i]);
n->console_len = 0;
}
if (n->stopped) { say(b, "\nV4: the node stopped on a fault\n"); return 0; }
if (v4_image_waiting(n, b->im)) break;
if (++steps > STEP_LIMIT) { say(b, "\nV4: the node did not come back to its prompt\n"); return 0; }
}
if (t.len == sizeof t.held) {
for (i = 0; i < sizeof t.held && t.held[i] == accepted[i]; i++) { }
if (i == sizeof t.held) return 1;
}
if (show) b->out(t.held, t.len);
return 0;
}
/* ---- one capsule --------------------------------------------------------- */
static void say_where(const v4_boot *b, const char *name, uint32_t block, uint32_t line)
{
say(b, "\nV4: capsule "); say(b, name);
say(b, " block "); say_dec(b, block);
say(b, " line "); say_dec(b, line);
}
static int load_capsule(const v4_boot *b, const char *name)
{
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, *end;
CapsuleValidateResult vr;
CapsuleSigResult sr;
uint32_t block = 0, line = 0;
int in_block = 0;
cap = capsule_find_by_name(dir, descs, names, name);
if (!cap) { say(b, "V4: capsule "); say(b, name); say(b, " 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 "); say(b, name); say(b, ": "); 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 "); say(b, name); say(b, " 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) { say(b, "V4: capsule "); say(b, name); say(b, " has no payload\n"); return 0; }
end = p + cap->length;
while (p < end) {
const uint8_t *after, *nl;
uint32_t num;
unsigned len, i;
char text[LINE_MAX];
if (capsule_block_header(p, end, &num, &after)) {
block = num; line = 0; in_block = 1; p = after;
continue;
}
for (nl = p; nl < end && *nl != '\n'; nl++) { }
len = (unsigned)(nl - p);
if (len && p[len - 1] == '\r') len--;
if (in_block) {
line++;
if (len >= LINE_MAX) { say_where(b, name, block, line); say(b, " is too long\n"); return 0; }
if (len) {
for (i = 0; i < len; i++) text[i] = (char)p[i];
text[len] = '\n';
if (v4_node_console_feed(b->n, text, len + 1u) != len + 1u) {
say_where(b, name, block, line); say(b, ": the node's input is full\n");
return 0;
}
if (!run_to_prompt(b, 1)) {
say_where(b, name, block, line); say(b, " was not accepted: ");
b->out(text, len); say(b, "\n");
return 0;
}
}
}
p = (nl < end) ? nl + 1 : end;
}
say(b, "PARITY:V4_CAPSULE name="); say(b, name);
say(b, " capsule_id="); say_hex(b, cap->capsule_id);
say(b, " capsule_hash="); say_hex(b, cap->content_hash);
say(b, " dict_hash="); say_hex(b, v4_boot_dict_hash(b->n, b->im));
say(b, "\n");
return 1;
}
/* ---- the whole boot ------------------------------------------------------ */
int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks)
{
unsigned i;
if (!v4_image_boot(b->n, b->es, b->h, b->im, disk, blocks)) {
say(b, "V4: the nucleus image is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n");
return 0;
}
say(b, "PARITY:V4_NUCLEUS words="); say_dec(b, word_count(b->n, b->im));
say(b, " image_hash="); say_hex(b, image_hash(b->im));
say(b, "\n");
/* the node's own first prompt is not shown: the boot prints one at the end */
(void)run_to_prompt(b, 0);
if (b->n->stopped) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
for (i = 0; i < sizeof boot_capsules / sizeof boot_capsules[0]; i++)
if (!load_capsule(b, boot_capsules[i])) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
say(b, "PARITY:OK\nok> ");
return 1;
}
+48
View File
@@ -0,0 +1,48 @@
/* hosted.c -- StarForth v4 as a Linux program: the nucleus image on the
* golden model, its capsules loaded as the system comes up (v4/include/v4/
* boot.h, the same boot the bare-metal kernel runs), the console on stdin
* and stdout.
*
* It runs until its input ends. Blocks are kept in memory and are gone
* when it stops.
*/
#include "v4/boot.h"
#include <stdio.h>
#include <unistd.h>
#define BLOCKS 64u
static v4_node n;
static v4_exec_state es;
static v4_heat h;
static unsigned char disk[BLOCKS * V4_BLOCK_BYTES];
static void console_out(const char *text, unsigned len)
{
(void)fwrite(text, 1, len, stdout);
}
int main(void)
{
const v4_image *im = &v4_capsule_image;
unsigned char buf[256];
v4_boot boot;
boot.n = &n; boot.es = &es; boot.h = &h; boot.im = im; boot.out = console_out;
if (!v4_boot_run(&boot, disk, BLOCKS)) { fflush(stdout); return 1; }
for (;;) {
(void)v4_exec_step_word(&n, &es, &h);
if (n.console_len) {
(void)fwrite(n.console, 1, n.console_len, stdout);
n.console_len = 0;
}
if (n.stopped) { fflush(stdout); fprintf(stderr, "starforth4: the node stopped on a fault\n"); return 1; }
if (v4_image_waiting(&n, im)) {
ssize_t got;
fflush(stdout);
got = read(0, buf, sizeof buf);
if (got <= 0) { putchar('\n'); return 0; }
(void)v4_node_console_feed(&n, buf, (unsigned)got);
}
}
}
+134
View File
@@ -0,0 +1,134 @@
/* mkimage.c -- build the capsule image (include/v4/image.h).
*
* mkimage OUTPUT.c
*
* 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
* tests/host_map.h and starts the node at its prompt. Then it writes every
* word of the node's memory that is not zero, and the addresses a loader
* needs, as a C file. No FORTH source is compiled here: what is not in the
* nucleus is loaded from capsules when the system boots (v4/system/boot.c).
*
* 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
* for.
*/
#include "v4/image.h"
#include "v4/text.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../tests/host_map.h"
static v4_node n;
static v4_exec_state es;
static v4_heat h;
static v4_text tx;
static v4_cell w_key, w_key_end;
static unsigned char disk[4 * V4_BLOCK_BYTES];
static void die(const char *what, const char *detail)
{
fprintf(stderr, "mkimage: %s%s%s\n", what, detail ? ": " : "", detail ? detail : "");
exit(1);
}
/* run until the node has taken all its input and is waiting in KEY */
static void run_until_waiting(void)
{
long steps = 0;
unsigned idle = 0;
while (idle < 64) {
if (++steps > 200000000L) die("the node did not come back to its prompt", NULL);
if (n.stopped) die("the node stopped on a fault", NULL);
(void)v4_exec_step_word(&n, &es, &h);
if (n.input_pos == n.input_len && n.p >= w_key && n.p < w_key_end) idle++; else idle = 0;
}
}
/* every entry from xt back: no access control fields set */
static void clear_acl(v4_cell xt)
{
for (; xt != 0; xt = n.mem[xt - 1]) n.mem[xt - 3] &= 31;
}
int main(int argc, char **argv)
{
static const char *const files[] = { "core.v4", "input.v4", "dict.v4", "codegen.v4", "compile.v4", "quit.v4", "forth.v4",
"numout.v4", "words.v4", "system.v4", "qmath.v4", "blocks.v4", "log.v4", "acl.v4" };
FILE *out;
v4_cell k, entry, faults, voc;
unsigned count = 0;
if (argc != 2) die("usage: mkimage OUTPUT.c", 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_here(&tx) >= DICT_W) die("the capsule's code runs into the dictionary space", NULL);
entry = v4_text_word(&tx, "QUIT");
faults = v4_text_word(&tx, "(FAULTS)");
w_key = v4_text_word(&tx, "KEY");
w_key_end = v4_text_word(&tx, "CR");
/* the variables, as at switch-on */
n.mem[DP] = DICT_W * 4;
n.mem[LATEST] = v4_text_latest(&tx);
n.mem[STATE] = 0;
n.mem[CFP] = CFS_W;
n.mem[BASE] = 10;
n.mem[FENCE] = DICT_W;
n.mem[LOG_LEVEL] = 2;
n.mem[CONTEXT] = LATEST;
n.mem[CURRENT] = LATEST;
n.mem[SRC] = TIB;
v4_exec_reset(&es);
v4_heat_reset(&h);
v4_node_console_attach(&n, CONSOLE_TX);
v4_node_console_input_attach(&n, CONSOLE_RX, CONSOLE_ST);
v4_node_error_attach(&n, NODE_ERROR);
v4_node_fault_attach(&n, faults);
v4_node_storage_attach(&n, STORAGE_REG, disk, 4);
n.p = entry;
run_until_waiting();
/* The nucleus is the system as COLD finds it: COLD comes back to here,
* and FORGET will not go below it. What the capsules add at boot
* (v4/system/boot.c) is above it. */
n.mem[BOOT_CELLS] = n.mem[DP];
n.mem[BOOT_CELLS + 1] = n.mem[LATEST];
n.mem[FENCE] = (n.mem[DP] + 3) / 4;
/* and nothing of the building is left behind: the terminal is the input,
* no block is in a buffer, no word has an access control field set */
n.mem[NODE_ERROR] = 0;
n.mem[BLK] = 0; n.mem[SCR] = 0; n.mem[SRC] = TIB; n.mem[SRC_HOOK] = 0;
for (k = BVARS; k < BVARS + 6; k++) n.mem[k] = 0;
for (k = STORAGE_REG; k < STORAGE_REG + 4; k++) n.mem[k] = 0;
for (k = BUF0_W; k < BUF0_W + 2 * 256; k++) n.mem[k] = 0;
for (k = TIB_W; k < TIB_W + 260; k++) n.mem[k] = 0;
for (k = WBUF_W; k < WBUF_W + WBUF_CELLS; k++) n.mem[k] = 0;
for (k = PAD_W; k < PAD_W + 21; k++) n.mem[k] = 0;
n.mem[TO_IN] = 0; n.mem[SPAN] = 0;
clear_acl(n.mem[LATEST]);
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);
out = fopen(argv[1], "w");
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, "static const v4_image_cell cells[] = {\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);
fprintf(out, " %ld, %ld,\n %ld, %ld,\n", (long)entry, (long)faults, (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);
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;
}