diff --git a/Kconfig.kernel b/Kconfig.kernel index 9cf88534..25c5017a 100644 --- a/Kconfig.kernel +++ b/Kconfig.kernel @@ -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 diff --git a/capsules/BLOCK_MAP.md b/capsules/BLOCK_MAP.md index d2477242..2ab3a33f 100644 --- a/capsules/BLOCK_MAP.md +++ b/capsules/BLOCK_MAP.md @@ -1,5 +1,5 @@ # Capsule Block Manifest — Auto-generated - + @@ -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 ` to refresh.* +*37 capsule(s) scanned. Re-run `mkcapsule --manifest ` to refresh.* diff --git a/capsules/v4/forth79.4th b/capsules/v4/forth79.4th new file mode 100644 index 00000000..47be6dff --- /dev/null +++ b/capsules/v4/forth79.4th @@ -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. ) diff --git a/kernel/Makefile b/kernel/Makefile index 2ea15960..17416ec2 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -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: diff --git a/kernel/include/starkernel/capsule_blocks.h b/kernel/include/starkernel/capsule_blocks.h new file mode 100644 index 00000000..b3b5645d --- /dev/null +++ b/kernel/include/starkernel/capsule_blocks.h @@ -0,0 +1,21 @@ +/* capsule_blocks.h -- the block format of a .4th capsule payload. + * + * A .4th capsule is text: "Block " 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 + +/* Is the line that starts at p a "Block " 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 */ diff --git a/kernel/include/starkernel/v4/sk_v4.h b/kernel/include/starkernel/v4/sk_v4.h new file mode 100644 index 00000000..26697cd2 --- /dev/null +++ b/kernel/include/starkernel/v4/sk_v4.h @@ -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 */ diff --git a/kernel/src/capsule/capsule_blocks.c b/kernel/src/capsule/capsule_blocks.c new file mode 100644 index 00000000..2a8c2448 --- /dev/null +++ b/kernel/src/capsule/capsule_blocks.c @@ -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; +} diff --git a/kernel/src/kernel_main.c b/kernel/src/kernel_main.c index 4a4329e2..fcd390a0 100644 --- a/kernel/src/kernel_main.c +++ b/kernel/src/kernel_main.c @@ -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 diff --git a/kernel/src/v4/sk_v4.c b/kernel/src/v4/sk_v4.c new file mode 100644 index 00000000..61ff98df --- /dev/null +++ b/kernel/src/v4/sk_v4.c @@ -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); + } + } +} diff --git a/v4/Makefile b/v4/Makefile index 697b2bba..c5b983fa 100644 --- a/v4/Makefile +++ b/v4/Makefile @@ -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_.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-. 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 diff --git a/v4/README.md b/v4/README.md index ebcf0c98..224643e0 100644 --- a/v4/README.md +++ b/v4/README.md @@ -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= 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. diff --git a/v4/include/v4/boot.h b/v4/include/v4/boot.h new file mode 100644 index 00000000..b925c5b0 --- /dev/null +++ b/v4/include/v4/boot.h @@ -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 */ diff --git a/v4/include/v4/image.h b/v4/include/v4/image.h new file mode 100644 index 00000000..4e548d8e --- /dev/null +++ b/v4/include/v4/image.h @@ -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 */ diff --git a/v4/src/heat.c b/v4/src/heat.c index 8d80b1ba..482ddc20 100644 --- a/v4/src/heat.c +++ b/v4/src/heat.c @@ -1,12 +1,14 @@ /* heat.c -- heat and anti-clock. See heat.h. */ #include "v4/heat.h" -#include 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) diff --git a/v4/src/image.c b/v4/src/image.c new file mode 100644 index 00000000..36f2578f --- /dev/null +++ b/v4/src/image.c @@ -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; +} diff --git a/v4/system/boot.c b/v4/system/boot.c new file mode 100644 index 00000000..e3e0ddf1 --- /dev/null +++ b/v4/system/boot.c @@ -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; +} diff --git a/v4/tools/hosted.c b/v4/tools/hosted.c new file mode 100644 index 00000000..f41ca4f8 --- /dev/null +++ b/v4/tools/hosted.c @@ -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 +#include + +#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); + } + } +} diff --git a/v4/tools/mkimage.c b/v4/tools/mkimage.c new file mode 100644 index 00000000..fe3b4dad --- /dev/null +++ b/v4/tools/mkimage.c @@ -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 +#include +#include + +#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; +}