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
+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