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:
co-authored by
Claude Opus 5.5
parent
903321e548
commit
294e69463a
@@ -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
|
||||
|
||||
@@ -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.*
|
||||
|
||||
@@ -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. )
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user