Compare commits
41
Commits
v2.1.0
...
2e4d1330ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e4d1330ea | ||
|
|
f030edcd08 | ||
|
|
13ec4b1b6c | ||
|
|
8235078b3b | ||
|
|
1ce705783c | ||
|
|
e117333a87 | ||
|
|
a2a4a85d38 | ||
|
|
f4bfe7de02 | ||
|
|
a2d21d5cda | ||
|
|
29cf2688d2 | ||
|
|
9e03bdab19 | ||
|
|
8f37e15f8a | ||
|
|
54e1cdafb1 | ||
|
|
ab3b708540 | ||
|
|
d0accdbb88 | ||
|
|
167fdc3973 | ||
|
|
981f4180ce | ||
|
|
d948e0c1a3 | ||
|
|
4f493fa317 | ||
|
|
5ac52f19d6 | ||
|
|
6a371b850e | ||
|
|
092036c97a | ||
|
|
93d09840da | ||
|
|
3b9d1e4337 | ||
|
|
41f59afeb3 | ||
|
|
9a391eb0ad | ||
|
|
067f317c47 | ||
|
|
1f98034311 | ||
|
|
a8b70e88d3 | ||
|
|
f238f02afa | ||
|
|
3c709c115b | ||
|
|
357cb5b4ac | ||
|
|
2fcc468ecb | ||
|
|
6302dcb50e | ||
|
|
26c1117ccd | ||
|
|
b7d9ed5425 | ||
|
|
81049da268 | ||
|
|
089ab2160e | ||
|
|
2406158668 | ||
|
|
2008c4596a | ||
|
|
7306872848 |
+1
-1
@@ -14,7 +14,7 @@ Checks: >
|
|||||||
|
|
||||||
WarningsAsErrors: [ ]
|
WarningsAsErrors: [ ]
|
||||||
|
|
||||||
HeaderFilterRegex: '^src/.*|^include/.*'
|
HeaderFilterRegex: '^v3/(src|include)/.*|^kernel/(src|include)/.*'
|
||||||
|
|
||||||
AnalyzeTemporaryDtors: false
|
AnalyzeTemporaryDtors: false
|
||||||
FormatStyle: none
|
FormatStyle: none
|
||||||
|
|||||||
+28
-28
@@ -6,8 +6,8 @@
|
|||||||
# clang-tidy will ALWAYS false-positive them. Always.
|
# clang-tidy will ALWAYS false-positive them. Always.
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
src/platform/**
|
v3/src/platform/**
|
||||||
src/platform/*
|
v3/src/platform/*
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# Skip test infrastructure
|
# Skip test infrastructure
|
||||||
@@ -15,8 +15,8 @@ src/platform/*
|
|||||||
# and stress edge conditions clang-tidy will never grok.
|
# and stress edge conditions clang-tidy will never grok.
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
src/test_runner/**
|
v3/src/test_runner/**
|
||||||
src/test_runner/*
|
v3/src/test_runner/*
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# Skip word definitions generated or "mechanically patterned"
|
# Skip word definitions generated or "mechanically patterned"
|
||||||
@@ -24,30 +24,30 @@ src/test_runner/*
|
|||||||
# clang-tidy gives ZERO useful feedback on them.
|
# clang-tidy gives ZERO useful feedback on them.
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
src/word_source/string_words.c
|
v3/src/word_source/string_words.c
|
||||||
src/word_source/block_words.c
|
v3/src/word_source/block_words.c
|
||||||
src/word_source/arithmetic_words.c
|
v3/src/word_source/arithmetic_words.c
|
||||||
src/word_source/mixed_arithmetic_words.c
|
v3/src/word_source/mixed_arithmetic_words.c
|
||||||
src/word_source/return_stack_words.c
|
v3/src/word_source/return_stack_words.c
|
||||||
src/word_source/defining_words.c
|
v3/src/word_source/defining_words.c
|
||||||
src/word_source/stack_words.c
|
v3/src/word_source/stack_words.c
|
||||||
src/word_source/logical_words.c
|
v3/src/word_source/logical_words.c
|
||||||
src/word_source/memory_words.c
|
v3/src/word_source/memory_words.c
|
||||||
src/word_source/io_words.c
|
v3/src/word_source/io_words.c
|
||||||
src/word_source/dictionary_words.c
|
v3/src/word_source/dictionary_words.c
|
||||||
src/word_source/dictionary_manipulation_words.c
|
v3/src/word_source/dictionary_manipulation_words.c
|
||||||
src/word_source/vocabulary_words.c
|
v3/src/word_source/vocabulary_words.c
|
||||||
src/word_source/system_words.c
|
v3/src/word_source/system_words.c
|
||||||
src/word_source/starforth_words.c
|
v3/src/word_source/starforth_words.c
|
||||||
src/word_source/editor_words.c
|
v3/src/word_source/editor_words.c
|
||||||
src/word_source/format_words.c
|
v3/src/word_source/format_words.c
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# Skip headers belonging to the word system
|
# Skip headers belonging to the word system
|
||||||
# Same reason: no signal, infinite false positives.
|
# Same reason: no signal, infinite false positives.
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
src/word_source/include/*
|
v3/src/word_source/include/*
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# Skip anything that implements performance knobs,
|
# Skip anything that implements performance knobs,
|
||||||
@@ -55,15 +55,15 @@ src/word_source/include/*
|
|||||||
# These are meant to be weird, and tidy can't reason about them.
|
# These are meant to be weird, and tidy can't reason about them.
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
src/stack_management.c
|
v3/src/stack_management.c
|
||||||
src/profiler.c
|
v3/src/profiler.c
|
||||||
src/vm_debug.c
|
v3/src/vm_debug.c
|
||||||
src/physics_runtime.c
|
v3/src/physics_runtime.c
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# Skip integration + stress torture-tests.
|
# Skip integration + stress torture-tests.
|
||||||
# These *intentionally* do UB-like bullshit.
|
# These *intentionally* do UB-like bullshit.
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
src/test_runner/modules/*stress*
|
v3/src/test_runner/modules/*stress*
|
||||||
src/test_runner/modules/*integration*
|
v3/src/test_runner/modules/*integration*
|
||||||
|
|||||||
+20
-20
@@ -224,43 +224,43 @@ changes with any new C word registration).
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build kernel for a given architecture (amd64 default)
|
# Build kernel for a given architecture (amd64 default)
|
||||||
make -f Makefile.starkernel ARCH=amd64
|
make -f kernel/Makefile ARCH=amd64
|
||||||
make -f Makefile.starkernel ARCH=aarch64
|
make -f kernel/Makefile ARCH=aarch64
|
||||||
make -f Makefile.starkernel ARCH=riscv64
|
make -f kernel/Makefile ARCH=riscv64
|
||||||
|
|
||||||
# Run in QEMU with OVMF
|
# Run in QEMU with OVMF
|
||||||
make -f Makefile.starkernel qemu
|
make -f kernel/Makefile qemu
|
||||||
make -f Makefile.starkernel ARCH=aarch64 qemu
|
make -f kernel/Makefile ARCH=aarch64 qemu
|
||||||
make -f Makefile.starkernel ARCH=riscv64 qemu
|
make -f kernel/Makefile ARCH=riscv64 qemu
|
||||||
|
|
||||||
# Clean
|
# Clean
|
||||||
make -f Makefile.starkernel clean
|
make -f kernel/Makefile clean
|
||||||
```
|
```
|
||||||
|
|
||||||
Output: `build/<arch>/kernel/starkernel_loader.efi` + `build/<arch>/kernel/starkernel_kernel.elf`.
|
Output: `build/<arch>/kernel/starkernel_loader.efi` + `build/<arch>/kernel/starkernel_kernel.elf`.
|
||||||
|
|
||||||
Two independently tracked version strings flow into the generated `include/version.h`:
|
Two independently tracked version strings flow into the generated `include/version.h`:
|
||||||
`VERSION` (`Makefile.starkernel` — the embedded StarForth engine version, currently `3.1.0`;
|
`VERSION` (`kernel/Makefile` — the embedded StarForth engine version, currently `3.1.0`;
|
||||||
note this does **not** auto-sync with the standalone StarForth repo's own version) and
|
note this does **not** auto-sync with the standalone StarForth repo's own version) and
|
||||||
`LITHOS_VERSION` (`Makefile.starkernel` — the kernel version, currently **`2.0.0`**;
|
`LITHOS_VERSION` (`kernel/Makefile` — the kernel version, currently **`2.0.0`**;
|
||||||
corrected 2026-09-19 — this file said `2.0.1`, but `FABRIC-3.md` §I.2 rolled it back to
|
corrected 2026-09-19 — this file said `2.0.1`, but `FABRIC-3.md` §I.2 rolled it back to
|
||||||
`2.0.0` on 2026-09-04 because `2.0.1` names the SER5 hardware-track line and claims hardware
|
`2.0.0` on 2026-09-04 because `2.0.1` names the SER5 hardware-track line and claims hardware
|
||||||
progress not yet verified. The versioning policy is **semantic, not sequential** — see the
|
progress not yet verified. The versioning policy is **semantic, not sequential** — see the
|
||||||
roadmap table in `Makefile.starkernel` before choosing any version).
|
roadmap table in `kernel/Makefile` before choosing any version).
|
||||||
|
|
||||||
### Build configuration (Kconfig — real, wired, not vestigial)
|
### Build configuration (Kconfig — real, wired, not vestigial)
|
||||||
|
|
||||||
Every kernel-only knob (`STARFORTH_ENABLE_VM`, `PARITY_MODE`, the shared physics/heartbeat
|
Every kernel-only knob (`STARFORTH_ENABLE_VM`, `PARITY_MODE`, the shared physics/heartbeat
|
||||||
family, etc.) is an optional Kconfig symbol defined across `Kconfig`, `Kconfig.arch`,
|
family, etc.) is an optional Kconfig symbol defined across `Kconfig`, `Kconfig.arch`,
|
||||||
`Kconfig.heartbeat`, `Kconfig.kernel`, `Kconfig.physics`, `Kconfig.variant` (~40 symbols
|
`Kconfig.heartbeat`, `Kconfig.kernel`, `Kconfig.physics`, `Kconfig.variant` (~40 symbols
|
||||||
total). `Makefile.starkernel` pulls its defaults from this system via a `kconfig_bool(...)`
|
total). `kernel/Makefile` pulls its defaults from this system via a `kconfig_bool(...)`
|
||||||
mechanism — e.g. `STARFORTH_ENABLE_VM` defaults to **1** (confirmed at
|
mechanism — e.g. `STARFORTH_ENABLE_VM` defaults to **1** (confirmed at
|
||||||
`Makefile.starkernel:61`), meaning a plain `make -f Makefile.starkernel` already builds with
|
`kernel/Makefile:61`), meaning a plain `make -f kernel/Makefile` already builds with
|
||||||
VM + capsule-birth + ACL active. A bare invocation uses the defaults it always has:
|
VM + capsule-birth + ACL active. A bare invocation uses the defaults it always has:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make -f Makefile.starkernel ARCH=amd64 menuconfig
|
make -f kernel/Makefile ARCH=amd64 menuconfig
|
||||||
make -f Makefile.starkernel ARCH=amd64 kernel_amd64_defconfig
|
make -f kernel/Makefile ARCH=amd64 kernel_amd64_defconfig
|
||||||
```
|
```
|
||||||
|
|
||||||
### Hosted VM (vendored, for local sanity only)
|
### Hosted VM (vendored, for local sanity only)
|
||||||
@@ -277,7 +277,7 @@ StarForth repo) were removed 2026-08-15 — they referenced
|
|||||||
in the actual generated `include/version.h` (which only has `STARFORTH_VERSION`,
|
in the actual generated `include/version.h` (which only has `STARFORTH_VERSION`,
|
||||||
`STARFORTH_ARCH`, `STARFORTH_TARGET`, `STARFORTH_TIMESTAMP`, `STARFORTH_VERSION_FULL`,
|
`STARFORTH_ARCH`, `STARFORTH_TARGET`, `STARFORTH_TIMESTAMP`, `STARFORTH_VERSION_FULL`,
|
||||||
`LITHOS_VERSION`, `LITHOS_VERSION_STR`), so they could never have worked. Bump versions by hand-editing the `VERSION`/
|
`LITHOS_VERSION`, `LITHOS_VERSION_STR`), so they could never have worked. Bump versions by hand-editing the `VERSION`/
|
||||||
`LITHOS_VERSION` variables in `Makefile.starkernel` instead. Report the broken targets if
|
`LITHOS_VERSION` variables in `kernel/Makefile` instead. Report the broken targets if
|
||||||
asked, don't silently fix them.
|
asked, don't silently fix them.
|
||||||
|
|
||||||
### Important: Linker Configuration
|
### Important: Linker Configuration
|
||||||
@@ -298,9 +298,9 @@ The vendored hosted `make` build (above) is NEVER used to validate kernel change
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run in this exact order for every kernel change:
|
# Run in this exact order for every kernel change:
|
||||||
make -f Makefile.starkernel ARCH=amd64 clean qemu
|
make -f kernel/Makefile ARCH=amd64 clean qemu
|
||||||
make -f Makefile.starkernel ARCH=aarch64 clean qemu
|
make -f kernel/Makefile ARCH=aarch64 clean qemu
|
||||||
make -f Makefile.starkernel ARCH=riscv64 clean qemu
|
make -f kernel/Makefile ARCH=riscv64 clean qemu
|
||||||
```
|
```
|
||||||
|
|
||||||
**QEMU rule — non-negotiable:** Only ONE QEMU instance may run at a time, always in the
|
**QEMU rule — non-negotiable:** Only ONE QEMU instance may run at a time, always in the
|
||||||
@@ -475,7 +475,7 @@ listed in `proof/ROOT` (corrected 2026-09-19; this file said 23, and `proof/COVE
|
|||||||
said 52 all along). **Read `proof/COVERAGE.md` first**: its stated goal is not a green build but
|
said 52 all along). **Read `proof/COVERAGE.md` first**: its stated goal is not a green build but
|
||||||
"identify precisely what cannot be proven and why — the boundary between 'formally verified'
|
"identify precisely what cannot be proven and why — the boundary between 'formally verified'
|
||||||
and 'not, for this specific reason'." Run `isabelle build -D proof/`
|
and 'not, for this specific reason'." Run `isabelle build -D proof/`
|
||||||
directly; neither `Makefile` nor `Makefile.starkernel` in this repo defines an
|
directly; neither `Makefile` nor `kernel/Makefile` in this repo defines an
|
||||||
`isabelle-build`/`isabelle-check` target (unlike the standalone StarForth repo, which has a
|
`isabelle-build`/`isabelle-check` target (unlike the standalone StarForth repo, which has a
|
||||||
broken one — this repo simply doesn't have the target at all, so there's nothing to
|
broken one — this repo simply doesn't have the target at all, so there's nothing to
|
||||||
mistakenly invoke).
|
mistakenly invoke).
|
||||||
@@ -495,7 +495,7 @@ status for kernel work.
|
|||||||
|
|
||||||
- **Strict ANSI C99** — No GNU extensions, no C++ features
|
- **Strict ANSI C99** — No GNU extensions, no C++ features
|
||||||
- **Zero warnings target, with four explicit exceptions — corrected 2026-08-18, previous
|
- **Zero warnings target, with four explicit exceptions — corrected 2026-08-18, previous
|
||||||
claim was wrong.** Build with `-Wall -Wextra -Werror`, but `Makefile.starkernel` carries
|
claim was wrong.** Build with `-Wall -Wextra -Werror`, but `kernel/Makefile` carries
|
||||||
`-Wno-error=unused-parameter -Wno-error=shift-negative-value -Wno-error=sign-compare
|
`-Wno-error=unused-parameter -Wno-error=shift-negative-value -Wno-error=sign-compare
|
||||||
-Wno-error=missing-field-initializers` — those four classes are enabled (still visible as
|
-Wno-error=missing-field-initializers` — those four classes are enabled (still visible as
|
||||||
warnings) but deliberately downgraded from fatal, everything else is. The previous version
|
warnings) but deliberately downgraded from fatal, everything else is. The previous version
|
||||||
|
|||||||
@@ -933,10 +933,12 @@ WARN_LOGFILE = docs/api/doxygen_warnings.log
|
|||||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||||
# Note: If this tag is empty the current directory is searched.
|
# Note: If this tag is empty the current directory is searched.
|
||||||
|
|
||||||
INPUT = include \
|
INPUT = v3/include \
|
||||||
src \
|
v3/src \
|
||||||
src/word_source \
|
v3/src/word_source \
|
||||||
src/test_runner \
|
v3/src/test_runner \
|
||||||
|
kernel/include \
|
||||||
|
kernel/src \
|
||||||
README.md
|
README.md
|
||||||
|
|
||||||
# This tag can be used to specify the character encoding of the source files
|
# This tag can be used to specify the character encoding of the source files
|
||||||
@@ -2366,9 +2368,10 @@ SEARCH_INCLUDES = YES
|
|||||||
# RECURSIVE has no effect here.
|
# RECURSIVE has no effect here.
|
||||||
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
|
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
|
||||||
|
|
||||||
INCLUDE_PATH = include \
|
INCLUDE_PATH = v3/include \
|
||||||
src/word_source/include \
|
kernel/include \
|
||||||
src/test_runner/include
|
v3/src/word_source/include \
|
||||||
|
v3/src/test_runner/include
|
||||||
|
|
||||||
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
|
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
|
||||||
# patterns (like *.h and *.hpp) to filter out the header-files in the
|
# patterns (like *.h and *.hpp) to filter out the header-files in the
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ choice
|
|||||||
default ARCH_AMD64
|
default ARCH_AMD64
|
||||||
help
|
help
|
||||||
Selects the value passed as ARCH= to whichever Makefile
|
Selects the value passed as ARCH= to whichever Makefile
|
||||||
(Makefile or Makefile.starkernel) is driven from this config.
|
(Makefile or kernel/Makefile) is driven from this config.
|
||||||
Both Makefiles already accept "amd64"/"aarch64"/"riscv64" as
|
Both Makefiles already accept "amd64"/"aarch64"/"riscv64" as
|
||||||
canonical spellings (each has its own alias-normalizing logic
|
canonical spellings (each has its own alias-normalizing logic
|
||||||
for other spellings like x86_64/arm64/riscv), so this choice
|
for other spellings like x86_64/arm64/riscv), so this choice
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ config HEARTBEAT_THREAD_ENABLED
|
|||||||
builds without needing a special case. This is UI/model
|
builds without needing a special case. This is UI/model
|
||||||
correctness (menuconfig can't offer a choice LithosAnanke has no
|
correctness (menuconfig can't offer a choice LithosAnanke has no
|
||||||
way to honor -- there are no pthreads in a freestanding kernel),
|
way to honor -- there are no pthreads in a freestanding kernel),
|
||||||
not the sole enforcement mechanism: Makefile.starkernel additionally
|
not the sole enforcement mechanism: kernel/Makefile additionally
|
||||||
keeps its own unconditional `VM_FEATURE_OVERRIDES +=
|
keeps its own unconditional `VM_FEATURE_OVERRIDES +=
|
||||||
-DHEARTBEAT_THREAD_ENABLED=0` post-override exactly as it was
|
-DHEARTBEAT_THREAD_ENABLED=0` post-override exactly as it was
|
||||||
before this migration. Deliberately redundant with the `depends
|
before this migration. Deliberately redundant with the `depends
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ config STARFORTH_ENABLE_VM
|
|||||||
kernel that only reaches the M0-M6 hardware milestones (console,
|
kernel that only reaches the M0-M6 hardware milestones (console,
|
||||||
PMM, VMM, interrupts, timers, kmalloc) with no FORTH interpreter,
|
PMM, VMM, interrupts, timers, kmalloc) with no FORTH interpreter,
|
||||||
no capsules, no "ok" REPL. Gates a large source-file selection
|
no capsules, no "ok" REPL. Gates a large source-file selection
|
||||||
block in Makefile.starkernel, not just a handful of -D flags.
|
block in kernel/Makefile, not just a handful of -D flags.
|
||||||
|
|
||||||
config PARITY_MODE
|
config PARITY_MODE
|
||||||
bool "Deterministic parity harness mode (PARITY_MODE)"
|
bool "Deterministic parity harness mode (PARITY_MODE)"
|
||||||
|
|||||||
@@ -6,6 +6,25 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Products
|
||||||
|
|
||||||
|
StarForth v4 moves the engine onto a 32-instruction core derived from Chuck Moore's F18
|
||||||
|
(`docs/v4.0.0/`). With it, this repository carries three products, not one:
|
||||||
|
|
||||||
|
| Product | What it is |
|
||||||
|
|---|---|
|
||||||
|
| **Hosted StarForth F18** | The v4 engine as a native Linux build, for amd64, arm64 and riscv64, on real hardware. |
|
||||||
|
| **FPGA StarForth F18** | A 32-bit build of the same engine loaded into the FPGA. It is the gateway to the rest: the foundation everything else is built up from. |
|
||||||
|
| **StarshipOS** | The bare-metal product: LithosAnanke running the v4 F18 engine. |
|
||||||
|
|
||||||
|
FORTH-79 is recomposed on the F18 engine and stored as a capsule; the StarshipOS-specific
|
||||||
|
vocabulary gets the same treatment. The source tree will be reorganised around this split.
|
||||||
|
|
||||||
|
v3 remains the reference system until v4 meets its acceptance criteria
|
||||||
|
(`docs/v4.0.0/JUSTIFICATION.md` §16). Everything below describes v3 as it stands today.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Status — M7.1 (Capsule System · Multi-VM Fleet)
|
## Status — M7.1 (Capsule System · Multi-VM Fleet)
|
||||||
|
|
||||||
| Milestone | | Status |
|
| Milestone | | Status |
|
||||||
@@ -48,19 +67,39 @@ POST at boot: **parity hash verified across amd64/aarch64/riscv64 · Mama capsul
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# One boot image per physical target (output: build/boards/<board>/)
|
||||||
|
make boot_image TARGET=SER5 # Beelink SER5 -- GPT/ESP, BOOTX64.EFI
|
||||||
|
make boot_image TARGET=RASPI # Raspberry Pi 5 -- FAT, config.txt + kernel_2712.img + DTB
|
||||||
|
make boot_image TARGET=MILKV # Milk-V Mars -- GPT/ESP, BOOTRISCV64.EFI (U-Boot bootefi)
|
||||||
|
make boot_image TARGET=ZYNQ7020 # Zynq-7020 -- ARMv7 port in progress
|
||||||
|
make boards # list boards
|
||||||
|
|
||||||
|
# The same TARGET drives all, docs and clean (make help)
|
||||||
|
make # every board with a kernel port + hosted v3 + the book
|
||||||
|
make TARGET=RASPI # one board's image + build/docs/LithosAnanke-raspi.pdf
|
||||||
|
make docs # build/docs/LithosAnanke.pdf (LaTeX master: docs/book/main.tex)
|
||||||
|
make clean TARGET=RASPI # that board's outputs and build/aarch64/kernel
|
||||||
|
make clean # all of build/ except build/cache/
|
||||||
|
|
||||||
# Build kernel (requires cross-compilation toolchain, or native gcc)
|
# Build kernel (requires cross-compilation toolchain, or native gcc)
|
||||||
make -f Makefile.starkernel ARCH=amd64
|
make -f kernel/Makefile ARCH=amd64
|
||||||
|
|
||||||
# Run in QEMU with OVMF
|
# Run in QEMU with OVMF
|
||||||
make -f Makefile.starkernel qemu
|
make -f kernel/Makefile qemu
|
||||||
|
|
||||||
# Other architectures
|
# Other architectures
|
||||||
make -f Makefile.starkernel ARCH=aarch64 qemu
|
make -f kernel/Makefile ARCH=aarch64 qemu
|
||||||
make -f Makefile.starkernel ARCH=riscv64 qemu
|
make -f kernel/Makefile ARCH=riscv64 qemu
|
||||||
```
|
```
|
||||||
|
|
||||||
Artifacts: `build/amd64/kernel/starkernel_loader.efi` · `build/amd64/kernel/starkernel_kernel.elf`
|
Artifacts: `build/amd64/kernel/starkernel_loader.efi` · `build/amd64/kernel/starkernel_kernel.elf`
|
||||||
|
|
||||||
|
Tree: `kernel/` (LithosAnanke: `Makefile`, `src/`, `include/starkernel/`, `linker/`),
|
||||||
|
`v3/` (StarForth v3 engine and hosted build: `Makefile`, `src/`, `include/`),
|
||||||
|
`v4/` (StarForth v4, see `docs/v4.0.0/`), `boards/<board>/` (`board.mk` + boot files).
|
||||||
|
Every makefile runs from the repo root; the root `Makefile` forwards any other goal to
|
||||||
|
`v3/Makefile` (hosted build).
|
||||||
|
|
||||||
For the hosted VM by itself (Linux, no cross-compiler needed, no bare-metal tooling): see
|
For the hosted VM by itself (Linux, no cross-compiler needed, no bare-metal tooling): see
|
||||||
the separate **StarForth** repository — LithosAnanke used to be a branch inside that repo,
|
the separate **StarForth** repository — LithosAnanke used to be a branch inside that repo,
|
||||||
now it's its own project with its own `master`.
|
now it's its own project with its own `master`.
|
||||||
@@ -69,12 +108,12 @@ now it's its own project with its own `master`.
|
|||||||
|
|
||||||
Every kernel-only knob (`STARFORTH_ENABLE_VM`, `PARITY_MODE`, the shared
|
Every kernel-only knob (`STARFORTH_ENABLE_VM`, `PARITY_MODE`, the shared
|
||||||
physics/heartbeat family, etc.) is an optional Kconfig symbol — a plain
|
physics/heartbeat family, etc.) is an optional Kconfig symbol — a plain
|
||||||
`make -f Makefile.starkernel` uses the same defaults it always has unless
|
`make -f kernel/Makefile` uses the same defaults it always has unless
|
||||||
you opt in:
|
you opt in:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make -f Makefile.starkernel ARCH=amd64 menuconfig
|
make -f kernel/Makefile ARCH=amd64 menuconfig
|
||||||
make -f Makefile.starkernel ARCH=amd64 kernel_amd64_defconfig
|
make -f kernel/Makefile ARCH=amd64 kernel_amd64_defconfig
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# boards/
|
||||||
|
|
||||||
|
One directory per physical target. `make boot_image TARGET=<NAME>` (repo root) maps the
|
||||||
|
name to a directory here, reads its `board.mk`, and builds the single file that goes on
|
||||||
|
that board's boot medium into `build/boards/<board>/`.
|
||||||
|
|
||||||
|
| TARGET | Directory | ISA | Boot path | Image |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `SER5` | `ser5/` | amd64 | UEFI firmware, removable-media path | GPT + FAT32 ESP: `EFI/BOOT/BOOTX64.EFI` |
|
||||||
|
| `RASPI` | `raspi/` | aarch64 | Pi 5 EEPROM firmware, native | MBR + FAT32: `config.txt`, `kernel_2712.img`, `bcm2712-rpi-5-b.dtb` |
|
||||||
|
| `MILKV` | `milkv/` | riscv64 | SPI-flash U-Boot + OpenSBI, `bootefi` | GPT + FAT32 ESP: `EFI/BOOT/BOOTRISCV64.EFI` |
|
||||||
|
| `ZYNQ7020` | `zynq7020/` | armv7 | BootROM, `BOOT.BIN`, U-Boot | not yet: needs `kernel/src/arch/armv7` |
|
||||||
|
|
||||||
|
`board.mk` sets:
|
||||||
|
|
||||||
|
- `BOARD_DESC`: one-line description (`make boards`)
|
||||||
|
- `BOARD_ARCH`: kernel ISA (`kernel/src/arch/<arch>`)
|
||||||
|
- `BOARD_BOOT`: image recipe in `kernel/Makefile` (`uefi-esp`, `rpi-native`, ...)
|
||||||
|
- `BOARD_IMAGE`: output file name
|
||||||
|
|
||||||
|
Disk images are assembled by `scripts/mkdiskimage.sh`, which needs no root access and no loop
|
||||||
|
devices. `KERNEL_ARGS="..."` is written as `starforth.cfg` (UEFI boards) or `cmdline.txt`
|
||||||
|
(Pi 5, where the firmware copies it into `/chosen/bootargs`). `BOOT_IMAGE_SIZE_MIB` sets the
|
||||||
|
image size; the default is 128.
|
||||||
|
|
||||||
|
Hardware status: none of these images has been booted on real silicon from this build. The
|
||||||
|
SER5 image has been booted in QEMU (q35 + OVMF, from its own disk) through POST. The Pi 5
|
||||||
|
native path (`raspi/README.md`) and the JH7110 peripherals have still only been checked by
|
||||||
|
compiling.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Milk-V Mars boot media
|
||||||
|
|
||||||
|
`make boot_image TARGET=MILKV` builds `build/boards/milkv/lithos-milkv.img`: a GPT disk with one
|
||||||
|
FAT32 EFI System Partition. Write it to an SD card with `dd`. The partition holds:
|
||||||
|
|
||||||
|
- `EFI/BOOT/BOOTRISCV64.EFI`: the riscv64 monolithic loader, the same one QEMU boots.
|
||||||
|
- `startup.nsh`: for a UEFI shell, if one is used.
|
||||||
|
- `starforth.cfg`: only present when `KERNEL_ARGS="..."` is given.
|
||||||
|
|
||||||
|
The board's own SPI-flash U-Boot, with OpenSBI underneath, finds the loader through its standard
|
||||||
|
`bootefi` scan of the removable-media path. U-Boot passes its devicetree to the loader through the
|
||||||
|
EFI configuration table.
|
||||||
|
|
||||||
|
Status: booted in QEMU through the same chain the board uses: OpenSBI, then U-Boot
|
||||||
|
`qemu-riscv64_smode`, then `bootefi` from this image's ESP, then the loader and the kernel. POST
|
||||||
|
passed, Stadium conservation was exact (resident 43691 + reservoir 21845 = 65536 = `Q48_ONE`), and
|
||||||
|
Hestia and Artemis came up. Not yet booted on the Mars itself. The JH7110 peripherals
|
||||||
|
(framebuffer, RNG) have only been checked by compiling.
|
||||||
|
|
||||||
|
To repeat the QEMU boot (`qemu-system-riscv64` is in Ubuntu 26.04's `qemu-system-riscv` package;
|
||||||
|
OpenSBI and U-Boot come from `opensbi` and `u-boot-qemu`). Use a copy, because the kernel writes
|
||||||
|
to its disk:
|
||||||
|
|
||||||
|
```
|
||||||
|
cp build/boards/milkv/lithos-milkv.img /tmp/mv.img
|
||||||
|
qemu-system-riscv64 -machine virt -cpu rv64 -m 2048 -nographic \
|
||||||
|
-bios /usr/lib/riscv64-linux-gnu/opensbi/generic/fw_dynamic.bin \
|
||||||
|
-kernel /usr/lib/u-boot/qemu-riscv64_smode/u-boot.bin \
|
||||||
|
-drive if=none,format=raw,file=/tmp/mv.img,id=d0 -device virtio-blk-device,drive=d0
|
||||||
|
```
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# boards/milkv/board.mk -- Milk-V Mars (StarFive JH7110, SiFive U74, RV64GC).
|
||||||
|
#
|
||||||
|
# The board's own SPI-flash U-Boot (OpenSBI underneath) runs the EFI
|
||||||
|
# application through its standard distro/bootefi scan: the SD card is a GPT
|
||||||
|
# disk with one FAT32 EFI System Partition holding EFI/BOOT/BOOTRISCV64.EFI,
|
||||||
|
# the same riscv64 monolithic loader QEMU boots. U-Boot passes its own
|
||||||
|
# devicetree through the EFI configuration table.
|
||||||
|
BOARD_DESC := Milk-V Mars (StarFive JH7110, RV64GC, U-Boot bootefi)
|
||||||
|
BOARD_ARCH := riscv64
|
||||||
|
BOARD_BOOT := uefi-esp
|
||||||
|
BOARD_IMAGE := lithos-milkv.img
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Raspberry Pi 5 native boot media
|
||||||
|
|
||||||
|
`make boot_image TARGET=RASPI` builds `build/boards/raspi/lithos-raspi.img`: an MBR disk
|
||||||
|
with one FAT32 (LBA) partition. Write it to an SD card with `dd`. The partition holds:
|
||||||
|
|
||||||
|
- `config.txt`: checked in here (FABRIC-3.md §IV.3 item 6). It pins `kernel_address=0x80000`
|
||||||
|
because Pi 5 firmware loads `kernel_2712.img` at 0x200000 by default.
|
||||||
|
- `kernel_2712.img`: the aarch64 kernel objects relinked at 0x80000 by
|
||||||
|
`kernel/linker/starkernel-native-rpi5.ld`, entered at `rpi5_native_start`
|
||||||
|
(`kernel/src/arch/aarch64/native_rpi5_entry.S`, which zeroes `.bss` first), and flattened
|
||||||
|
with `objcopy -O binary`.
|
||||||
|
- `bcm2712-rpi-5-b.dtb`: the Raspberry Pi firmware's own stock DTB. It is not built by this
|
||||||
|
project; it is fetched once from the firmware release pinned in `board.mk`
|
||||||
|
(`RPI_FIRMWARE_TAG`) into `build/cache/`. To use a local copy, pass `RPI5_DTB=/path/to/dtb`.
|
||||||
|
- `cmdline.txt`: only present when `KERNEL_ARGS="..."` is given. The firmware copies it into
|
||||||
|
`/chosen/bootargs`, which `rpi5_native_boot()` parses.
|
||||||
|
|
||||||
|
Not yet verified on hardware. The native path still assumes everything `kernel_main()`
|
||||||
|
needs after a UEFI handoff. The firmware enters at EL2 with the MMU off, which is not what
|
||||||
|
UEFI hands over, and that difference has not been exercised.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# boards/raspi/board.mk -- Raspberry Pi 5 (BCM2712, Cortex-A76, aarch64).
|
||||||
|
#
|
||||||
|
# Native firmware boot, no UEFI (FABRIC-3.md §IV): the Pi 5 bootloader
|
||||||
|
# lives in EEPROM and reads config.txt, kernel_2712.img and the board DTB
|
||||||
|
# from the first FAT partition of the SD card. kernel_2712.img is the
|
||||||
|
# aarch64 kernel relinked at 0x80000 (kernel/linker/starkernel-native-rpi5.ld)
|
||||||
|
# and flattened to a raw binary.
|
||||||
|
#
|
||||||
|
# The DTB is the Raspberry Pi firmware's own stock file, not built here. It
|
||||||
|
# is fetched once from the pinned firmware release below into build/cache/
|
||||||
|
# unless RPI5_DTB points at a local copy.
|
||||||
|
BOARD_DESC := Raspberry Pi 5 (BCM2712, Cortex-A76, native firmware boot)
|
||||||
|
BOARD_ARCH := aarch64
|
||||||
|
BOARD_BOOT := rpi-native
|
||||||
|
BOARD_IMAGE := lithos-raspi.img
|
||||||
|
RPI_FIRMWARE_TAG ?= 1.20260915
|
||||||
|
RPI5_DTB_URL ?= https://raw.githubusercontent.com/raspberrypi/firmware/$(RPI_FIRMWARE_TAG)/boot/bcm2712-rpi-5-b.dtb
|
||||||
|
RPI5_DTB ?= build/cache/raspi-$(RPI_FIRMWARE_TAG)/bcm2712-rpi-5-b.dtb
|
||||||
@@ -12,6 +12,13 @@
|
|||||||
# firmware picks up.
|
# firmware picks up.
|
||||||
kernel=kernel_2712.img
|
kernel=kernel_2712.img
|
||||||
|
|
||||||
|
# Pin the load address to the one the image is linked at
|
||||||
|
# (kernel/linker/starkernel-native-rpi5.ld). Pi 5 firmware's own default
|
||||||
|
# for kernel_2712.img is 0x200000, not the classic 0x80000 -- leaving it
|
||||||
|
# implicit would load a 0x80000-linked flat image 1.5 MiB away from where
|
||||||
|
# every absolute address in it points.
|
||||||
|
kernel_address=0x80000
|
||||||
|
|
||||||
# Disable the firmware's Linux-compatible-image sanity check. Without
|
# Disable the firmware's Linux-compatible-image sanity check. Without
|
||||||
# this, official docs describe it as checking for "a compatible Device
|
# this, official docs describe it as checking for "a compatible Device
|
||||||
# Tree file before attempting to boot" and warn that "older non-compatible
|
# Tree file before attempting to boot" and warn that "older non-compatible
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Beelink SER5 boot media
|
||||||
|
|
||||||
|
`make boot_image TARGET=SER5` builds `build/boards/ser5/lithos-ser5.img`: a GPT disk with one
|
||||||
|
FAT32 EFI System Partition. Write it to a USB stick with `dd`. The partition holds:
|
||||||
|
|
||||||
|
- `EFI/BOOT/BOOTX64.EFI`: the amd64 monolithic loader, with the whole kernel embedded. Firmware
|
||||||
|
finds it through the UEFI removable-media path, so no boot entry has to be created.
|
||||||
|
- `startup.nsh`: runs the loader if the firmware drops into the UEFI shell.
|
||||||
|
- `starforth.cfg`: only present when `KERNEL_ARGS="..."` is given.
|
||||||
|
|
||||||
|
Nothing in the image is SER5-specific. It follows the generic UEFI/ACPI path in FABRIC-3.md §III.
|
||||||
|
|
||||||
|
Status: booted in QEMU (q35 + OVMF, from this image as its own disk) through POST, with Stadium
|
||||||
|
conservation exact (sum 65536 = `Q48_ONE`). Not yet booted on the SER5 itself.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# boards/ser5/board.mk -- Beelink SER5 (AMD Ryzen, x86-64, UEFI/ACPI).
|
||||||
|
#
|
||||||
|
# Generic standards-compliant UEFI path (FABRIC-3.md §III): nothing in the
|
||||||
|
# image is SER5-specific. The monolithic loader embeds the whole kernel, so
|
||||||
|
# the image is a GPT disk with one FAT32 EFI System Partition holding
|
||||||
|
# EFI/BOOT/BOOTX64.EFI. Write it to a USB stick with dd.
|
||||||
|
BOARD_DESC := Beelink SER5 (AMD Ryzen, x86-64, UEFI)
|
||||||
|
BOARD_ARCH := amd64
|
||||||
|
BOARD_BOOT := uefi-esp
|
||||||
|
BOARD_IMAGE := lithos-ser5.img
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Xilinx Zynq-7020 boot media
|
||||||
|
|
||||||
|
The Zynq-7020's processing system is a dual Cortex-A9: ARMv7-A, 32-bit. It is the host for the
|
||||||
|
StarForth v4 mesh (`docs/v4.0.0/JUSTIFICATION.md` §5, §8).
|
||||||
|
|
||||||
|
Boot chain: BootROM, then `BOOT.BIN` (U-Boot SPL with the board's `ps7_init` DDR and clock setup),
|
||||||
|
then U-Boot, then LithosAnanke. Everything is read from the SD card's FAT partition.
|
||||||
|
|
||||||
|
Status: no image yet. `make boot_image TARGET=ZYNQ7020` stops with an error, because there is no
|
||||||
|
ARMv7 kernel port (`kernel/src/arch/armv7`: entry code, MMU, GIC, private timer). A trial
|
||||||
|
compile for Cortex-A9 built 147 of the 151 kernel and engine sources. The four that failed use
|
||||||
|
`__int128` or have no ARMv7 branch: `kernel/src/crypto/fe25519.c`,
|
||||||
|
`kernel/src/crypto/scalar25519.c`, `kernel/src/hal/hal.c` and
|
||||||
|
`v3/src/word_source/mixed_arithmetic_words.c`.
|
||||||
|
|
||||||
|
`BOOT.BIN` depends on the exact board (Zybo Z7-20, PYNQ-Z2, Arty Z7-20, ZedBoard, ...), because
|
||||||
|
`ps7_init` holds that board's DDR timing. The board has not been chosen yet.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# boards/zynq7020/board.mk -- Xilinx Zynq-7020 (dual Cortex-A9, ARMv7-A, 32-bit).
|
||||||
|
#
|
||||||
|
# Boot chain: BootROM -> BOOT.BIN (U-Boot SPL with the board's ps7_init DDR/
|
||||||
|
# clock setup) -> U-Boot -> LithosAnanke, all from the SD card's FAT
|
||||||
|
# partition. Needs the ARMv7 kernel port (kernel/src/arch/armv7), which is
|
||||||
|
# in progress: until it exists, boot_image stops with an explicit error
|
||||||
|
# rather than producing an image that cannot run.
|
||||||
|
BOARD_DESC := Xilinx Zynq-7020 (Cortex-A9, ARMv7-A, BOOT.BIN) -- ARMv7 port in progress
|
||||||
|
BOARD_ARCH := armv7
|
||||||
|
BOARD_BOOT := zynq-bootbin
|
||||||
|
BOARD_IMAGE := lithos-zynq7020.img
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Raspberry Pi 5 native boot media
|
|
||||||
|
|
||||||
What goes on the SD card for the native (non-UEFI) boot path (FABRIC-3.md §IV):
|
|
||||||
|
|
||||||
- `config.txt` — checked in here, done (§IV.3 item 6).
|
|
||||||
- `bcm2712-rpi-5-b.dtb` — Raspberry Pi firmware's own stock DTB; not built by this
|
|
||||||
project, copied from the firmware release the card is otherwise built from.
|
|
||||||
- `kernel_2712.img` — **does not exist yet.** `config.txt` names it, but no build target
|
|
||||||
in `Makefile.starkernel` currently emits a raw image by this name at load address
|
|
||||||
0x80000 — item 1 (entry stub) explicitly scoped a separate-image build target as future
|
|
||||||
work, not part of that item. Building this file is the remaining prerequisite before
|
|
||||||
item 7 (assembling the card) is possible.
|
|
||||||
+42
-40
@@ -1,5 +1,5 @@
|
|||||||
# Capsule Block Manifest — Auto-generated
|
# Capsule Block Manifest — Auto-generated
|
||||||
<!-- Generated by mkcapsule --manifest 2026-09-22T22:29:26Z -->
|
<!-- Generated by mkcapsule --manifest 2026-10-01T19:21:21Z -->
|
||||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||||
<!-- Hand-written justifications and immutability notes live -->
|
<!-- Hand-written justifications and immutability notes live -->
|
||||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||||
@@ -8,42 +8,44 @@
|
|||||||
|
|
||||||
| Capsule | Blocks claimed | xxHash64 | Signed |
|
| Capsule | Blocks claimed | xxHash64 | Signed |
|
||||||
|---------|----------------|----------|--------|
|
|---------|----------------|----------|--------|
|
||||||
| `ACL.4th` | 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 4015 | `0xf8890c05c0d8f921` | yes |
|
| `ACL.4th` | 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 4015 | `0xf8890c05c0d8f921` | n/a |
|
||||||
| `acl-std79.4th` | 4023, 4024, 4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 4034, 4035, 4036, 4037, 4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048 | `0x773bf9209df191d1` | yes |
|
| `acl-std79.4th` | 4023, 4024, 4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 4034, 4035, 4036, 4037, 4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048 | `0x773bf9209df191d1` | n/a |
|
||||||
| `artemis:init.4th` | 4110, 4111, 4112, 4113, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, 4138, 4139, 4140, 4141, 4160, 4161, 4162, 4163, 4164, 4165, 4166, 4167, 4168, 4169, 4170, 4171, 4172, 4173, 4174, 4177, 4178, 4179, 4180, 4181, 4182, 4851, 4852, 4853, 4854, 4856, 4857, 4858, 4860 | `0xe6fba9ae56e1c916` | yes |
|
| `artemis:init.4th` | 4110, 4111, 4112, 4113, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, 4138, 4139, 4140, 4141, 4160, 4161, 4162, 4163, 4164, 4165, 4166, 4167, 4168, 4169, 4170, 4171, 4172, 4173, 4174, 4177, 4178, 4179, 4180, 4181, 4182, 4851, 4852, 4853, 4854, 4856, 4857, 4858, 4860 | `0xe6fba9ae56e1c916` | n/a |
|
||||||
| `block-acl.4th` | 4019, 4020 | `0xf6cc2a59e3a6734e` | yes |
|
| `block-acl.4th` | 4019, 4020 | `0xf6cc2a59e3a6734e` | n/a |
|
||||||
| `doe-campaign.4th` | 4060, 4061, 4062, 4063, 4064, 4065 | `0x26fb485e5c9dc6ff` | yes |
|
| `doe-campaign.4th` | 4060, 4061, 4062, 4063, 4064, 4065 | `0x26fb485e5c9dc6ff` | n/a |
|
||||||
| `doe.4th` | 2100, 2101, 2102, 2103, 2104, 2105, 2106, 2107 | `0xf154616d248e861f` | yes |
|
| `doe.4th` | 2100, 2101, 2102, 2103, 2104, 2105, 2106, 2107 | `0xf154616d248e861f` | n/a |
|
||||||
| `fabric.4th` | 4900, 4901, 4902, 4903, 4904, 4905, 4906, 4907, 4908, 4909, 4910, 4911, 4912, 4913, 4914, 4915, 4916, 4917, 4918, 4919, 4920, 4921, 4922, 4923, 4924, 5000, 5001, 5002 | `0x9d9489cbeca4099b` | yes |
|
| `fabric.4th` | 4900, 4901, 4902, 4903, 4904, 4905, 4906, 4907, 4908, 4909, 4910, 4911, 4912, 4913, 4914, 4915, 4916, 4917, 4918, 4919, 4920, 4921, 4922, 4923, 4924, 5000, 5001, 5002 | `0x9d9489cbeca4099b` | n/a |
|
||||||
| `font.4th` | 4925, 4926, 4927, 4928, 4929, 4930, 4931, 4932, 4933, 4934, 4935, 4936, 4937, 4938, 4939, 4940, 4941, 4942, 4943, 4944, 4945, 4946, 4947, 4948, 4949, 4950, 4951, 4952, 4953, 4954, 4955, 4956, 4957, 4958, 4959, 4960, 4961, 4962, 4963, 4964, 4965, 4966, 4967, 4968, 4969, 4970, 4971, 4972, 4973, 4974, 4975, 4976, 4977, 4978, 4979, 4980, 4981, 4982, 4983, 4984, 4985 | `0x3f305911500c78f6` | yes |
|
| `font.4th` | 4925, 4926, 4927, 4928, 4929, 4930, 4931, 4932, 4933, 4934, 4935, 4936, 4937, 4938, 4939, 4940, 4941, 4942, 4943, 4944, 4945, 4946, 4947, 4948, 4949, 4950, 4951, 4952, 4953, 4954, 4955, 4956, 4957, 4958, 4959, 4960, 4961, 4962, 4963, 4964, 4965, 4966, 4967, 4968, 4969, 4970, 4971, 4972, 4973, 4974, 4975, 4976, 4977, 4978, 4979, 4980, 4981, 4982, 4983, 4984, 4985 | `0x3f305911500c78f6` | n/a |
|
||||||
| `hestia:init.4th` | 4986, 4987, 4988 | `0x64e111990fbc45ff` | yes |
|
| `hestia:init.4th` | 4986, 4987, 4988 | `0x64e111990fbc45ff` | n/a |
|
||||||
| `init-l8-diverse.4th` | 4820, 4821, 4822 | `0xaa293201a6c91838` | yes |
|
| `init-l8-diverse.4th` | 4820, 4821, 4822 | `0xaa293201a6c91838` | n/a |
|
||||||
| `init-l8-omni.4th` | 2064, 2065, 2066, 2067, 2068, 2069, 2070, 2071, 2072, 2073, 2074, 2075, 2076, 2077, 2078, 2079 | `0x5979e314d6452045` | yes |
|
| `init-l8-omni.4th` | 2064, 2065, 2066, 2067, 2068, 2069, 2070, 2071, 2072, 2073, 2074, 2075, 2076, 2077, 2078, 2079 | `0x5979e314d6452045` | n/a |
|
||||||
| `init-l8-stable.4th` | 4806 | `0xdc3830f189063a9a` | yes |
|
| `init-l8-stable.4th` | 4806 | `0xdc3830f189063a9a` | n/a |
|
||||||
| `init-l8-temporal.4th` | 4830, 4831 | `0x51abd4c138246651` | yes |
|
| `init-l8-temporal.4th` | 4830, 4831 | `0x51abd4c138246651` | n/a |
|
||||||
| `init-l8-transition.4th` | 4840, 4841, 4842 | `0xbcc1a81976f0a4c9` | yes |
|
| `init-l8-transition.4th` | 4840, 4841, 4842 | `0xbcc1a81976f0a4c9` | n/a |
|
||||||
| `init-l8-volatile.4th` | 4810, 4811, 4812, 4813 | `0x98caabbbd92abac4` | yes |
|
| `init-l8-volatile.4th` | 4810, 4811, 4812, 4813 | `0x98caabbbd92abac4` | n/a |
|
||||||
| `init.4th` | 2049, 2050, 2057 | `0xea038ba684c53443` | yes |
|
| `init.4th` | 2049, 2050, 2057 | `0xea038ba684c53443` | n/a |
|
||||||
| `lib.4th` | 4050 | `0x4b216635c359ef73` | yes |
|
| `lib.4th` | 4050 | `0x4b216635c359ef73` | n/a |
|
||||||
| `multiuser-doe.4th` | 5044, 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5054, 5055 | `0x22140588ee7c39e6` | yes |
|
| `multiuser-doe.4th` | 5044, 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5054, 5055 | `0x22140588ee7c39e6` | n/a |
|
||||||
| `sdk.4th` | 5109, 5110, 5111, 5112, 5113, 5114, 5115 | `0x008fdbbb62c94a3a` | yes |
|
| `sdk.4th` | 5109, 5110, 5111, 5112, 5113, 5114, 5115 | `0x008fdbbb62c94a3a` | n/a |
|
||||||
| `turtle.4th` | 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5107, 5108 | `0x4d470418ca543365` | yes |
|
| `turtle.4th` | 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5107, 5108 | `0x4d470418ca543365` | n/a |
|
||||||
| `user-font-demo.4th` | 4200, 4201, 4202 | `0xce1fd7d1b581a56d` | yes |
|
| `user-font-demo.4th` | 4200, 4201, 4202 | `0xce1fd7d1b581a56d` | n/a |
|
||||||
| `workload-0.4th` | 2200, 2201 | `0x93f86f60aeba8feb` | yes |
|
| `workload-0.4th` | 2200, 2201 | `0x93f86f60aeba8feb` | n/a |
|
||||||
| `workload-1-lite.4th` | 5058, 5059 | `0x44a7a7e3176dcc8d` | yes |
|
| `workload-1-lite.4th` | 5058, 5059 | `0x44a7a7e3176dcc8d` | n/a |
|
||||||
| `workload-1.4th` | 4406, 4415, 4425, 4435 | `0x63e251adb0a03613` | yes |
|
| `workload-1.4th` | 4406, 4415, 4425, 4435 | `0x63e251adb0a03613` | n/a |
|
||||||
| `workload-2.4th` | 4506, 4515, 4525, 4535, 4545 | `0xf113b3d0bcccae47` | yes |
|
| `workload-2.4th` | 4506, 4515, 4525, 4535, 4545 | `0xf113b3d0bcccae47` | n/a |
|
||||||
| `workload-3.4th` | 4606, 4615, 4625, 4635, 4645, 4655, 4665 | `0x62b7a71576ad1041` | yes |
|
| `workload-3.4th` | 4606, 4615, 4625, 4635, 4645, 4655, 4665 | `0x62b7a71576ad1041` | n/a |
|
||||||
| `workload-4.4th` | 2130, 2131, 2132 | `0x099619264e650b1c` | yes |
|
| `workload-4.4th` | 2130, 2131, 2132 | `0x099619264e650b1c` | n/a |
|
||||||
| `workload-5-lite.4th` | 5056, 5057 | `0xea0303f38824f254` | yes |
|
| `workload-5-lite.4th` | 5056, 5057 | `0xea0303f38824f254` | n/a |
|
||||||
| `workload-5.4th` | *(none — raw code capsule)* | `0x526964c7c9506a11` | yes |
|
| `workload-5.4th` | *(none — raw code capsule)* | `0x526964c7c9506a11` | n/a |
|
||||||
| `workload-6.4th` | 2080, 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089, 2090, 2091, 2092, 2093, 2094, 2095 | `0x06fc0ce1e369ef5a` | yes |
|
| `workload-6.4th` | 2080, 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089, 2090, 2091, 2092, 2093, 2094, 2095 | `0x06fc0ce1e369ef5a` | n/a |
|
||||||
| `workload-7.4th` | 2150 | `0x02862291387eb7ac` | yes |
|
| `workload-7.4th` | 2150 | `0x02862291387eb7ac` | n/a |
|
||||||
| `workload-8.4th` | 2160 | `0x56b7f2f0efa000df` | yes |
|
| `workload-8.4th` | 2160 | `0x56b7f2f0efa000df` | n/a |
|
||||||
| `workload-9.4th` | 4706, 4715, 4725, 4735, 4745 | `0x3f2bec73142aa424` | yes |
|
| `workload-9.4th` | 4706, 4715, 4725, 4735, 4745 | `0x3f2bec73142aa424` | n/a |
|
||||||
| `workload-calib1.4th` | 5043 | `0x3b9f2d17b554fabc` | yes |
|
| `workload-calib1.4th` | 5043 | `0x3b9f2d17b554fabc` | n/a |
|
||||||
| `zuse-eligibility.4th` | 4021, 4022 | `0x7b28f4776a32e0b7` | yes |
|
| `zuse-eligibility.4th` | 4021, 4022 | `0x7b28f4776a32e0b7` | n/a |
|
||||||
| `zuse.4th` | 4016, 4017, 4018 | `0x490ded9be257a90b` | yes |
|
| `zuse.4th` | 4016, 4017, 4018 | `0xa14d5a7a88791cf6` | n/a |
|
||||||
|
|
||||||
|
*Signed column is `n/a`: this manifest run had no `--sign-key`. Re-run with `--sign-key <path>` to check signing status (does not modify or require rebuilding capsule_generated.c).*
|
||||||
|
|
||||||
## Block Map (sorted by LBN)
|
## Block Map (sorted by LBN)
|
||||||
|
|
||||||
@@ -109,9 +111,9 @@
|
|||||||
| 4007 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
|
| 4007 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
|
||||||
| 4008 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
|
| 4008 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
|
||||||
| 4015 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
|
| 4015 | `ACL.4th` | `0xf8890c05c0d8f921` | ok |
|
||||||
| 4016 | `zuse.4th` | `0x490ded9be257a90b` | ok |
|
| 4016 | `zuse.4th` | `0xa14d5a7a88791cf6` | ok |
|
||||||
| 4017 | `zuse.4th` | `0x490ded9be257a90b` | ok |
|
| 4017 | `zuse.4th` | `0xa14d5a7a88791cf6` | ok |
|
||||||
| 4018 | `zuse.4th` | `0x490ded9be257a90b` | ok |
|
| 4018 | `zuse.4th` | `0xa14d5a7a88791cf6` | ok |
|
||||||
| 4019 | `block-acl.4th` | `0xf6cc2a59e3a6734e` | ok |
|
| 4019 | `block-acl.4th` | `0xf6cc2a59e3a6734e` | ok |
|
||||||
| 4020 | `block-acl.4th` | `0xf6cc2a59e3a6734e` | ok |
|
| 4020 | `block-acl.4th` | `0xf6cc2a59e3a6734e` | ok |
|
||||||
| 4021 | `zuse-eligibility.4th` | `0x7b28f4776a32e0b7` | ok |
|
| 4021 | `zuse-eligibility.4th` | `0x7b28f4776a32e0b7` | ok |
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ Block 4016
|
|||||||
( >BODY-then-store, so a pinned CONSTANT isn't tamper-proof. )
|
( >BODY-then-store, so a pinned CONSTANT isn't tamper-proof. )
|
||||||
( Read with ZUSE-PUBKEY@ / ZUSE-CERT-INSTALLED? -- both C )
|
( Read with ZUSE-PUBKEY@ / ZUSE-CERT-INSTALLED? -- both C )
|
||||||
( primitives, read-only; the seed has no FORTH access at all. )
|
( primitives, read-only; the seed has no FORTH access at all. )
|
||||||
|
( ZUSE-ELIGIBILITY-ADD denied by default -- see block 4017. )
|
||||||
|
0 ['] ZUSE-ELIGIBILITY-ADD ACL-ALLOW!
|
||||||
|
|
||||||
Block 4017
|
Block 4017
|
||||||
( ACL-ZUSE-BOOT ( -- ) re-invokable: capsule_zuse_boot.c )
|
( ACL-ZUSE-BOOT ( -- ) re-invokable: capsule_zuse_boot.c )
|
||||||
@@ -23,6 +25,8 @@ Block 4017
|
|||||||
: ACL-ZUSE-BOOT ( -- )
|
: ACL-ZUSE-BOOT ( -- )
|
||||||
ZUSE-CERT-INSTALLED? IF
|
ZUSE-CERT-INSTALLED? IF
|
||||||
ZUSE-AUTHENTICATE
|
ZUSE-AUTHENTICATE
|
||||||
|
1 ['] ZUSE-ELIGIBILITY-ADD ACL-ALLOW!
|
||||||
|
['] ZUSE-ELIGIBILITY-ADD ACL-PIN
|
||||||
LOG-INFO" zuse: activated"
|
LOG-INFO" zuse: activated"
|
||||||
ELSE
|
ELSE
|
||||||
LOG-INFO" zuse: NOT activated -- no cert installed"
|
LOG-INFO" zuse: NOT activated -- no cert installed"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# configs/
|
# configs/
|
||||||
|
|
||||||
Example Kconfig `defconfig` files — starting points for `make -f
|
Example Kconfig `defconfig` files — starting points for `make -f
|
||||||
Makefile.starkernel ARCH=<arch> defconfig` (kernel) or the equivalent
|
kernel/Makefile ARCH=<arch> defconfig` (kernel) or the equivalent
|
||||||
hosted target, matching the current committed default build behavior for
|
hosted target, matching the current committed default build behavior for
|
||||||
each profile. Loaded via `tools/kconfig/conf`.
|
each profile. Loaded via `tools/kconfig/conf`.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,787 @@
|
|||||||
|
# StarForth Primitive Word Reference
|
||||||
|
|
||||||
|
This reference covers every **C-implemented primitive** word that StarForth registers. It was built from
|
||||||
|
`admin/LithosAnanake` at commit `6302dcb` (2026-09-23). It lists only words registered in C
|
||||||
|
through `register_word()` or `vm_create_word()`. Words defined in FORTH inside capsules (`*.4th`) are out of scope.
|
||||||
|
|
||||||
|
Sources:
|
||||||
|
|
||||||
|
- `src/word_registry.c`: `register_forth79_words()` registers the core set in every VM.
|
||||||
|
- `src/word_source/*.c`: one file per module.
|
||||||
|
- `src/starkernel/capsule/mama_forth_words.c`: kernel-only Hera (Mama) and child-VM words.
|
||||||
|
- `src/starkernel/repl.c` and `src/starkernel/doe_log.c`: kernel-only REPL and DoE words.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
| Item | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| Cell | `cell_t` is `int64_t`, so every cell is 64 bits and signed. |
|
||||||
|
| Flag | TRUE is `-1` (all bits set) and FALSE is `0`. Words that take a flag treat any non-zero value as true. |
|
||||||
|
| `addr` | A **VM address**: a byte offset into the VM's 5 MB linear memory (`VM_MEMORY_SIZE`), not a host pointer. |
|
||||||
|
| `c-addr u` | A string given as its address and length. |
|
||||||
|
| `d`, `ud` | A double-cell number made of two cells, with the **high cell on top**. |
|
||||||
|
| `xt` | An execution token. In StarForth this is the `DictEntry*` of the word. |
|
||||||
|
| `q` | A Q48.16 fixed-point value in one cell (`1.0` = `65536`). |
|
||||||
|
| `"name"` | The word parses a name from the input stream after it. |
|
||||||
|
| `( R: ... )` | The effect on the return stack. |
|
||||||
|
| **IMM** | The word is IMMEDIATE, so it runs even while compiling. |
|
||||||
|
| **CO** | The word is compile-only and sets `vm->error` if used outside a definition. |
|
||||||
|
| **K** | The word is registered only in the kernel build (`__STARKERNEL__`). |
|
||||||
|
| **H** | The word is registered only in the hosted build (the Linux or macOS binary). |
|
||||||
|
|
||||||
|
Errors: a primitive does not throw. On stack underflow or overflow, a bad address, or division by zero it sets
|
||||||
|
`vm->error = 1` and logs a message.
|
||||||
|
|
||||||
|
Stack limits: the data stack and return stack hold 1024 cells each (`STACK_SIZE`). A word name can be at most 31
|
||||||
|
characters (`WORD_NAME_MAX`).
|
||||||
|
|
||||||
|
Shadowing: when a later module registers a name again, the newer entry wins lookups. For example, `MOD`, `/MOD`,
|
||||||
|
`*/` and `*/MOD` are registered by the arithmetic module and again by the mixed-arithmetic module, so the
|
||||||
|
mixed-arithmetic versions are the active ones.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
1. [Stack](#1-stack)
|
||||||
|
2. [Return stack](#2-return-stack)
|
||||||
|
3. [Memory](#3-memory)
|
||||||
|
4. [Arithmetic](#4-arithmetic)
|
||||||
|
5. [Logic and comparison](#5-logic-and-comparison)
|
||||||
|
6. [Mixed-precision arithmetic](#6-mixed-precision-arithmetic)
|
||||||
|
7. [Double-cell numbers](#7-double-cell-numbers)
|
||||||
|
8. [Number formatting and output](#8-number-formatting-and-output)
|
||||||
|
9. [Strings, parsing, and input](#9-strings-parsing-and-input)
|
||||||
|
10. [Terminal I/O](#10-terminal-io)
|
||||||
|
11. [Blocks and mass storage](#11-blocks-and-mass-storage)
|
||||||
|
12. [Dictionary space](#12-dictionary-space)
|
||||||
|
13. [Dictionary manipulation](#13-dictionary-manipulation)
|
||||||
|
14. [Vocabularies](#14-vocabularies)
|
||||||
|
15. [System](#15-system)
|
||||||
|
16. [Line editor](#16-line-editor)
|
||||||
|
17. [Defining words and the compiler](#17-defining-words-and-the-compiler)
|
||||||
|
18. [Control flow](#18-control-flow)
|
||||||
|
19. [StarForth extensions](#19-starforth-extensions)
|
||||||
|
20. [Word-level ACL](#20-word-level-acl)
|
||||||
|
21. [Physics: benchmark and diagnostics](#21-physics-benchmark-and-diagnostics)
|
||||||
|
22. [Physics: pipelining diagnostics](#22-physics-pipelining-diagnostics)
|
||||||
|
23. [Physics: freeze, heat, and decay](#23-physics-freeze-heat-and-decay)
|
||||||
|
24. [Dictionary heat optimisation](#24-dictionary-heat-optimisation)
|
||||||
|
25. [Logging](#25-logging)
|
||||||
|
26. [Q48.16 fixed-point math](#26-q4816-fixed-point-math)
|
||||||
|
27. [Inference engine (SSM, L8, and Bayes)](#27-inference-engine-ssm-l8-and-bayes)
|
||||||
|
28. [DEFER and IS](#28-defer-and-is)
|
||||||
|
29. [Framebuffer (Hestia only)](#29-framebuffer-hestia-only)
|
||||||
|
30. [Keyboard](#30-keyboard)
|
||||||
|
31. [TrueType text](#31-truetype-text)
|
||||||
|
32. [REPL scrollback](#32-repl-scrollback)
|
||||||
|
33. [Kernel REPL and DoE hooks](#33-kernel-repl-and-doe-hooks)
|
||||||
|
34. [Hera (Mama) and child-VM words](#34-hera-mama-and-child-vm-words)
|
||||||
|
35. [Hosted lifecycle stubs](#35-hosted-lifecycle-stubs)
|
||||||
|
36. [Implementation quirks to know](#36-implementation-quirks-to-know)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Stack
|
||||||
|
`src/word_source/stack_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `DROP` | `( x -- )` | Discards the top cell. |
|
||||||
|
| `DUP` | `( x -- x x )` | Copies the top cell. |
|
||||||
|
| `?DUP` | `( x -- x x \| 0 -- 0 )` | Copies the top cell only when it is non-zero. The usual idiom is `?DUP IF ... THEN`. |
|
||||||
|
| `SWAP` | `( x1 x2 -- x2 x1 )` | Swaps the top two cells. |
|
||||||
|
| `OVER` | `( x1 x2 -- x1 x2 x1 )` | Copies the second cell to the top. |
|
||||||
|
| `ROT` | `( x1 x2 x3 -- x2 x3 x1 )` | Moves the third cell to the top. |
|
||||||
|
| `-ROT` | `( x1 x2 x3 -- x3 x1 x2 )` | Moves the top cell down to third place. This is the reverse of `ROT`. |
|
||||||
|
| `DEPTH` | `( -- n )` | Pushes the number of cells that were on the data stack before `DEPTH` ran. |
|
||||||
|
| `PICK` | `( xn … x0 n -- xn … x0 xn )` | Copies the n-th cell to the top. **0-based:** `0 PICK` is `DUP` and `1 PICK` is `OVER`. An error occurs if `n < 0` or `n ≥ depth`. |
|
||||||
|
| `ROLL` | `( … n -- … )` | Moves a cell to the top and closes the gap. **Non-standard:** `n` counts from the *bottom* of the stack (1-based), so `1 ROLL` moves the deepest cell to the top. `0 ROLL` does nothing. See [§36](#36-implementation-quirks-to-know). |
|
||||||
|
|
||||||
|
## 2. Return stack
|
||||||
|
`src/word_source/return_stack_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `>R` | `( x -- ) ( R: -- x )` | Moves a cell from the data stack to the return stack. Inside a definition, balance it with `R>` before `;` or `EXIT`. |
|
||||||
|
| `R>` | `( -- x ) ( R: x -- )` | Moves a cell from the return stack back to the data stack. |
|
||||||
|
| `R@` | `( -- x ) ( R: x -- x )` | Copies the top of the return stack without removing it. |
|
||||||
|
|
||||||
|
## 3. Memory
|
||||||
|
`src/word_source/memory_words.c`. Every address is a VM byte offset and is checked against the VM's memory bounds.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `@` | `( addr -- x )` | Fetches the cell at `addr`. |
|
||||||
|
| `!` | `( x addr -- )` | Stores `x` at `addr`. |
|
||||||
|
| `C@` | `( addr -- c )` | Fetches the byte at `addr`, zero-extended. |
|
||||||
|
| `C!` | `( c addr -- )` | Stores the low 8 bits of `c` at `addr`. |
|
||||||
|
| `+!` | `( n addr -- )` | Adds `n` to the cell at `addr`. |
|
||||||
|
| `-!` | `( n addr -- )` | Subtracts `n` from the cell at `addr`. |
|
||||||
|
| `2@` | `( addr -- x-lo x-hi )` | Fetches two cells: the low cell from `addr` and the high cell from `addr+8`, leaving the high cell on top. |
|
||||||
|
| `2!` | `( x-lo x-hi addr -- )` | Stores two cells: the low cell at `addr` and the high cell at `addr+8`. |
|
||||||
|
| `FILL` | `( addr u c -- )` | Fills `u` bytes starting at `addr` with the byte `c`. |
|
||||||
|
| `MOVE` | `( src dst u -- )` | Copies `u` bytes from `src` to `dst`. It is safe when the ranges overlap because it uses memmove semantics. |
|
||||||
|
| `ERASE` | `( addr u -- )` | Sets `u` bytes to zero. |
|
||||||
|
| `CELLS` | `( n -- n*8 )` | Scales a cell count to a byte count. |
|
||||||
|
|
||||||
|
## 4. Arithmetic
|
||||||
|
`src/word_source/arithmetic_words.c`. All arithmetic is signed 64-bit, and division truncates toward zero as in C.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `+` | `( n1 n2 -- n1+n2 )` | Adds the two cells. |
|
||||||
|
| `-` | `( n1 n2 -- n1-n2 )` | Subtracts `n2` from `n1`. |
|
||||||
|
| `*` | `( n1 n2 -- n1*n2 )` | Multiplies the two cells. The result wraps modulo 2⁶⁴. |
|
||||||
|
| `/` | `( n1 n2 -- n1/n2 )` | Divides, truncating toward zero. Division by zero sets an error. |
|
||||||
|
| `MOD` | `( n1 n2 -- rem )` | Pushes the remainder of `n1 / n2`, which has the sign of `n1`. This name is shadowed by §6. |
|
||||||
|
| `/MOD` | `( n1 n2 -- rem quot )` | Pushes the remainder and the quotient, with the quotient on top. This name is shadowed by §6. |
|
||||||
|
| `*/` | `( n1 n2 n3 -- n1*n2/n3 )` | Multiplies and then divides using a wide intermediate. This name is shadowed by §6. |
|
||||||
|
| `*/MOD` | `( n1 n2 n3 -- rem quot )` | Like `*/`, but also leaves the remainder. This name is shadowed by §6. |
|
||||||
|
| `1+` `1-` | `( n -- n±1 )` | Increments or decrements by 1. |
|
||||||
|
| `2+` `2-` | `( n -- n±2 )` | Adds or subtracts 2. |
|
||||||
|
| `2*` | `( n -- n*2 )` | Shifts left by one bit. |
|
||||||
|
| `2/` | `( n -- n/2 )` | Shifts right by one bit, keeping the sign (arithmetic shift). |
|
||||||
|
| `ABS` | `( n -- \|n\| )` | Pushes the absolute value. |
|
||||||
|
| `NEGATE` | `( n -- -n )` | Pushes the two's-complement negation. |
|
||||||
|
| `MIN` `MAX` | `( n1 n2 -- n3 )` | Pushes the smaller or the larger value, compared as signed numbers. |
|
||||||
|
|
||||||
|
## 5. Logic and comparison
|
||||||
|
`src/word_source/logical_words.c`. Comparison words return a proper flag of `-1` or `0`.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `AND` `OR` `XOR` | `( x1 x2 -- x3 )` | Bitwise AND, OR, and XOR. |
|
||||||
|
| `NOT` | `( x -- flag )` | **FORTH-79 logical NOT:** `0` gives `TRUE` and any other value gives `FALSE`. This is *not* a bitwise complement; use `INVERT` for that. |
|
||||||
|
| `INVERT` | `( x -- ~x )` | Bitwise complement (from FORTH-83). |
|
||||||
|
| `LSHIFT` | `( x u -- x<<u )` | Logical shift left by `u` bits. |
|
||||||
|
| `RSHIFT` | `( x u -- x>>u )` | Logical (unsigned) shift right by `u` bits. |
|
||||||
|
| `0=` | `( n -- flag )` | True if `n` is 0. |
|
||||||
|
| `0<` | `( n -- flag )` | True if `n` is negative. |
|
||||||
|
| `0>` | `( n -- flag )` | True if `n` is positive. |
|
||||||
|
| `0<>` | `( n -- flag )` | True if `n` is not 0. |
|
||||||
|
| `=` `<>` | `( n1 n2 -- flag )` | Tests for equality or inequality. |
|
||||||
|
| `<` `>` `<=` `>=` | `( n1 n2 -- flag )` | Signed comparisons of `n1` against `n2`. |
|
||||||
|
| `U<` `U>` | `( u1 u2 -- flag )` | Unsigned comparisons. |
|
||||||
|
| `WITHIN` | `( n lo hi -- flag )` | True if `lo ≤ n < hi`, using the standard half-open range. |
|
||||||
|
| `TRUE` | `( -- -1 )` | Pushes the canonical true flag. |
|
||||||
|
| `FALSE` | `( -- 0 )` | Pushes the canonical false flag. |
|
||||||
|
|
||||||
|
## 6. Mixed-precision arithmetic
|
||||||
|
`src/word_source/mixed_arithmetic_words.c`. A double here means two full 64-bit cells (128 bits).
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `M+` | `( d n -- d' )` | Adds a signed single to a double and propagates the carry into the high cell. |
|
||||||
|
| `M-` | `( d n -- d' )` | Subtracts a signed single from a double and propagates the borrow. |
|
||||||
|
| `M*` | `( n1 n2 -- d )` | Multiplies 64×64 into a full 128-bit signed product, using `__int128` internally. The result can be printed with `D.`. |
|
||||||
|
| `M/MOD` | `( d n -- rem quot )` | Divides a 128-bit double by a single, leaving the quotient on top. It uses bit-serial long division, so it works in the freestanding kernel without libgcc. |
|
||||||
|
| `MOD` | `( n1 n2 -- rem )` | **Active version.** Computes `n1 % n2`. Division by zero sets an error. |
|
||||||
|
| `/MOD` | `( n1 n2 -- rem quot )` | **Active version.** Leaves the remainder under the quotient. Division by zero sets an error. |
|
||||||
|
| `*/` | `( n1 n2 n3 -- n4 )` | **Active version.** Computes `(n1*n2)/n3` with a wide intermediate, so `n1*n2` does not overflow. Division by zero sets an error. Typical use is scaling, for example `x 355 113 */`. |
|
||||||
|
| `*/MOD` | `( n1 n2 n3 -- rem quot )` | **Active version.** Like `*/`, but also leaves the remainder under the quotient. |
|
||||||
|
|
||||||
|
## 7. Double-cell numbers
|
||||||
|
`src/word_source/double_words.c`. In every stack picture, `d` stands for the pair `( lo hi )` with the high cell on top.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `S>D` | `( n -- d )` | Sign-extends a single to a double. |
|
||||||
|
| `D+` `D-` | `( d1 d2 -- d3 )` | Double add and subtract, with carry or borrow. |
|
||||||
|
| `DNEGATE` | `( d -- -d )` | Negates a double. |
|
||||||
|
| `DABS` | `( d -- \|d\| )` | Pushes the absolute value of a double. |
|
||||||
|
| `DMAX` `DMIN` | `( d1 d2 -- d3 )` | Pushes the larger or smaller double, compared as signed values. |
|
||||||
|
| `D<` | `( d1 d2 -- flag )` | Signed less-than on doubles. |
|
||||||
|
| `D=` | `( d1 d2 -- flag )` | Equality on doubles. |
|
||||||
|
| `D0=` | `( d -- flag )` | True if the double is zero. |
|
||||||
|
| `D0<` | `( d -- flag )` | True if the double is negative. |
|
||||||
|
| `D2*` | `( d -- d*2 )` | Shifts a double left by one bit across both cells. |
|
||||||
|
| `D2/` | `( d -- d/2 )` | Shifts a double right by one bit (arithmetic shift) across both cells. |
|
||||||
|
| `2DROP` | `( x1 x2 -- )` | Drops a cell pair. |
|
||||||
|
| `2DUP` | `( x1 x2 -- x1 x2 x1 x2 )` | Duplicates a cell pair. |
|
||||||
|
| `2SWAP` | `( p1 p2 -- p2 p1 )` | Swaps two cell pairs. |
|
||||||
|
| `2OVER` | `( p1 p2 -- p1 p2 p1 )` | Copies the second pair to the top. |
|
||||||
|
| `2ROT` | `( p1 p2 p3 -- p2 p3 p1 )` | Rotates three cell pairs. |
|
||||||
|
| `2>R` | `( x1 x2 -- ) ( R: -- x1 x2 )` | Moves a pair to the return stack. |
|
||||||
|
| `2R>` | `( -- x1 x2 ) ( R: x1 x2 -- )` | Moves a pair back from the return stack. |
|
||||||
|
| `2R@` | `( -- x1 x2 ) ( R: x1 x2 -- x1 x2 )` | Copies a pair from the return stack. |
|
||||||
|
|
||||||
|
## 8. Number formatting and output
|
||||||
|
`src/word_source/format_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `.` | `( n -- )` | Prints a signed number in the current `BASE`, followed by a space. |
|
||||||
|
| `.R` | `( n width -- )` | Prints a signed number right-aligned in a field `width` characters wide. |
|
||||||
|
| `U.` | `( u -- )` | Prints an unsigned number followed by a space. |
|
||||||
|
| `U.R` | `( u width -- )` | Prints an unsigned number right-aligned. |
|
||||||
|
| `D.` | `( d -- )` | Prints a signed double. |
|
||||||
|
| `D.R` | `( d width -- )` | Prints a signed double right-aligned. |
|
||||||
|
| `.S` | `( -- )` | Prints the data stack without changing it. This is the main debugging aid. |
|
||||||
|
| `?` | `( addr -- )` | Prints the cell at `addr`; it is the same as `@ .`. |
|
||||||
|
| `DUMP` | `( addr u -- )` | Prints a hex and ASCII dump of `u` bytes starting at `addr`. |
|
||||||
|
| `<#` | `( -- )` | Starts pictured numeric output by resetting the conversion buffer. |
|
||||||
|
| `#` | `( ud -- ud' )` | Converts one digit (`ud mod BASE`) into the buffer. It also accepts a single signed cell and converts its magnitude. |
|
||||||
|
| `#S` | `( ud -- 0 0 )` | Converts digits until the value is zero, always producing at least one digit. It accepts a single cell the same way `#` does. |
|
||||||
|
| `HOLD` | `( c -- )` | Inserts the character `c` into the pictured output buffer. |
|
||||||
|
| `SIGN` | `( n -- )` | Inserts `-` if `n` is negative. |
|
||||||
|
| `#>` | `( ud -- c-addr u )` | Ends conversion and leaves the string. It is tolerant: it pops `ud` only if one is present. |
|
||||||
|
| `BASE` | `( -- addr )` | Pushes the address of the number-conversion radix variable. |
|
||||||
|
| `DECIMAL` `HEX` `OCTAL` | `( -- )` | Sets `BASE` to 10, 16, or 8. |
|
||||||
|
|
||||||
|
Example: `: .$ ( n -- ) <# # # 46 HOLD #S #> TYPE ;` prints `1234` as `12.34`.
|
||||||
|
|
||||||
|
## 9. Strings, parsing, and input
|
||||||
|
`src/word_source/string_words.c`. The comparison and search words below also accept a counted string in place of an
|
||||||
|
`addr u` pair; they detect it when the first byte at `addr` equals `u`.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `COUNT` | `( c-addr1 -- c-addr2 u )` | Converts a counted string (length byte followed by characters) into an address and length. |
|
||||||
|
| `EXPECT` | `( addr u -- )` | Reads up to `u` characters from the terminal into `addr` and stores the count read in `SPAN`. |
|
||||||
|
| `SPAN` | `( -- addr )` | Pushes the address of the variable that holds the count from the last `EXPECT`. |
|
||||||
|
| `QUERY` | `( -- )` | Reads a line into `TIB` and resets `>IN`. |
|
||||||
|
| `TIB` | `( -- addr )` | Pushes the address of the terminal input buffer. |
|
||||||
|
| `>IN` | `( -- addr )` | Pushes the address of the offset into the current input source. |
|
||||||
|
| `SOURCE` | `( -- addr u )` | Pushes the current input buffer and its length. |
|
||||||
|
| `WORD` | `( c -- c-addr )` | Skips leading `c` characters, parses up to the next `c`, and returns a counted string. The usual form is `BL WORD`. |
|
||||||
|
| `BL` | `( -- 32 )` | Pushes the ASCII space character. |
|
||||||
|
| `S"` **IMM** | `( "ccc<">" -- c-addr u )` | In interpret mode, stores the string at `HERE` and pushes it. When compiling, it compiles `(s")` followed by the inline text. |
|
||||||
|
| `(s")` | `( -- c-addr u )` | Runtime for a compiled `S"`: reads the inline `[len][chars][pad]` block and skips the IP past it. The compiler inserts it; you do not call it directly. |
|
||||||
|
| `[']` **IMM** | `( "name" -- xt )` | While compiling, compiles the xt of `name` as a literal. In interpret mode it behaves like `'`. |
|
||||||
|
| `LITERAL` `[LITERAL]` | – | Placeholders that do nothing. `LITERAL` is re-registered in §17 (the working version); `[LITERAL]` has no replacement and still does nothing. |
|
||||||
|
| `CONVERT` | `( d1 addr1 -- d2 addr2 )` | Accumulates the digits at `addr1+1…` into `d1` and stops at the first non-digit. This is a simplified version. |
|
||||||
|
| `NUMBER` | `( c-addr -- n flag )` | Converts a counted string to a number. Only base 10 is supported, and `flag` shows whether it succeeded. |
|
||||||
|
| `ENCLOSE` | `( addr c -- addr n1 n2 n3 )` | The classic FIG parser: gives the offsets of the start of the token, the delimiter after it, and the next character. |
|
||||||
|
| `-TRAILING` | `( addr u -- addr u' )` | Removes trailing spaces from the length. |
|
||||||
|
| `CMOVE` | `( src dst u -- )` | Copies bytes upward from low to high addresses. It is safe for overlapping ranges when `dst ≤ src`. |
|
||||||
|
| `CMOVE>` | `( src dst u -- )` | Copies bytes downward from high to low addresses. It is safe for overlapping ranges when `dst > src`. |
|
||||||
|
| `COMPARE` | `( a1 u1 a2 u2 -- n )` | Compares two strings case-sensitively and returns `-1`, `0`, or `1`. |
|
||||||
|
| `SEARCH` | `( a1 u1 a2 u2 -- a3 u3 flag )` | Finds string 2 inside string 1. If found, it returns the tail starting at the match and `-1`. If not, it returns string 1 unchanged and `0`. |
|
||||||
|
| `SCAN` | `( addr u c -- addr' u' )` | Advances to the first occurrence of `c`. If there is none, it returns the end of the string and `0`. |
|
||||||
|
| `SKIP` | `( addr u c -- addr' u' )` | Skips leading occurrences of `c`. |
|
||||||
|
| `BLANK` | `( addr u -- )` | Fills `u` bytes with spaces. |
|
||||||
|
|
||||||
|
## 10. Terminal I/O
|
||||||
|
`src/word_source/io_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `EMIT` | `( c -- )` | Prints one character. |
|
||||||
|
| `CR` | `( -- )` | Prints a newline. |
|
||||||
|
| `KEY` | `( -- c )` | Waits for a character and pushes it. |
|
||||||
|
| `?TERMINAL` | `( -- flag )` | True if a key is waiting. It does not block. |
|
||||||
|
| `TYPE` | `( c-addr u -- )` | Prints `u` characters. |
|
||||||
|
| `SPACE` | `( -- )` | Prints one space. |
|
||||||
|
| `SPACES` | `( n -- )` | Prints `n` spaces. |
|
||||||
|
| `."` **IMM** | `( "ccc<">" -- )` | In interpret mode, prints the string immediately. When compiling, it compiles `(do-string)` and the inline text. |
|
||||||
|
| `(do-string)` | `( -- )` | Runtime for a compiled `."`: prints the inline string and skips the IP past it. The compiler inserts it; you do not call it directly. |
|
||||||
|
|
||||||
|
## 11. Blocks and mass storage
|
||||||
|
`src/word_source/block_words.c`. A block is 1024 bytes, viewed as 16 lines of 64 characters. Block numbers are
|
||||||
|
LBNs (logical block numbers) in one address space that spans every attached device.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `BLOCK` | `( u -- addr )` | Returns the VM address of the buffer holding block `u`, reading it from disk if needed. It does not mark the buffer dirty. |
|
||||||
|
| `BUFFER` | `( u -- addr )` | Assigns a buffer to block `u` *without* reading from disk and marks it dirty. Use it when you will overwrite the whole block. |
|
||||||
|
| `UPDATE` | `( -- )` | Marks the current (`SCR`) block dirty and syncs it to the C block layer. |
|
||||||
|
| `SAVE-BUFFERS` | `( -- )` | Writes every dirty buffer to disk. |
|
||||||
|
| `EMPTY-BUFFERS` | `( -- )` | Discards every buffer **without** writing it and zeroes the user block window. |
|
||||||
|
| `FLUSH` | `( -- )` | Runs `SAVE-BUFFERS` and then invalidates all buffers. |
|
||||||
|
| `LOAD` | `( u -- )` | Sets `SCR` to `u` and interprets the 1024 bytes of block `u` as FORTH source. Block 0 cannot be loaded. |
|
||||||
|
| `THRU` | `( u1 u2 -- )` | Loads blocks `u1` through `u2`, including both ends. |
|
||||||
|
| `-->` | `( -- )` | Inside a block being loaded, continues interpreting at the next block. |
|
||||||
|
| `LIST` | `( u -- )` | Sets `SCR` to `u` and prints the block. |
|
||||||
|
| `SCR` | `( -- addr )` | Pushes the address of the variable holding the block number last listed or loaded. |
|
||||||
|
| `BLK-CONFIRM-FORMAT` | `( lbn -- )` | Commits the container format of the device that owns `lbn`. Until this has run, the block layer **refuses every write** to that device. Only the owner (for example Artemis) should call it, and only after checking the disk contents are safe to touch. |
|
||||||
|
| `RELOCATE-BLOCK` | `( home target -- )` | Moves the contents of `home` to `target` and redirects all later access to `home` through `target`. It is a mechanical primitive: it does not check whether `target` is free or owned by the caller. |
|
||||||
|
| `BLK-ACL-ALLOW@` | `( blk -- allow )` | Reads the cached allow/deny flag of a block's ACL. |
|
||||||
|
| `BLK-ACL-ALLOW!` | `( allow blk -- )` | Sets a block's cached allow/deny flag. |
|
||||||
|
| `BLK-ACL-TTL@` | `( blk -- ttl )` | Reads a block's ACL TTL countdown. |
|
||||||
|
| `BLK-ACL-TTL!` | `( ttl blk -- )` | Sets a block's ACL TTL countdown. |
|
||||||
|
| `BLK-OWNER@` | `( blk -- fp )` | Pushes the 8-byte owner fingerprint as the raw bits of one cell. There is no `BLK-OWNER!`: ownership is set only in C, during MINT or birth. |
|
||||||
|
| `BLK-ATTACH` | `( dev-ptr -- ok? )` | Registers an already-open `blkio_dev_t*`, passed as a raw pointer cell, in the unified LBN space. The pointer is trusted without checks. Artemis's USB-attach handler uses it. |
|
||||||
|
|
||||||
|
## 12. Dictionary space
|
||||||
|
`src/word_source/dictionary_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `HERE` | `( -- addr )` | Pushes the next free byte in the dictionary. |
|
||||||
|
| `ALIGN` | `( -- )` | Rounds `HERE` up to the next 8-byte cell boundary. |
|
||||||
|
| `ALLOT` | `( n -- )` | Reserves `n` bytes at `HERE`. A negative `n` gives space back. |
|
||||||
|
| `,` | `( x -- )` | Compiles one cell at `HERE` and advances `HERE`. |
|
||||||
|
| `C,` | `( c -- )` | Compiles one byte. |
|
||||||
|
| `2,` | `( x-lo x-hi -- )` | Compiles two cells, low cell first. |
|
||||||
|
| `PAD` | `( -- addr )` | Pushes the address of a 512-byte scratch buffer near the top of memory. It is safe for temporary strings. |
|
||||||
|
| `SP@` | `( -- n )` | Pushes the data stack pointer *index* (`dsp`). An empty stack gives `-1` and one item gives `0`. |
|
||||||
|
| `SP!` | `( n -- )` | Restores the stack pointer index. It can only shrink the stack, never grow it. |
|
||||||
|
| `LATEST` | `( -- addr )` | Pushes the address of the most recent definition. |
|
||||||
|
|
||||||
|
## 13. Dictionary manipulation
|
||||||
|
`src/word_source/dictionary_manipulation_words.c`. Header-field words work on raw header addresses. They exist for
|
||||||
|
FIG and FORTH-79 compatibility; take care with them.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `'` | `( "name" -- xt )` | Parses `name` and pushes its xt. It is not IMMEDIATE; inside a definition, use `[']`. |
|
||||||
|
| `FIND` | `( "name" -- xt \| 0 )` | **Parses from the input stream**, not from a counted string on the stack. It pushes the entry, or `0` if the word is not found (a miss is not an error). For a counted string already in memory, use `(FIND)` from §14. |
|
||||||
|
| `SMUDGE` | `( -- )` | **CO.** Toggles the smudge (hidden) bit on the latest word. |
|
||||||
|
| `HIDDEN` | `( -- )` | **CO.** Sets the latest word's hidden bit (unlike `SMUDGE`, it does not toggle). |
|
||||||
|
| `>BODY` | `( xt -- addr )` | Pushes the address of the parameter (data) field. |
|
||||||
|
| `>NAME` | `( xt -- nfa )` | Pushes the name field. |
|
||||||
|
| `NAME>` | `( nfa -- xt )` | Goes from the name field to the xt. |
|
||||||
|
| `>LINK` | `( xt -- lfa )` | Pushes the link field. |
|
||||||
|
| `LINK>` | `( lfa -- xt )` | Follows the link to the next (older) word. |
|
||||||
|
| `CFA` `LFA` `NFA` `PFA` | `( addr -- addr' )` | FIG-style field-address conversions (code, link, name, and parameter fields). |
|
||||||
|
| `TRAVERSE` | `( addr n -- addr' )` | Moves across a name field forward (`n=1`) or backward (`n=-1`). |
|
||||||
|
| `INTERPRET` | `( -- )` | Runs the text interpreter on the rest of the current input. |
|
||||||
|
|
||||||
|
## 14. Vocabularies
|
||||||
|
`src/word_source/vocabulary_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `VOCABULARY` | `( "name" -- )` | Creates a vocabulary. Running `name` later makes it the `CONTEXT` (search) vocabulary. |
|
||||||
|
| `DEFINITIONS` | `( -- )` | Sets `CURRENT` to `CONTEXT`, so new definitions go into the vocabulary being searched. |
|
||||||
|
| `CONTEXT` | `( -- addr )` | Pushes the address of the search-vocabulary pointer. |
|
||||||
|
| `CURRENT` | `( -- addr )` | Pushes the address of the definition-vocabulary pointer. |
|
||||||
|
| `FORTH` | `( -- )` | Makes the root `FORTH` vocabulary the context. |
|
||||||
|
| `ORDER` | `( -- )` | Prints the search order (`CONTEXT`, then `FORTH`) and `CURRENT`. |
|
||||||
|
| `(FIND)` | `( c-addr -- c-addr 0 \| xt 1 \| xt -1 )` | Looks up a counted string in `CONTEXT` and then in `FORTH`. It returns `1` for an IMMEDIATE word, `-1` for a normal word, and `0` if not found. |
|
||||||
|
|
||||||
|
Example: `VOCABULARY GRAPHICS GRAPHICS DEFINITIONS : BOX ... ; FORTH DEFINITIONS`
|
||||||
|
|
||||||
|
## 15. System
|
||||||
|
`src/word_source/system_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `(` **IMM** | `( "ccc<)>" -- )` | Starts a comment that runs to the closing `)`. |
|
||||||
|
| `\` **IMM** | `( "ccc<eol>" -- )` | Starts a comment that runs to the end of the line. |
|
||||||
|
| `EXECUTE` | `( xt -- )` | Runs the word identified by `xt`. |
|
||||||
|
| `NOP` | `( -- )` | Does nothing. |
|
||||||
|
| `QUIT` **IMM** | `( -- ) ( R: … -- )` | Clears the return stack and the error flag and returns to the outer interpreter. The data stack is kept. It is refused (sets an error) inside a definition. |
|
||||||
|
| `ABORT` | `( … -- )` | Clears both stacks and returns to the interpreter. It is **not** reported as an error. |
|
||||||
|
| `ABORT"` **IMM** | `( flag "ccc<">" -- )` | If `flag` is non-zero, prints the message and runs `ABORT`. It works in both interpret and compile mode. |
|
||||||
|
| `(ABORT")` | `( flag addr u -- )` | Runtime for a compiled `ABORT"`. The compiler inserts it; you do not call it directly. |
|
||||||
|
| `COLD` | `( -- )` | Clears both stacks and the error flag, returns to interpret mode, and moves `HERE` back to 1024 if it is higher. Dictionary headers are **not** removed; this is a minimal cold start. |
|
||||||
|
| `WARM` | `( -- )` | Clears both stacks and the error flag and returns to interpret mode. `HERE` and the dictionary are kept. |
|
||||||
|
| `BYE` | `( -- )` | Leaves this VM. In a child VM it halts the VM and returns to the parent's REPL. On Hera, the kernel's `BYE` from §34 takes precedence. |
|
||||||
|
| `REBOOT` | `( c-addr u -- )` | Sets the boot arguments and does a cold reset. Interpret mode only. |
|
||||||
|
| `SAVE-SYSTEM` | `( -- )` | Takes a simple snapshot of the start of VM memory. |
|
||||||
|
| `WORDS` | `( -- )` | Lists the words in the current vocabulary. |
|
||||||
|
| `VLIST` | `( -- )` | Gives a detailed word listing. |
|
||||||
|
| `SEE` | `( "name" -- )` | Decompiles and shows a definition. |
|
||||||
|
| `PAGE` | `( -- )` | Clears the screen. |
|
||||||
|
| `79-STANDARD` | `( -- flag )` | Pushes `-1` when FORTH-79 compliance mode is on. |
|
||||||
|
|
||||||
|
## 16. Line editor
|
||||||
|
`src/word_source/editor_words.c`. The editor works on block `SCR` as 16 lines of 64 characters.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `L` | `( u -- )` | Prints line `u` (0–15) of the current screen. |
|
||||||
|
| `S` | `( c-addr len u -- )` | Replaces line `u` with the string, padding with spaces or truncating to 64 characters. |
|
||||||
|
| `SHOW` | `( -- )` | Prints the whole screen with line numbers. |
|
||||||
|
| `EDIT` | `( u -- )` | Opens a minimal stdin/stdout line-editor shell on block `u`. |
|
||||||
|
|
||||||
|
## 17. Defining words and the compiler
|
||||||
|
`src/word_source/defining_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `:` **IMM** | `( "name" -- )` | Starts a colon definition and switches to compile mode. The new word stays hidden until `;`. |
|
||||||
|
| `;` **IMM** | `( -- )` | Compiles `EXIT`, ends the definition, reveals the word, and returns to interpret mode. |
|
||||||
|
| `CREATE` | `( "name" -- )` | Makes a header whose runtime pushes its data-field address (`HERE` aligned to a cell). It allocates **no** space, so follow it with `ALLOT` or `,`. |
|
||||||
|
| `VARIABLE` | `( "name" -- )` | Makes a word that pushes the address of one newly allocated cell. |
|
||||||
|
| `CONSTANT` | `( x "name" -- )` | Makes a word that pushes `x`. |
|
||||||
|
| `DOES>` **IMM** | `( -- )` | Inside a defining word, ends the create part. Words later made by that defining word run the code after `DOES>` with their body address on the stack. |
|
||||||
|
| `IMMEDIATE` **IMM** | `( -- )` | Marks the latest definition IMMEDIATE. |
|
||||||
|
| `STATE` | `( -- addr )` | Pushes the address of the compile-state cell (0 means interpreting). |
|
||||||
|
| `[` **IMM** | `( -- )` | Switches to interpret mode inside a definition. |
|
||||||
|
| `]` **IMM** | `( -- )` | Switches to compile mode. |
|
||||||
|
| `LITERAL` **IMM** | `( x -- )` | Compiles `x` so that it is pushed at runtime. Typical use: `[ 6 7 * ] LITERAL`. |
|
||||||
|
| `LIT` | `( -- x )` | Runtime for literals: pushes the next inline cell. The compiler inserts it; you do not call it directly. |
|
||||||
|
| `COMPILE` **IMM** | `( "name" -- )` | Legacy form: compiles a call to `name`. |
|
||||||
|
| `[COMPILE]` **IMM** | `( "name" -- )` | Compiles `name` even when it is IMMEDIATE. |
|
||||||
|
| `FORGET` | `( "name" -- )` | Removes `name` and every newer word and moves `HERE` back. Words below `FENCE` cannot be forgotten. |
|
||||||
|
| `FENCE` | `( -- )` | Moves the `FORGET` boundary up to the current top of the dictionary. A capsule calls it after loading to protect its own words. |
|
||||||
|
| `does_rt` | – | Internal `DOES>` helper that switches the new child word to DODOES. It is registered only so the threaded code can refer to it; do not call it. |
|
||||||
|
|
||||||
|
Example: `: ARRAY ( n "name" -- ) CREATE CELLS ALLOT DOES> ( i -- addr ) SWAP CELLS + ;`
|
||||||
|
|
||||||
|
## 18. Control flow
|
||||||
|
`src/word_source/control_words.c`. Every structure word is **IMM** and **CO**. Branch offsets are in bytes. Up to 64
|
||||||
|
structures can be nested at compile time (`CF_STACK_MAX`).
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `IF` | `( flag -- )` | Runs the following code only if `flag` is non-zero. It compiles `(0BRANCH)`. |
|
||||||
|
| `ELSE` | `( -- )` | Starts the code that runs when the `IF` flag was zero. |
|
||||||
|
| `THEN` | `( -- )` | Ends an `IF` or `IF … ELSE` structure. |
|
||||||
|
| `BEGIN` | `( -- )` | Marks the start of a loop. |
|
||||||
|
| `UNTIL` | `( flag -- )` | Loops back to `BEGIN` while `flag` is zero. |
|
||||||
|
| `AGAIN` | `( -- )` | Loops back to `BEGIN` unconditionally. Leave with `EXIT` or `ABORT`. |
|
||||||
|
| `WHILE` | `( flag -- )` | In `BEGIN … WHILE … REPEAT`, leaves the loop when `flag` is zero. |
|
||||||
|
| `REPEAT` | `( -- )` | Jumps back to `BEGIN` and resolves the exit of `WHILE`. |
|
||||||
|
| `DO` | `( limit start -- ) ( R: -- limit index )` | Starts a counted loop that always runs at least once. |
|
||||||
|
| `?DO` | `( limit start -- )` | Like `DO`, but skips the loop body when `start = limit`. |
|
||||||
|
| `LOOP` | `( -- )` | Adds 1 to the index and loops while `index < limit`. |
|
||||||
|
| `+LOOP` | `( n -- )` | Adds `n` to the index. For `n ≥ 0` it continues while `index < limit`; for `n < 0` it continues while `index ≥ limit`. |
|
||||||
|
| `LEAVE` | `( -- )` | Exits the innermost `DO` loop immediately: it sets the index to the limit and jumps past `LOOP`. |
|
||||||
|
| `I` | `( -- index )` | Pushes the index of the innermost loop. It is not IMMEDIATE. |
|
||||||
|
| `J` | `( -- index )` | Pushes the index of the next outer loop. |
|
||||||
|
| `UNLOOP` | `( -- ) ( R: limit index -- )` | Drops the loop parameters. Use it before `EXIT` inside a `DO` loop. |
|
||||||
|
| `EXIT` | `( -- )` | Returns from the current colon definition. Using it in interpret mode is an error. |
|
||||||
|
| `CASE` | `( x -- x )` | Starts a case structure. |
|
||||||
|
| `OF` | `( x v -- \| x )` | If `x = v`, drops both and runs the clause; otherwise keeps `x` and skips to the next `OF`. |
|
||||||
|
| `ENDOF` | `( -- )` | Ends an `OF` clause and jumps to `ENDCASE`. |
|
||||||
|
| `ENDCASE` | `( x -- )` | Drops the selector and resolves every `ENDOF` jump. |
|
||||||
|
| `(BRANCH)` | `( -- )` | Runtime: unconditional relative branch. The compiler inserts it; you do not call it directly. |
|
||||||
|
| `(0BRANCH)` | `( flag -- )` | Runtime: branches when `flag` is 0. The compiler inserts it; you do not call it directly. |
|
||||||
|
| `(DO)` `(?DO)` | `( limit start -- )` | Runtime for loop entry. The compiler inserts them; you do not call them directly. |
|
||||||
|
| `(LOOP)` `(+LOOP)` | `( -- )` / `( n -- )` | Runtime for loop increment and test. The compiler inserts them; you do not call them directly. |
|
||||||
|
| `(LEAVE)` | `( -- )` | Runtime for `LEAVE`: sets index to limit. The compiler inserts it; you do not call it directly. |
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```forth
|
||||||
|
: COUNTDOWN ( n -- ) BEGIN DUP . 1- DUP 0= UNTIL DROP ;
|
||||||
|
: TABLE ( -- ) 5 0 DO 5 0 DO I J * 4 .R LOOP CR LOOP ;
|
||||||
|
: COLOR ( n -- ) CASE 0 OF ." red" ENDOF 1 OF ." green" ENDOF ." ?" ENDCASE ;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 19. StarForth extensions
|
||||||
|
`src/word_source/starforth_words.c`. These words are registered in both `FORTH` and the `STARFORTH` vocabulary.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `ENTROPY@` | `( xt -- n )` | Pushes the `execution_heat` counter of a word. The name says "entropy", but the value is execution heat. Registered only in the `STARFORTH` vocabulary. |
|
||||||
|
| `ENTROPY!` | `( n xt -- )` | Sets a word's `execution_heat` counter. Registered only in the `STARFORTH` vocabulary. |
|
||||||
|
| `WORD-ENTROPY` | `( -- )` | Prints the execution heat of every word. |
|
||||||
|
| `RESET-ENTROPY` | `( -- )` | Sets every heat counter to zero. |
|
||||||
|
| `TOP-WORDS` | `( n -- )` | Prints the `n` hottest words. |
|
||||||
|
| `(-` | `( "ccc<)>" -- )` | A comment that marks metadata blocks to extract into `init.4th`. It consumes input up to `)`. |
|
||||||
|
| `INIT` | `( -- )` | Reads `./capsules/core/init.4th`, copies its blocks from block 1 onward, and runs them. |
|
||||||
|
| `VERSION` | `( -- )` | Prints `StarForth v<ver> <arch> <variant> <timestamp>`. |
|
||||||
|
| `SEED` | `( n -- )` | Seeds the PRNG so random sequences can be reproduced. |
|
||||||
|
| `RANDOM` | `( lo hi -- n )` | Pushes a pseudo-random number in `[lo, hi]`, including both ends. |
|
||||||
|
| `WAIT` | `( n -- )` | Waits `n` heartbeat ticks by calling `vm_tick()` `n` times. It counts heartbeats, not wall-clock time, so it behaves the same on amd64, aarch64, and riscv64. |
|
||||||
|
| `HEARTBEAT-TICKS@` | `( -- n )` | Pushes the canonical heartbeat tick count (Loop #7). The project uses this as its clock. It is read-only. |
|
||||||
|
| `ZUSE-AUTHENTICATE` | `( -- )` | Sets `zuse_session = 1`. The write happens only in C. |
|
||||||
|
| `ZUSE-SESSION?` | `( -- flag )` | True if `ZUSE-AUTHENTICATE` has run during this boot. It is read-only. |
|
||||||
|
| `ZUSE-PUBKEY@` | `( i -- u )` | Pushes 8-byte little-endian chunk `i` (0–3) of Zuse's Ed25519 **public** key. An out-of-range `i` pushes 0 and sets an error. The private seed is never exposed. |
|
||||||
|
| `ZUSE-CERT-INSTALLED?` | `( -- flag )` | True once the one-time certificate fuse has been blown. |
|
||||||
|
|
||||||
|
## 20. Word-level ACL
|
||||||
|
`src/word_source/acl_words.c`. Every word takes an `xt`, obtained with `'` or `[']`. Writes are silently ignored for
|
||||||
|
a **pinned** word.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `ACL-MODE@` | `( xt -- mode )` | Pushes the enforcement mode: 0 is STRICT (the decision is permanent) and 1 is TTL (the decision is rechecked when the countdown expires). |
|
||||||
|
| `ACL-MODE!` | `( mode xt -- )` | Sets the enforcement mode. |
|
||||||
|
| `ACL-TTL@` | `( xt -- n )` | Pushes the TTL countdown. At 0 in TTL mode, the interpreter calls `acl_recheck()`. |
|
||||||
|
| `ACL-TTL!` | `( n xt -- )` | Sets the TTL, clamped to `[0, UINT32_MAX]`. |
|
||||||
|
| `ACL-ALLOW@` | `( xt -- flag )` | Pushes the cached decision: `-1` means allowed and `0` means denied. |
|
||||||
|
| `ACL-ALLOW!` | `( flag xt -- )` | Sets the cached decision; any non-zero value means allowed. |
|
||||||
|
| `ACL-PINNED?` | `( xt -- flag )` | True if the ACL fields are pinned and therefore immutable. |
|
||||||
|
| `ACL-PIN` | `( xt -- )` | Pins the word. **This is one-way**: no FORTH word can unpin it. |
|
||||||
|
| `ACL-HEAT@` | `( xt -- heat )` | Pushes the execution heat. `ACL.4th` uses it to calibrate TTLs. |
|
||||||
|
| `ACL-WORD-ID` | `( xt -- id )` | Pushes the word's stable `word_id`. It never changes, so it is safe to use as a table index. |
|
||||||
|
| `ACL-INHERIT` | `( src-xt dst-xt -- )` | Copies the mode from `src` to `dst` and resets `dst`: unpinned, TTL 0, allowed. It is written in C because only C may clear a pin. |
|
||||||
|
| `ACL-INIT-PRIMITIVES` | `( -- )` | For every unpinned word, sets TTL to 0, allow to 1, and mode to TTL. `ACL-BOOT` calls it. |
|
||||||
|
|
||||||
|
## 21. Physics: benchmark and diagnostics
|
||||||
|
`src/word_source/physics_benchmark_words.c`. These are interactive diagnostics that print to the console.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `BENCH-DICT-LOOKUP` | `( iterations -- )` | Benchmarks dictionary lookup and records Q48.16 latencies. Use at least 10,000 iterations; 100,000 is the standard run and 1,000,000 is a stress test. |
|
||||||
|
| `PHYSICS-CACHE-STATS` | `( -- )` | Prints hot-words cache statistics. |
|
||||||
|
| `PHYSICS-TOGGLE-CACHE` | `( -- )` | Turns the hot-words cache on or off, for A/B testing. |
|
||||||
|
| `PHYSICS-RESET-STATS` | `( -- )` | Resets the cache statistics. |
|
||||||
|
| `PHYSICS-BUILD-INFO` | `( -- )` | Prints the variant's build configuration. |
|
||||||
|
| `PHYSICS-BAYESIAN-REPORT` | `( addr -- )` | Prints a Bayesian comparison of the current cache statistics against the baseline stored at `addr`. |
|
||||||
|
|
||||||
|
> `physics_diagnostic_words.c` also defines `PHYSICS-WORD-METRICS`, `PHYSICS-CALC-KNOBS`, `PHYSICS-BURN ( n -- )`
|
||||||
|
> and `PHYSICS-SHOW-FEEDBACK`, but nothing calls `register_physics_diagnostic_words()`, so **none of them are in
|
||||||
|
> the dictionary** at this commit.
|
||||||
|
|
||||||
|
## 22. Physics: pipelining diagnostics
|
||||||
|
`src/word_source/physics_pipelining_diagnostic_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `PIPELINING-SHOW-STATS` | `( "name" -- )` | Prints the word-to-word transition metrics of `name`. |
|
||||||
|
| `PIPELINING-SHOW-TOP-TRANSITIONS` | `( "name" n -- )` | Prints the `n` words that most often follow `name`. |
|
||||||
|
| `PIPELINING-ANALYZE-WORD` | `( "name" -- )` | Prints a full analysis of one word's transitions, with hints for reading them. |
|
||||||
|
| `PIPELINING-STATS` | `( -- )` | Prints pipelining statistics aggregated across the whole dictionary. |
|
||||||
|
| `PIPELINING-RESET-ALL` | `( -- )` | Clears all transition metrics. |
|
||||||
|
| `PIPELINING-ENABLE` | `( -- )` | Placeholder. Pipelining is switched on or off at compile time. |
|
||||||
|
|
||||||
|
## 23. Physics: freeze, heat, and decay
|
||||||
|
`src/word_source/physics_freeze_words.c`. Words are named by `c-addr u` strings, for example `S" DUP" HEAT@`. An
|
||||||
|
unknown name is not an error.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `FREEZE-WORD` | `( c-addr u -- )` | Sets `WORD_FROZEN` on the word, so Loop #3 decay stops lowering its heat. |
|
||||||
|
| `UNFREEZE-WORD` | `( c-addr u -- )` | Clears `WORD_FROZEN` and leaves `WORD_PINNED` alone. |
|
||||||
|
| `FROZEN?` | `( c-addr u -- flag )` | True if the word is frozen. An unknown word gives `0`. |
|
||||||
|
| `HEAT!` | `( heat c-addr u -- )` | Writes `execution_heat` directly, bypassing Loops #1 and #3. For testing only. |
|
||||||
|
| `HEAT@` | `( c-addr u -- heat )` | Reads `execution_heat`. An unknown word gives `0`. |
|
||||||
|
| `SHOW-HEAT` | `( c-addr u -- )` | Prints `NAME: HEAT (frozen) (pinned)`. |
|
||||||
|
| `ALL-HEATS` | `( -- )` | Prints up to 1024 words sorted by heat, hottest first. |
|
||||||
|
| `DECAY-RATE@` | `( -- q )` | Pushes the base decay rate per µs (`DECAY_RATE_PER_US_Q16`) in Q48.16. |
|
||||||
|
| `FREEZE-CRITICAL` | `( -- )` | Freezes 21 core words: `DUP DROP SWAP OVER ROT @ ! C@ C! EXECUTE IF THEN ELSE DO LOOP BEGIN UNTIL REPEAT . EMIT CR`. |
|
||||||
|
|
||||||
|
## 24. Dictionary heat optimisation
|
||||||
|
`src/word_source/dictionary_heat_diagnostic_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `HEAT-PERCENTILES` | `( -- p25 p50 p75 )` | Pushes the current heat percentile thresholds, with the 75th on top. |
|
||||||
|
| `LOOKUP-STRATEGY@` | `( -- n )` | Pushes the lookup strategy: 0 is naive (newest-first linear scan) and 1 is heat-aware (hot bucket first). |
|
||||||
|
| `LOOKUP-STRATEGY!` | `( n -- )` | Sets the strategy. Only 0 or 1 is accepted; other values are silently ignored. |
|
||||||
|
| `REORG-BUCKETS` | `( -- )` | Re-sorts the lookup buckets by heat and refreshes the percentiles immediately, without waiting for the heartbeat. |
|
||||||
|
| `SHOW-HEAT-OPTIMIZATION` | `( -- )` | Prints the strategy, the percentiles, and the hot, warm, and cool zones. |
|
||||||
|
| `COMPARE-LOOKUPS` | `( iterations -- )` | Benchmarks naive against heat-aware lookup and prints the speedup. It restores the original strategy afterwards. |
|
||||||
|
|
||||||
|
## 25. Logging
|
||||||
|
`src/word_source/log_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `LOG-ERROR` `LOG-WARN` `LOG-INFO` `LOG-TEST` `LOG-DEBUG` | `( -- level )` | Push the log level constants. |
|
||||||
|
| `LOG-LEVEL!` | `( level -- )` | Sets the active log filter, clamped to `[LOG-ERROR, LOG-DEBUG]`. |
|
||||||
|
| `LOG-LEVEL@` | `( -- level )` | Pushes the current log level. |
|
||||||
|
| `LOG-ERROR"` `LOG-WARN"` `LOG-INFO"` `LOG-TEST"` `LOG-DEBUG"` **IMM** | `( "ccc<">" -- )` | Log a literal string at that level. In interpret mode the string is logged immediately; when compiling, a runtime word and the inline string are compiled. |
|
||||||
|
| `LOG-ERROR-STR` `LOG-WARN-STR` `LOG-INFO-STR` `LOG-TEST-STR` `LOG-DEBUG-STR` | `( c-addr u -- )` | Log a string taken from the stack at that level. |
|
||||||
|
| `(do-log-error)` `(do-log-warn)` `(do-log-info)` `(do-log-test)` `(do-log-debug)` | `( -- )` | Runtimes for the compiled `LOG-*"` words. The compiler inserts them; you do not call them directly. |
|
||||||
|
| `(LOG-APPEND-RAW)` **K** | `( level timestamp c-addr u -- )` | Appends a raw entry to the kernel log ring, attributed to the calling VM (or `HADES`). It reports errors on the console, not through `log_message`, to avoid recursion. |
|
||||||
|
|
||||||
|
Example: `: CHECK ( n -- ) 0< IF LOG-WARN" negative input" THEN ;`
|
||||||
|
|
||||||
|
## 26. Q48.16 fixed-point math
|
||||||
|
`src/word_source/q48_words.c`. A value is `n × 65536`. The underlying type is **unsigned** `uint64_t`; see
|
||||||
|
[§36](#36-implementation-quirks-to-know) for what that means for negative values.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `Q.+` `Q.-` | `( q1 q2 -- q3 )` | Add and subtract. |
|
||||||
|
| `Q.*` | `( q1 q2 -- q3 )` | Multiplies, computing `(a*b) >> 16`. |
|
||||||
|
| `Q./` | `( q1 q2 -- q3 )` | Divides, computing `(a << 16) / b`. **Division by zero returns 0** and sets no error. |
|
||||||
|
| `Q.ABS` | `( q -- \|q\| )` | Absolute value, treating the top bit as a sign bit. |
|
||||||
|
| `Q.NEG` | `( q -- -q )` | Two's-complement negation. |
|
||||||
|
| `Q.LOG` | `( q -- ln q )` | Natural logarithm by Newton-Raphson. Requires `q > 0`. |
|
||||||
|
| `Q.EXP` | `( q -- e^q )` | Exponential by Taylor series. |
|
||||||
|
| `Q.SQRT` | `( q -- √q )` | Square root by Newton-Raphson. |
|
||||||
|
| `Q.SIN` `Q.COS` | `( q -- q' )` | Sine and cosine of an angle in radians. The argument is reduced to [-π, π] and then a Taylor series is applied. |
|
||||||
|
| `Q.FROM-INT` | `( n -- q )` | Converts an integer to Q48.16 as `n << 16`. **A negative `n` becomes 0.** |
|
||||||
|
| `Q.TO-INT` | `( q -- n )` | Converts to an integer as `q >> 16`, truncating. |
|
||||||
|
| `Q.1` | `( -- 65536 )` | Pushes 1.0. |
|
||||||
|
| `Q.0` | `( -- 0 )` | Pushes 0.0. |
|
||||||
|
| `Q.SCALE` | `( -- 65536 )` | Pushes the scale factor; the same value as `Q.1`. |
|
||||||
|
| `Q.=` | `( q1 q2 -- flag )` | Equality. |
|
||||||
|
| `Q.<` `Q.>` | `( q1 q2 -- flag )` | Comparison, done **unsigned**. |
|
||||||
|
| `Q.0=` | `( q -- flag )` | True if the value is zero. |
|
||||||
|
| `Q.MAX` `Q.MIN` | `( q1 q2 -- q3 )` | Maximum and minimum, compared **unsigned**. |
|
||||||
|
| `Q.PRINT` | `( q -- )` | Prints the value as `int.fffff ` with five fractional digits. |
|
||||||
|
|
||||||
|
Example: `3 Q.FROM-INT Q.SQRT Q.PRINT` prints approximately `1.732`.
|
||||||
|
|
||||||
|
## 27. Inference engine (SSM, L8, and Bayes)
|
||||||
|
`src/word_source/inference_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `INFER-RUN` | `( -- )` | Runs the full inference engine on this VM's rolling window and dictionary heat, and caches the results. |
|
||||||
|
| `INFER-WINDOW@` | `( -- u )` | Pushes the last inferred optimal window width. |
|
||||||
|
| `INFER-DECAY@` | `( -- q )` | Pushes the last inferred decay slope. |
|
||||||
|
| `INFER-VARIANCE@` | `( -- q )` | Pushes the last inferred variance. |
|
||||||
|
| `INFER-FIT@` | `( -- q )` | Pushes the last fit quality. |
|
||||||
|
| `INFER-EARLY-EXIT@` | `( -- flag )` | True if the last run exited early. |
|
||||||
|
| `Q.VARIANCE` | `( addr u -- q )` | Pushes the variance of `u` uint64 cells at `addr`. |
|
||||||
|
| `INFER-DECAY-SLOPE` | `( addr u -- q )` | Fits a decay slope to the array by linear regression. |
|
||||||
|
| `INFER-WINDOW-WIDTH` | `( addr u -- n )` | Computes the optimal window width for the array. |
|
||||||
|
| `WINDOW-DIVERSITY` | `( -- u )` | Pushes the number of distinct words in the rolling window. |
|
||||||
|
| `L8-MODE` | `( -- n )` | Pushes the current L8 (legacy 16-mode) Jacquard selection. |
|
||||||
|
| `L8-UPDATE` | `( entropy cv temporal stability -- )` | Feeds four Q48.16 metrics to `ssm_l8_update()`. `stability` is on top of the stack. |
|
||||||
|
| `L8-APPLY` | `( -- )` | Applies the currently selected L8 mode. |
|
||||||
|
| `L8-TABLE-FORCE` | `( idx -- )` | Forces the adaptive 128-config table to `idx & 127` as though the UCB bandit had picked it, and applies it. Unlike `L8-UPDATE` and `L8-APPLY`, this choice is not overwritten at the next heartbeat trial. Use it for DoE campaigns. |
|
||||||
|
| `BAYES-CACHE-MEAN` `BAYES-CACHE-LOWER` `BAYES-CACHE-UPPER` | `( -- q )` | Push the posterior mean latency and the 95 % credible bounds for hot-words cache hits. |
|
||||||
|
| `BAYES-BUCKET-MEAN` `BAYES-BUCKET-LOWER` `BAYES-BUCKET-UPPER` | `( -- q )` | Push the same three values for bucket searches. |
|
||||||
|
|
||||||
|
## 28. DEFER and IS
|
||||||
|
`src/word_source/defer_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `DEFER` | `( "name" -- )` | Creates a vectored word. Running it before an action has been set with `IS` sets `vm->error`. |
|
||||||
|
| `IS` | `( xt "name" -- )` | Sets the action of `name`. It is refused unless `name` was created by `DEFER`. |
|
||||||
|
| `DEFER@` | `( "name" -- xt )` | Pushes the current action of a deferred word. |
|
||||||
|
|
||||||
|
Example: `DEFER GREET : HI ." hi" ; ' HI IS GREET GREET`
|
||||||
|
|
||||||
|
## 29. Framebuffer (Hestia only)
|
||||||
|
`src/word_source/framebuffer_words.c`. These words are registered only in the Hestia VM, by `capsule_birth_baby()`,
|
||||||
|
and not by `register_forth79_words()`.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `PLOT` | `( x y color -- )` | Writes one raw pixel. The origin is top-left and Y increases downward. |
|
||||||
|
| `FB-WIDTH` | `( -- n )` | Pushes the framebuffer width in pixels. |
|
||||||
|
| `FB-HEIGHT` | `( -- n )` | Pushes the framebuffer height in pixels. |
|
||||||
|
|
||||||
|
## 30. Keyboard
|
||||||
|
`src/word_source/keyboard_words.c`. These are diagnostics for the console fabric. On a platform without the device,
|
||||||
|
each word pushes `0`.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `KBD-SCAN` | `( -- sc -1 \| 0 )` | amd64 i8042: pops one raw XT scancode from the queue, if there is one. |
|
||||||
|
| `KBD-DEBUG` | `( -- isr spurious )` | amd64: pushes the i8042 interrupt count and the spurious-interrupt count. |
|
||||||
|
| `VKBD-EVENT` | `( -- code value -1 \| 0 )` | riscv64 and aarch64 virtio-input: pops one Linux-style `EV_KEY` code and value. |
|
||||||
|
| `VKBD-DEBUG` | `( -- isr )` | riscv64 and aarch64: pushes the virtio-input interrupt count. |
|
||||||
|
| `KEY-EVENT` | `( -- keycode pressed -1 \| 0 )` | The unified event on every architecture: pops one keycode with its pressed (1) or released (0) state. |
|
||||||
|
| `ALT+TAB` | `( -- )` | Switches the console between text and graphics, the same as the physical Alt+Tab. |
|
||||||
|
|
||||||
|
## 31. TrueType text
|
||||||
|
`src/word_source/ttf_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `TTF-TEXT` **K** | `( c-addr u x y size color -- )` | Draws the string with the TrueType renderer at pixel `(x, y)` in the given size and color. |
|
||||||
|
|
||||||
|
## 32. REPL scrollback
|
||||||
|
`src/word_source/scroll_words.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `SCROLL-BACK` **K** | `( n -- )` | Scrolls the REPL view back `n` lines. |
|
||||||
|
| `SCROLL-FWD` **K** | `( n -- )` | Scrolls the REPL view forward `n` lines, toward the live output. |
|
||||||
|
|
||||||
|
## 33. Kernel REPL and DoE hooks
|
||||||
|
`src/starkernel/repl.c` and `src/starkernel/doe_log.c`
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `HB-ON` **K** | `( -- )` | Turns on per-tick DoE instrumentation. |
|
||||||
|
| `HB-OFF` **K** | `( -- )` | Turns off per-tick DoE instrumentation. |
|
||||||
|
| `BLK-ATTACH-ACK` **K** | `( dev-ptr ok? -- )` | The acknowledgement Artemis sends to Hera after `BLK-ATTACH`, delivered with `VM-EXEC`. On success, Hera runs the deferred Zuse or WIREBIND attach. |
|
||||||
|
| `KH-BLK-ATTACH-SEND` **K** | `( c-addr u -- ok? )` | Sends a payload to Hera through kernel-Hermes. The sender and receiver are worked out in C, so the caller cannot spoof them. A refused send is logged. |
|
||||||
|
| `KH-ELEVATE-SEND` **K** | `( c-addr u -- ok? )` | The same mechanism for elevation requests. Nothing calls it at present. |
|
||||||
|
|
||||||
|
## 34. Hera (Mama) and child-VM words
|
||||||
|
`src/starkernel/capsule/mama_forth_words.c`. These are **K** only. Hera receives every word in this section, in
|
||||||
|
both `FORTH` and the `MAMA` vocabulary. Child VMs receive only `STOP EXEC USE BIRTH CAPSULE-BIRTH VM-EXEC VM-CALL
|
||||||
|
VM-HEAT VM-ERROR? SWITCH-MARK-WORK` and the `STADIUM-*` words. VM names are matched without regard to case.
|
||||||
|
|
||||||
|
### VM lifecycle
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `BIRTH` | `( c-addr u -- )` | Births a VM from its capsule (`S" Artemis"` loads `artemis:init.4th`). If the VM is already live, nothing happens. `Hera` is rejected. |
|
||||||
|
| `KILL` | `( c-addr u -- )` | Destroys a VM. Hera cannot be killed, and killing a dead VM does nothing. |
|
||||||
|
| `START` | `( c-addr u -- )` | Enters the VM's REPL and blocks until it runs `STOP` or `BYE`. It refuses a VM that is LIVE, DEAD, or STILLBORN. |
|
||||||
|
| `STOP` | `( -- )` | Halts the current VM, so its REPL loop returns. |
|
||||||
|
| `USE` | `( c-addr u -- )` | Redirects console input to the named VM without nesting the C stack, and changes the prompt to `[Name]`. `S" Hera" USE` switches back to Hera. |
|
||||||
|
| `EXEC` | `( c-addr u -- )` | Runs a named capsule inside the current VM. |
|
||||||
|
| `EJECT` | `( -- )` | Cleanly detaches the identity attached through USB home blocks: it flushes the user VM and kills it, or logs Zuse out. |
|
||||||
|
| `CONNECT-HERMES` | `( -- )` | Enters Hermes's REPL, birthing Hermes first if needed. |
|
||||||
|
| `CONNECT-ARTEMIS` | `( -- )` | Enters Artemis's REPL, birthing Artemis first if needed. |
|
||||||
|
| `BYE` | `( -- )` | **On Hera:** reaps every child and then cold-resets the machine. |
|
||||||
|
|
||||||
|
### Cross-VM execution (compudynamics)
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `VM-EXEC` | `( cmd-a cmd-u name-a name-u -- )` | Injects a command into the named VM and runs it immediately without blocking. Example: `S" DOE-WORK" S" Hermes" VM-EXEC`. |
|
||||||
|
| `VM-CALL` | `( cmd-a cmd-u name-a name-u -- n )` | Like `VM-EXEC`, then pops the target's top of stack onto the caller's stack. If the target left nothing, it pushes 0 and sets an error. |
|
||||||
|
| `VM-STEP` | `( c-addr u -- )` | Gives the named VM one REPL turn: one prompt, one line, then it returns. |
|
||||||
|
| `VM-HEAT` | `( c-addr u -- q )` | Pushes the VM's `execution_heat_q48`. An unknown VM gives 0 and prints nothing. |
|
||||||
|
| `VM-ERROR?` | `( c-addr u -- flag )` | True if the VM has `vm->error` set. An unknown VM gives `0`. |
|
||||||
|
| `VM-COUNT` | `( -- n )` | Pushes the number of registered VMs. |
|
||||||
|
| `VM-CONSERVED?` | `( -- flag )` | True if the total fleet heat is within ε of `Q.1`. |
|
||||||
|
| `VM-PHYSICS-STATUS` | `( -- )` | Prints the fleet physics report. |
|
||||||
|
| `SWITCH-MARK-WORK` | `( c-addr u -- )` | Marks a VM as having work, which makes it eligible for a context switch. The message path calls it, and it ignores bad names silently. |
|
||||||
|
| `MAMA-VM-ID` | `( -- 0 0 )` | Pushes Hera's 128-bit VM ID, which is all zeros. |
|
||||||
|
| `NAME>XT` | `( c-addr u -- xt \| 0 )` | Looks up a name held in a data buffer. A miss gives `0`. |
|
||||||
|
|
||||||
|
### Capsules
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `CAPSULE-COUNT` | `( -- n )` | Pushes the number of entries in the capsule directory. |
|
||||||
|
| `CAPSULE@` | `( idx -- desc \| 0 )` | Pushes the descriptor for the capsule at `idx`. |
|
||||||
|
| `CAPSULE-HASH@` | `( desc -- hash )` | Pushes the capsule's content hash. |
|
||||||
|
| `CAPSULE-FLAGS@` | `( desc -- flags )` | Pushes the capsule's flags. |
|
||||||
|
| `CAPSULE-LEN@` | `( desc -- len )` | Pushes the capsule's payload length. |
|
||||||
|
| `CAPSULE-BIRTH` | `( id -- vmid-lo vmid-hi )` | Births an unnamed VM from a production (p) capsule and pushes its 128-bit ID. On failure both cells are all ones. |
|
||||||
|
| `CAPSULE-RUN` | `( id -- )` | Runs an experiment (e) capsule on Hera. |
|
||||||
|
| `CAPSULE-TEST` | `( -- )` | Prints a message confirming the capsule system is running. |
|
||||||
|
| `WORKER-BIRTH` | `( cap-a cap-u name-a name-u -- ok? )` | Births a named, `VM-EXEC`-addressable worker from any p-capsule, with no identity attached. |
|
||||||
|
| `UNATTENDED-BIRTH` | `( cap-a cap-u name-a name-u -- ok? )` | Births a VM and installs the verified identity whose `UNATTENDED-ID-UUID` and `UNATTENDED-ID-CERT` the capsule defined. |
|
||||||
|
| `CONSOLE-ATTACH` | `( name-a name-u -- ok? )` | Pairs a new console VM with the live VM registered as `<name>~user`. |
|
||||||
|
|
||||||
|
### Identity, Zuse, and diagnostics
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `MINT` | `( fn-a fn-u un-a un-u em-a em-u ph-a ph-u restrict? -- ok? )` | Mints an identity onto the attached USB drive. The full name and username are required; pass an empty string for email or phone to leave them out. A non-zero `restrict?` selects the locked-down personality that allows only FORTH-79 and FORTH-83 words. |
|
||||||
|
| `MINT-SCRATCH` | same as `MINT` | Mints onto the scratch device instead, prints the UUID, and pushes 1 or 0. |
|
||||||
|
| `MINT-SCRATCH-EMIT` | `( -- ok? )` | Prints the last scratch mint as FORTH source (`CREATE UNATTENDED-ID-UUID` / `-CERT` byte lists) for copying by hand. It refuses (pushes 0) if no mint has succeeded. |
|
||||||
|
| `ZUSE-ELIGIBILITY-ADD` | `( c-addr -- ok? )` | Adds the 32-byte Ed25519 public key at `c-addr` to Zuse's elevation list. |
|
||||||
|
| `ZUSE-ELIGIBLE?` | `( c-addr -- flag )` | Checks whether a key is on the list. It fails closed. |
|
||||||
|
| `ELEVATE-PUBKEY-UNPACK` | `( pk0 pk1 pk2 pk3 buf -- )` | Rebuilds a 32-byte public key from four cells. It is the inverse of `ZUSE-PUBKEY@`. |
|
||||||
|
| `RUNCAP-TEST` | `( c-addr u -- ok? rc )` | Diagnostic: runs `capsule_runcap_birth()` against the current home-blocks drive. |
|
||||||
|
| `PAIR-TEST` | `( c-addr u -- ok? )` | Diagnostic: births a console VM and a `<name>~user` VM as a pair. |
|
||||||
|
|
||||||
|
### Stadium (heat accounting)
|
||||||
|
Heat values are Q48.16. Each word works only on the calling VM's own quota.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `STADIUM-ADMIT` | `( identity heat behaviour -- cell \| -1 )` | Admits a patron. `behaviour` must be 0–3. |
|
||||||
|
| `STADIUM-EVICT` | `( cell -- flag )` | Reaps the patron in `cell`. It is refused if the cell is out of range, not resident, pinned, or blocked by what it contains. |
|
||||||
|
| `STADIUM-HEAT@` | `( cell -- heat )` | Reads a resident cell's heat. A cell that is not the caller's gives 0. |
|
||||||
|
| `STADIUM-HEAT!` | `( heat cell -- )` | Writes a cell's heat, pulling the difference from the reservoir or pushing it back. An increase the reservoir cannot cover is silently refused. |
|
||||||
|
| `STADIUM-RES@` | `( -- heat )` | Pushes the VM's reservoir balance. |
|
||||||
|
| `STADIUM-RES-PULL` | `( qty -- got )` | Pulls up to `qty` from the reservoir and pushes the amount actually taken. |
|
||||||
|
| `STADIUM-RES-PUSH` | `( heat -- )` | Credits heat back to the reservoir. |
|
||||||
|
| `STADIUM-WORD-HEAT` | `( -- heat )` | Pushes the total heat held by this VM's word-execution residents. |
|
||||||
|
|
||||||
|
## 35. Hosted lifecycle stubs
|
||||||
|
`src/word_source/lifecycle_words_hosted.c`. These are **H** only; `main.c` registers them. Each word only logs
|
||||||
|
`"<WORD> <name> (hosted)"`. They exist so that capsule scripts also parse in hosted builds.
|
||||||
|
|
||||||
|
| Word | Stack | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `BIRTH` `KILL` `PAUSE` `RESUME` `USE` | `( c-addr u -- )` | No-op stubs that only write a log line. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 36. Implementation quirks to know
|
||||||
|
|
||||||
|
These behaviours differ from what a FORTH-79 or ANS programmer would expect. Each was checked against the C source.
|
||||||
|
|
||||||
|
1. **`ROLL` counts from the bottom of the stack.** With `1 2 3` on the stack, `1 ROLL` gives `2 3 1`; ANS gives
|
||||||
|
`1 3 2`. The test suite (`stack_words_test.c`) asserts the current behaviour, so it looks intended. Portable code
|
||||||
|
should use `SWAP` and `ROT`.
|
||||||
|
2. **`PICK` is 0-based,** as in ANS. FORTH-79's `PICK` was 1-based.
|
||||||
|
3. **`FIND` parses the input stream.** It does not take a counted string. Use `(FIND)` for a counted string or
|
||||||
|
`NAME>XT` (kernel only) for a name in a buffer.
|
||||||
|
4. **The Q48.16 type is unsigned.** `Q.<`, `Q.>`, `Q.MIN`, `Q.MAX` and `Q.PRINT` treat a negative Q value as a huge
|
||||||
|
positive one, and `Q.FROM-INT` turns a negative integer into 0. `Q.ABS` and `Q.NEG` do treat the top bit as a sign.
|
||||||
|
5. **`Q./` by zero returns 0 without setting an error,** while the integer `/`, `MOD`, and `*/` all set `vm->error`.
|
||||||
|
6. **`[LITERAL]` does nothing,** and `LITERAL` works only because §17 registers it again after the placeholder.
|
||||||
|
7. **`MOD`, `/MOD`, `*/`, and `*/MOD` are registered twice.** The mixed-arithmetic versions (§6) are the ones used.
|
||||||
|
8. **Four `PHYSICS-*` diagnostic words are not registered.** `PHYSICS-WORD-METRICS`, `PHYSICS-CALC-KNOBS`,
|
||||||
|
`PHYSICS-BURN` and `PHYSICS-SHOW-FEEDBACK` are defined in C but never added to the dictionary.
|
||||||
|
9. **`does_rt` is a visible dictionary entry.** It is an internal helper; do not call it.
|
||||||
|
10. **The `STARFORTH` vocabulary registers its words twice** (once in `FORTH`, once in `STARFORTH`). Hera does the
|
||||||
|
same with `MAMA`. As a result, those names appear twice in `WORDS` output.
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# docs/book/Makefile -- the single LithosAnanke book, built from LaTeX.
|
||||||
|
#
|
||||||
|
# Run from the repo root (the root Makefile does this):
|
||||||
|
# make docs -> build/docs/LithosAnanke.pdf
|
||||||
|
# make docs TARGET=<board> -> build/docs/LithosAnanke-<board>.pdf
|
||||||
|
# (same book + that board's appendix)
|
||||||
|
#
|
||||||
|
# docs/book/main.tex is the master. Markdown sources are converted by pandoc
|
||||||
|
# into LaTeX fragments under build/docs/gen/ and \input from main.tex; the
|
||||||
|
# Markdown stays the source of truth until a chapter is rewritten in LaTeX.
|
||||||
|
# Figures and tables that present data read the CSV directly at LaTeX time
|
||||||
|
# (pgfplots / pgfplotstable) and cite it with \datasource{path}; see
|
||||||
|
# docs/book/README.md.
|
||||||
|
|
||||||
|
BOOK_DIR := docs/book
|
||||||
|
OUT := build/docs
|
||||||
|
GEN := $(OUT)/gen
|
||||||
|
|
||||||
|
BOARD ?=
|
||||||
|
BOARD_NAME ?=
|
||||||
|
VARIANT := $(if $(BOARD),$(BOARD),book)
|
||||||
|
VAR_DIR := $(OUT)/$(VARIANT)
|
||||||
|
PDF := $(OUT)/LithosAnanke$(if $(BOARD),-$(BOARD)).pdf
|
||||||
|
|
||||||
|
PANDOC ?= pandoc
|
||||||
|
LATEXMK ?= latexmk
|
||||||
|
PANDOC_FILTERS := $(BOOK_DIR)/pandoc/table-widths.lua $(BOOK_DIR)/pandoc/code-breaks.lua
|
||||||
|
PANDOC_FLAGS := -f gfm -t latex --top-level-division=chapter --wrap=preserve \
|
||||||
|
$(foreach f,$(PANDOC_FILTERS),--lua-filter=$(f))
|
||||||
|
|
||||||
|
# fragment name : Markdown source. The order of \input lines is main.tex's.
|
||||||
|
CHAPTERS := \
|
||||||
|
ontology:docs/ONTOLOGY.md \
|
||||||
|
roadmap:docs/ROADMAP.md \
|
||||||
|
boards:boards/README.md \
|
||||||
|
justification:docs/v4.0.0/JUSTIFICATION.md \
|
||||||
|
decomposition:docs/v4.0.0/DECOMPOSITION.md \
|
||||||
|
fabric-0:docs/fabric/FABRIC-0.md \
|
||||||
|
fabric-1:docs/fabric/FABRIC-1.md \
|
||||||
|
fabric-2:docs/fabric/FABRIC-2.md \
|
||||||
|
fabric-3:docs/fabric/FABRIC-3.md \
|
||||||
|
fabric-3-5:docs/fabric/FABRIC-3.5.md \
|
||||||
|
fabric-3-6:docs/fabric/FABRIC-3.6.md \
|
||||||
|
fabric-3-7:docs/fabric/FABRIC-3.7.md \
|
||||||
|
fabric-4:docs/fabric/FABRIC-4.md
|
||||||
|
|
||||||
|
ifneq ($(BOARD),)
|
||||||
|
ifeq ($(wildcard boards/$(BOARD)/README.md),)
|
||||||
|
$(error boards/$(BOARD)/README.md is missing: it is the board's appendix)
|
||||||
|
endif
|
||||||
|
CHAPTERS += board-$(BOARD):boards/$(BOARD)/README.md
|
||||||
|
endif
|
||||||
|
|
||||||
|
chapter_name = $(word 1,$(subst :, ,$(1)))
|
||||||
|
chapter_src = $(word 2,$(subst :, ,$(1)))
|
||||||
|
FRAGMENTS := $(foreach c,$(CHAPTERS),$(GEN)/$(call chapter_name,$(c)).tex)
|
||||||
|
|
||||||
|
.PHONY: all tools FORCE
|
||||||
|
all: $(PDF)
|
||||||
|
|
||||||
|
tools:
|
||||||
|
@missing=""; \
|
||||||
|
for t in $(PANDOC) $(LATEXMK) xelatex; do command -v $$t >/dev/null 2>&1 || missing="$$missing $$t"; done; \
|
||||||
|
if [ -n "$$missing" ]; then \
|
||||||
|
echo "Error: make docs needs:$$missing"; \
|
||||||
|
echo " sudo apt-get install -y pandoc latexmk texlive-xetex texlive-latex-extra texlive-pictures texlive-fonts-recommended fonts-dejavu fonts-dejavu-extra"; \
|
||||||
|
echo " (or a user-local TinyTeX + pandoc in ~/.local; see docs/book/README.md)"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --id-prefix keeps heading labels unique across documents that reuse
|
||||||
|
# section names ("Summary", "Open questions", ...).
|
||||||
|
define chapter_rule
|
||||||
|
$(GEN)/$(call chapter_name,$(1)).tex: $(call chapter_src,$(1)) $(PANDOC_FILTERS) | tools
|
||||||
|
@mkdir -p $(GEN)
|
||||||
|
@echo " PANDOC $$< -> $$@"
|
||||||
|
@$(PANDOC) $(PANDOC_FLAGS) --id-prefix=$(call chapter_name,$(1))- $$< -o $$@
|
||||||
|
endef
|
||||||
|
$(foreach c,$(CHAPTERS),$(eval $(call chapter_rule,$(c))))
|
||||||
|
|
||||||
|
# Pandoc's syntax-highlighting macros, taken from the installed pandoc so the
|
||||||
|
# fragments and their macros always come from the same version.
|
||||||
|
$(GEN)/pandoc-highlighting.tex: $(BOOK_DIR)/pandoc/highlighting.latex | tools
|
||||||
|
@mkdir -p $(GEN)
|
||||||
|
@printf '```c\nx\n```\n' | $(PANDOC) -f gfm -t latex -s --template=$< -o $@
|
||||||
|
|
||||||
|
# Per-build facts the book prints: the commit every \datasource refers to,
|
||||||
|
# and which board appendix (if any) is included.
|
||||||
|
$(VAR_DIR)/meta.tex: FORCE
|
||||||
|
@mkdir -p $(VAR_DIR)
|
||||||
|
@{ \
|
||||||
|
c=$$(git rev-parse --short=12 HEAD 2>/dev/null || echo unknown); \
|
||||||
|
git diff --quiet HEAD -- 2>/dev/null || c="$$c (+ uncommitted changes)"; \
|
||||||
|
printf '\\newcommand{\\bookcommit}{%s}\n' "$$c"; \
|
||||||
|
printf '\\newcommand{\\bookdate}{%s}\n' "$$(date -u +%Y-%m-%d)"; \
|
||||||
|
printf '\\def\\bookboard{%s}\n' "$(BOARD_NAME)"; \
|
||||||
|
$(if $(BOARD),printf '\\newcommand{\\bookboardappendix}{board-%s}\n' "$(BOARD)";) \
|
||||||
|
} > $@.tmp
|
||||||
|
@cmp -s $@.tmp $@ && rm -f $@.tmp || mv $@.tmp $@
|
||||||
|
|
||||||
|
FORCE:
|
||||||
|
|
||||||
|
TEXINPUTS_BOOK := $(abspath $(VAR_DIR)):$(abspath $(GEN)):$(abspath $(BOOK_DIR)):
|
||||||
|
|
||||||
|
$(PDF): $(BOOK_DIR)/main.tex $(FRAGMENTS) $(GEN)/pandoc-highlighting.tex $(VAR_DIR)/meta.tex | tools
|
||||||
|
@echo " LATEXMK $(BOOK_DIR)/main.tex -> $@"
|
||||||
|
@TEXINPUTS=$(TEXINPUTS_BOOK) $(LATEXMK) -xelatex -interaction=nonstopmode -halt-on-error \
|
||||||
|
-file-line-error -outdir=$(VAR_DIR) $(BOOK_DIR)/main.tex > $(VAR_DIR)/latexmk.log 2>&1 || { \
|
||||||
|
grep -A4 -E '^(.*:[0-9]+:|!)' $(VAR_DIR)/main.log | head -40; \
|
||||||
|
echo "Error: LaTeX failed; full log: $(VAR_DIR)/main.log"; exit 1; }
|
||||||
|
@cp $(VAR_DIR)/main.pdf $@
|
||||||
|
@echo " PDF $@"
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# docs/book/
|
||||||
|
|
||||||
|
`make docs` builds one PDF, `build/docs/LithosAnanke.pdf`, from `main.tex` with xelatex.
|
||||||
|
`make docs TARGET=<board>` builds `build/docs/LithosAnanke-<board>.pdf`: the same book with
|
||||||
|
`boards/<board>/README.md` added as an appendix.
|
||||||
|
|
||||||
|
Tools: `sudo apt-get install -y pandoc latexmk texlive-xetex texlive-latex-extra texlive-pictures texlive-fonts-recommended fonts-dejavu fonts-dejavu-extra`
|
||||||
|
|
||||||
|
Without root, a user-local TeX Live works too (this is how the book was first built):
|
||||||
|
TinyTeX (`curl -sL https://yihui.org/tinytex/install-bin-unix.sh | sh`) plus
|
||||||
|
`tlmgr install latexmk xetex fontspec pgf pgfplots booktabs multirow fancyvrb fvextra lineno upquote ulem enumitem newunicodechar bookmark hyperref geometry xcolor graphics tools etoolbox fancyhdr truncate amsfonts`,
|
||||||
|
and the pandoc release tarball unpacked into `~/.local`.
|
||||||
|
|
||||||
|
The installed DejaVu decides italics: `fonts-dejavu-core` alone has no serif italic, so
|
||||||
|
`main.tex` falls back to a slanted upright face; `fonts-dejavu-extra` gives real italics.
|
||||||
|
|
||||||
|
## How it fits together
|
||||||
|
|
||||||
|
- `main.tex` is the master file. It sets the parts and the chapter order.
|
||||||
|
- Chapters that are still Markdown are listed in `Makefile` (`CHAPTERS`, as `name:source.md`).
|
||||||
|
At build time pandoc converts each one into `build/docs/gen/<name>.tex`, and `main.tex`
|
||||||
|
includes it with `\input{<name>}`. The Markdown stays the source until the chapter is
|
||||||
|
rewritten in LaTeX. At that point the `.tex` moves into this directory and the `CHAPTERS`
|
||||||
|
entry is removed.
|
||||||
|
- `pandoc/highlighting.latex` extracts the code-highlighting macros from the installed pandoc,
|
||||||
|
so the macros and the fragments always come from the same pandoc version.
|
||||||
|
- Two Lua filters run on every chapter. `pandoc/table-widths.lua` gives wide tables
|
||||||
|
proportional wrapping columns, because pandoc's gfm reader leaves column widths unset.
|
||||||
|
`pandoc/code-breaks.lua` lets long inline identifiers and paths break after `_ / . - :`.
|
||||||
|
Code blocks wrap through fvextra (`breaklines`).
|
||||||
|
- `build/docs/<book|board>/meta.tex` is written on every build. It records the commit (and
|
||||||
|
whether there were uncommitted changes), the date, and the board.
|
||||||
|
|
||||||
|
## Rule for data
|
||||||
|
|
||||||
|
Any figure or table that shows measured numbers is generated from the CSV when the book is
|
||||||
|
built. Numbers are never typed in by hand:
|
||||||
|
|
||||||
|
```latex
|
||||||
|
\begin{tikzpicture}
|
||||||
|
\begin{axis}[xlabel=run, ylabel=K]
|
||||||
|
\addplot table[col sep=comma, x=run, y=K]{experiments/<campaign>/results.csv};
|
||||||
|
\end{axis}
|
||||||
|
\end{tikzpicture}
|
||||||
|
\datasource{experiments/<campaign>/results.csv}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `\pgfplotstabletypeset[col sep=comma]{...}` for tables. `\datasource` prints the CSV path
|
||||||
|
and the commit, so a reader can find the exact file the figure was drawn from. CSV paths are
|
||||||
|
repo-relative, because xelatex runs from the repo root.
|
||||||
|
|
||||||
|
## Older pipelines to fold in or retire
|
||||||
|
|
||||||
|
These still exist and still have their own targets. Each one needs a decision during the
|
||||||
|
curation pass: move it into this book, or retire it.
|
||||||
|
|
||||||
|
| Source | Current target | Produces |
|
||||||
|
|---|---|---|
|
||||||
|
| `docs/formal/vol1-vm-physics`, `vol2-kernel`, `vol3-research` | `make -C docs/formal vols` | three volume PDFs |
|
||||||
|
| `docs/formal/dev-guide`, `user-guide`, `cookbook` | `make -C docs/formal books` | three practitioner PDFs |
|
||||||
|
| `docs/formal/experiments`, `proofs`, `ssrn`, `patent` | `make -C docs/formal standalone` | four standalone PDFs |
|
||||||
|
| Doxygen (`Doxyfile`) | `make -C docs/formal doxygen` | API reference PDF |
|
||||||
|
| `scripts/generate-doxygen-appendix.sh` | `make -f v3/Makefile api-docs` | AsciiDoc API appendix |
|
||||||
|
| `docs/src/internal/formal/*.thy` | `make -f v3/Makefile docs-isabelle` | Isabelle report |
|
||||||
|
| `scripts/asciidoc-to-latex.sh` | `make -f v3/Makefile docs-latex` | `docs/latex/` |
|
||||||
|
| `docs/SSRN_companion/Math_Companion_SSRN.tex` | `make -f v3/Makefile math-companion` | SSRN math companion |
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
% docs/book/main.tex -- master file of the single LithosAnanke book.
|
||||||
|
%
|
||||||
|
% Built by `make docs [TARGET=<board>]` (docs/book/Makefile) with xelatex.
|
||||||
|
% Fragments named below (ontology, fabric-0, ...) are generated by pandoc
|
||||||
|
% from the Markdown listed in docs/book/Makefile's CHAPTERS into
|
||||||
|
% build/docs/gen/; meta.tex is generated per build. Neither is committed.
|
||||||
|
\documentclass[11pt,openany]{book}
|
||||||
|
|
||||||
|
\usepackage{amsmath,amssymb}
|
||||||
|
\usepackage{fontspec}
|
||||||
|
% fonts-dejavu-core has no serif/sans italics (fonts-dejavu-extra does); when a
|
||||||
|
% shape is missing, slant the upright one rather than silently drop emphasis.
|
||||||
|
\setmainfont{DejaVu Serif}[AutoFakeSlant=0.2]
|
||||||
|
\setsansfont{DejaVu Sans}[AutoFakeSlant=0.2]
|
||||||
|
\setmonofont{DejaVu Sans Mono}[Scale=0.85]
|
||||||
|
\newfontfamily\symbolfont{DejaVu Sans}
|
||||||
|
|
||||||
|
\usepackage[letterpaper,margin=1in]{geometry}
|
||||||
|
\usepackage{xcolor}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{longtable,booktabs,array,calc,multirow}
|
||||||
|
\usepackage{fancyvrb}
|
||||||
|
\usepackage[normalem]{ulem}
|
||||||
|
\usepackage{enumitem}
|
||||||
|
\usepackage{newunicodechar}
|
||||||
|
\usepackage{pgfplots}
|
||||||
|
\usepackage{pgfplotstable}
|
||||||
|
\pgfplotsset{compat=1.18}
|
||||||
|
\usepackage[hidelinks]{hyperref}
|
||||||
|
\usepackage{bookmark}
|
||||||
|
|
||||||
|
% --- what pandoc's LaTeX fragments expect (normally from its own template)
|
||||||
|
\providecommand{\tightlist}{\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
|
||||||
|
\providecommand{\pandocbounded}[1]{#1}
|
||||||
|
\providecommand{\st}[1]{\sout{#1}}
|
||||||
|
\newcounter{none} % pandoc: {\def\LTcaptype{none} ...} marks unnumbered tables
|
||||||
|
\usepackage{etoolbox}
|
||||||
|
\makeatletter
|
||||||
|
\def\fnum@table{\tablename~\thetable}
|
||||||
|
\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{}
|
||||||
|
\makeatother
|
||||||
|
\usepackage{fvextra}
|
||||||
|
\input{pandoc-highlighting}
|
||||||
|
% Code and diagrams are wider than the page in places: wrap, never overflow.
|
||||||
|
\RecustomVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\},breaklines,breakanywhere,fontsize=\small}
|
||||||
|
\RecustomVerbatimEnvironment{verbatim}{Verbatim}{breaklines,breakanywhere,fontsize=\small}
|
||||||
|
\setlistdepth{9}
|
||||||
|
\renewlist{itemize}{itemize}{9}
|
||||||
|
\setlist[itemize]{label=\textbullet}
|
||||||
|
\renewlist{enumerate}{enumerate}{9}
|
||||||
|
\setlist[enumerate]{label=\arabic*.}
|
||||||
|
% Running heads: chapter on the left page, section on the right, both cut to
|
||||||
|
% the page width (several design-record titles are a full sentence long).
|
||||||
|
\usepackage{fancyhdr}
|
||||||
|
\usepackage[fit]{truncate}
|
||||||
|
\pagestyle{fancy}
|
||||||
|
\fancyhf{}
|
||||||
|
\renewcommand{\chaptermark}[1]{\markboth{#1}{}}
|
||||||
|
\renewcommand{\sectionmark}[1]{\markright{#1}}
|
||||||
|
\fancyhead[LE]{\small\truncate{\dimexpr\headwidth-3em}{\leftmark}}
|
||||||
|
\fancyhead[RO]{\small\truncate{\dimexpr\headwidth-3em}{\rightmark}}
|
||||||
|
\fancyhead[RE,LO]{\small\thepage}
|
||||||
|
\renewcommand{\headrulewidth}{0.4pt}
|
||||||
|
\setlength{\headheight}{14pt}
|
||||||
|
\fancypagestyle{plain}{\fancyhf{}\fancyfoot[C]{\small\thepage}\renewcommand{\headrulewidth}{0pt}}
|
||||||
|
\setlength{\emergencystretch}{3em}
|
||||||
|
\setlength{\parindent}{0pt}
|
||||||
|
\setlength{\parskip}{0.5em}
|
||||||
|
|
||||||
|
% --- glyphs DejaVu Serif lacks: the status marks used across the docs
|
||||||
|
\newunicodechar{✅}{{\symbolfont ✔}}
|
||||||
|
\newunicodechar{✓}{{\symbolfont ✓}}
|
||||||
|
\newunicodechar{❌}{{\symbolfont ✘}}
|
||||||
|
\newunicodechar{✗}{{\symbolfont ✗}}
|
||||||
|
\newunicodechar{⬜}{{\symbolfont ☐}}
|
||||||
|
\newunicodechar{⭐}{{\symbolfont ★}}
|
||||||
|
\newunicodechar{🔶}{{\symbolfont ◆}}
|
||||||
|
\newunicodechar{⚠}{{\symbolfont ⚠}}
|
||||||
|
\newunicodechar{📋}{}
|
||||||
|
\newunicodechar{🐛}{[bug]}
|
||||||
|
\newunicodechar{🎯}{[goal]}
|
||||||
|
\newunicodechar{🟡}{{\symbolfont ●}}
|
||||||
|
\newunicodechar{📍}{{\symbolfont ▸}}
|
||||||
|
\newunicodechar{🔓}{[unlocked]}
|
||||||
|
\newunicodechar{❓}{?}
|
||||||
|
\newunicodechar{⟺}{\ensuremath{\Longleftrightarrow}}
|
||||||
|
\newunicodechar{⋯}{\ensuremath{\cdots}}
|
||||||
|
\newunicodechar{^^^^fe0f}{}
|
||||||
|
|
||||||
|
% --- the documents number their own sections (§III.1, D-3, ...)
|
||||||
|
\setcounter{secnumdepth}{0}
|
||||||
|
\setcounter{tocdepth}{1}
|
||||||
|
|
||||||
|
\input{meta}
|
||||||
|
|
||||||
|
% \datasource{path/to/data.csv}: cite the data behind a figure or table.
|
||||||
|
% Every figure or table of measured numbers reads its CSV at build time
|
||||||
|
% (\addplot table / \pgfplotstabletypeset) and is followed by this line, so
|
||||||
|
% the reader can find the exact file at the exact commit.
|
||||||
|
\newcommand{\datasource}[1]{%
|
||||||
|
\par{\small Data: \texttt{\detokenize{#1}} at commit \texttt{\bookcommit}.}\par}
|
||||||
|
|
||||||
|
\title{LithosAnanke and StarForth}
|
||||||
|
\author{}
|
||||||
|
\date{Built \bookdate{} from commit \texttt{\bookcommit}%
|
||||||
|
\ifx\bookboard\empty\else\\Board appendix: \bookboard\fi}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\frontmatter
|
||||||
|
\maketitle
|
||||||
|
\tableofcontents
|
||||||
|
|
||||||
|
\chapter{About this book}
|
||||||
|
This book is generated from the repository by \texttt{make docs}. Each
|
||||||
|
chapter below is still maintained as the Markdown file named in the table and
|
||||||
|
converted at build time; a chapter moves into LaTeX source when it is
|
||||||
|
rewritten. Part~III is the design record: those documents are archival and
|
||||||
|
are reproduced as written, including their struck-through corrections.
|
||||||
|
|
||||||
|
\begin{longtable}{@{}ll@{}}
|
||||||
|
\toprule
|
||||||
|
Chapter & Source \\
|
||||||
|
\midrule
|
||||||
|
\endhead
|
||||||
|
Ontology & \texttt{docs/ONTOLOGY.md} \\
|
||||||
|
Roadmap & \texttt{docs/ROADMAP.md} \\
|
||||||
|
Boot targets & \texttt{boards/README.md} \\
|
||||||
|
v4 justification & \texttt{docs/v4.0.0/JUSTIFICATION.md} \\
|
||||||
|
v4 decomposition & \texttt{docs/v4.0.0/DECOMPOSITION.md} \\
|
||||||
|
FABRIC-0 \dots{} FABRIC-4 & \texttt{docs/fabric/FABRIC-*.md} \\
|
||||||
|
Board appendix (\texttt{TARGET=}) & \texttt{boards/<board>/README.md} \\
|
||||||
|
\bottomrule
|
||||||
|
\end{longtable}
|
||||||
|
|
||||||
|
\mainmatter
|
||||||
|
|
||||||
|
\part{The system}
|
||||||
|
\input{ontology}
|
||||||
|
\input{roadmap}
|
||||||
|
\input{boards}
|
||||||
|
|
||||||
|
\part{StarForth v4}
|
||||||
|
\input{justification}
|
||||||
|
\input{decomposition}
|
||||||
|
|
||||||
|
\part{Design record (FABRIC series)}
|
||||||
|
\input{fabric-0}
|
||||||
|
\input{fabric-1}
|
||||||
|
\input{fabric-2}
|
||||||
|
\input{fabric-3}
|
||||||
|
\input{fabric-3-5}
|
||||||
|
\input{fabric-3-6}
|
||||||
|
\input{fabric-3-7}
|
||||||
|
\input{fabric-4}
|
||||||
|
|
||||||
|
\ifdefined\bookboardappendix
|
||||||
|
\appendix
|
||||||
|
\input{\bookboardappendix}
|
||||||
|
\fi
|
||||||
|
|
||||||
|
\end{document}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
-- docs/book/pandoc/code-breaks.lua
|
||||||
|
--
|
||||||
|
-- Inline code in the design record is full of long identifiers and paths
|
||||||
|
-- (g_wirebind_attached_username, kernel/src/arch/riscv64/...), which LaTeX
|
||||||
|
-- cannot break, so they run into the margin. Emit inline code as \texttt
|
||||||
|
-- ourselves, escaped the same way pandoc does, with a break opportunity
|
||||||
|
-- after each separator character.
|
||||||
|
|
||||||
|
local BREAK_AFTER = { ["_"] = true, ["/"] = true, ["."] = true, ["-"] = true,
|
||||||
|
[":"] = true, [","] = true, ["("] = true, ["="] = true }
|
||||||
|
|
||||||
|
local ESCAPE = {
|
||||||
|
["\\"] = "\\textbackslash{}", ["{"] = "\\{", ["}"] = "\\}",
|
||||||
|
["$"] = "\\$", ["&"] = "\\&", ["#"] = "\\#", ["%"] = "\\%", ["_"] = "\\_",
|
||||||
|
["^"] = "\\textasciicircum{}", ["~"] = "\\textasciitilde{}",
|
||||||
|
["'"] = "\\textquotesingle{}", ["`"] = "\\textasciigrave{}",
|
||||||
|
[" "] = "\\ ",
|
||||||
|
}
|
||||||
|
|
||||||
|
function Code(el)
|
||||||
|
if not FORMAT:match("latex") then return nil end
|
||||||
|
local out = {}
|
||||||
|
for _, cp in utf8.codes(el.text) do
|
||||||
|
local ch = utf8.char(cp)
|
||||||
|
out[#out + 1] = ESCAPE[ch] or ch
|
||||||
|
if BREAK_AFTER[ch] then out[#out + 1] = "\\allowbreak{}" end
|
||||||
|
end
|
||||||
|
return pandoc.RawInline("latex", "\\texttt{" .. table.concat(out) .. "}")
|
||||||
|
end
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
$if(highlighting-macros)$
|
||||||
|
$highlighting-macros$
|
||||||
|
$endif$
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
-- docs/book/pandoc/table-widths.lua
|
||||||
|
--
|
||||||
|
-- pandoc's gfm reader leaves every column width unset, so the LaTeX writer
|
||||||
|
-- emits `l` columns and a table with long cells runs off the page. For any
|
||||||
|
-- table wider than WIDE characters, give each column a width proportional
|
||||||
|
-- to its longest cell (clamped to [MIN_COL, MAX_COL] characters); the
|
||||||
|
-- writer then emits wrapping p{} columns that together fill \linewidth.
|
||||||
|
|
||||||
|
local WIDE = 72
|
||||||
|
local MIN_COL = 6
|
||||||
|
local MAX_COL = 60
|
||||||
|
|
||||||
|
local stringify = pandoc.utils.stringify
|
||||||
|
|
||||||
|
local function measure(rows, widths)
|
||||||
|
for _, row in ipairs(rows) do
|
||||||
|
local col = 1
|
||||||
|
for _, cell in ipairs(row.cells) do
|
||||||
|
local span = cell.col_span or 1
|
||||||
|
local len = utf8.len(stringify(cell.contents)) or #stringify(cell.contents)
|
||||||
|
local per = len / span
|
||||||
|
for c = col, col + span - 1 do
|
||||||
|
if per > (widths[c] or 0) then widths[c] = per end
|
||||||
|
end
|
||||||
|
col = col + span
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Table(tbl)
|
||||||
|
local n = #tbl.colspecs
|
||||||
|
local widths = {}
|
||||||
|
for i = 1, n do widths[i] = 0 end
|
||||||
|
|
||||||
|
measure(tbl.head.rows, widths)
|
||||||
|
for _, body in ipairs(tbl.bodies) do
|
||||||
|
measure(body.head, widths)
|
||||||
|
measure(body.body, widths)
|
||||||
|
end
|
||||||
|
measure(tbl.foot.rows, widths)
|
||||||
|
|
||||||
|
local total = 0
|
||||||
|
for i = 1, n do total = total + widths[i] end
|
||||||
|
if total <= WIDE then return nil end
|
||||||
|
|
||||||
|
local sum = 0
|
||||||
|
for i = 1, n do
|
||||||
|
widths[i] = math.max(MIN_COL, math.min(MAX_COL, widths[i]))
|
||||||
|
sum = sum + widths[i]
|
||||||
|
end
|
||||||
|
for i = 1, n do
|
||||||
|
tbl.colspecs[i] = { tbl.colspecs[i][1], widths[i] / sum }
|
||||||
|
end
|
||||||
|
return tbl
|
||||||
|
end
|
||||||
@@ -1,37 +1,26 @@
|
|||||||
# FABRIC-3.5.md — the Tripod/kernel reshuffle
|
# FABRIC-3.5.md — the Tripod/kernel reshuffle
|
||||||
|
|
||||||
**Status: REOPENED 2026-09-19, by direct instruction ("reopen 3.5 and write it up as a gap
|
**Status: CLOSED/ARCHIVAL as of 2026-09-22, at tag `v2.1.0`.** Per §XXVI.5's own close
|
||||||
analysis section"), to add §XXXI — a gap-analysis sweep of the whole FABRIC set. The design
|
condition ("this document closes when the tag exists"). Produced the full design ruling for
|
||||||
phase remains closed; §XXXI adds findings, not new design.**
|
the Tripod/kernel reshuffle: Hermes moves from a FORTH VM into the kernel as kernel-Hermes; the
|
||||||
|
Tripod becomes Hera/Artemis/Hestia; every design question this document opened is ruled (§XXX.7
|
||||||
|
/ §XLI's punch list). Execution against that design is recorded in `FABRIC-3.6.md`, not here —
|
||||||
|
all five phases (0–5) closed there, ending in the `v2.1.0` tag this header names. **Nothing is
|
||||||
|
carried forward and no successor is created** — this document is not part of the
|
||||||
|
`FABRIC-0 → -1 → -2 → -3` chain (§XXVI.5), so closing it hands nothing to a successor.
|
||||||
|
**`FABRIC-3.md` remains open, living and authoritative for its own topic** (bare metal boot);
|
||||||
|
nothing here ever superseded it.
|
||||||
|
|
||||||
> **Prior status, kept rather than overwritten: DESIGN PHASE CLOSED as of 2026-09-19, by
|
> **Prior status headers, kept rather than overwritten, per this series' own rule against
|
||||||
> direct instruction ("close the document for now"). Not yet archival.** That close stood for
|
> silently rewriting a prior state:**
|
||||||
> the duration of the sweep and its substance is unchanged — every design question was and
|
>
|
||||||
> remains ruled. The reopen is recorded rather than the close deleted, per this series' own
|
> **REOPENED 2026-09-19**, by direct instruction ("reopen 3.5 and write it up as a gap analysis
|
||||||
> rule against silently rewriting a prior state.
|
> section"), to add §XXXI — a gap-analysis sweep of the whole FABRIC set.
|
||||||
|
>
|
||||||
**What is closed, and what is deliberately not.** Every design question this document opened is
|
> **DESIGN PHASE CLOSED as of 2026-09-19**, by direct instruction ("close the document for
|
||||||
ruled — see §XXX.7. **No code has been written and no code is authorized.** The execution
|
> now"). Not yet archival at that point — the execution sequence (surgical strip, build,
|
||||||
sequence (§XXVI.6, as amended by §XXX.7) is untouched: the surgical strip, the build, the
|
> Isabelle pass, doc sweep, SBOM, merge, tag) was still entirely ahead. That gap is now closed:
|
||||||
Isabelle/HOL pass, the documentation sweep, the SBOM, the merge to `master`, and the `v2.1.0`
|
> `FABRIC-3.6.md`'s Phases 0–5 are the execution this header once described as pending.
|
||||||
tag all remain ahead.
|
|
||||||
|
|
||||||
**This is therefore a narrower close than §XXVI.5 specified**, and the difference is stated
|
|
||||||
rather than glossed. §XXVI.5 ruled that this document closes *when the tag exists*, in
|
|
||||||
`FABRIC-2.md`'s CLOSED/ARCHIVAL form, naming the tag it closed at. **The tag does not exist**,
|
|
||||||
so claiming that close would be a label running ahead of the real state — the exact error
|
|
||||||
`FABRIC-3.md` §I.2 corrected when it rolled `LITHOS_VERSION` back, and the same standard §XXIX
|
|
||||||
applied to the LTS question. **The archival close specified by §XXVI.5 still stands and still
|
|
||||||
happens at `v2.1.0`.** This header is the provisional one the instruction's own "for now" asks
|
|
||||||
for.
|
|
||||||
|
|
||||||
**Nothing is carried forward and no successor is created.** As §XXVI.5 establishes, this
|
|
||||||
document is not part of the `FABRIC-0 → -1 → -2 → -3` chain, so closing it hands nothing to a
|
|
||||||
successor. **`FABRIC-3.md` remains open, living and authoritative for its own topic** (bare
|
|
||||||
metal boot); nothing here supersedes it. The live artifact from this document is its punch list
|
|
||||||
(§XXVI.6 / §XXX.7) — that, not this header, is what the build works from.
|
|
||||||
|
|
||||||
**Open by design, not by omission:** items 10–17 of §XXVI.6 are execution, not decisions.
|
|
||||||
Three things recorded here are explicitly *outside* this reshuffle and still need their own
|
Three things recorded here are explicitly *outside* this reshuffle and still need their own
|
||||||
authorization — the `.claude/CLAUDE.md`/`MANIFEST.md` documentation reconciliation (§XXVI.1),
|
authorization — the `.claude/CLAUDE.md`/`MANIFEST.md` documentation reconciliation (§XXVI.1),
|
||||||
the `src/*.c.bak` hygiene question (§XXII.5), and the stray `refs/heads/v2.0.1` branch and
|
the `src/*.c.bak` hygiene question (§XXII.5), and the stray `refs/heads/v2.0.1` branch and
|
||||||
@@ -1,6 +1,24 @@
|
|||||||
# FABRIC-3.6.md — the Tripod/kernel reshuffle: execution log
|
# FABRIC-3.6.md — the Tripod/kernel reshuffle: execution log
|
||||||
|
|
||||||
> ## START HERE — session handoff
|
**Status: CLOSED/ARCHIVAL as of 2026-09-22, at tag `v2.1.0`.** All five phases (0–5) closed —
|
||||||
|
Category A strip, Hestia relocation/birth, kernel-Hermes build (allocator, arena, message
|
||||||
|
types), Stage-cutover of every FORTH-owned message type, the Category B strip (Hermes VM +
|
||||||
|
`messaging.4th` removed), the Isabelle pass, the documentation sweep, `make sbom`, the version
|
||||||
|
bump, and the merge to `master` + tag. **This document is closed; the reshuffle it tracked is
|
||||||
|
done, not merely planned.** Per §XXVI.5 (cited here, ruled in `FABRIC-3.5.md`, also closed at
|
||||||
|
this same tag): closing this document hands nothing to a successor and does not touch
|
||||||
|
`FABRIC-3.md`, which remains open and authoritative for its own topic (bare metal boot).
|
||||||
|
|
||||||
|
**The `START HERE` section immediately below is kept as historical record of how this
|
||||||
|
execution began — it describes a session about to start work, not the current state.** Do not
|
||||||
|
follow its "first action: task 0.0" instruction; every task it points at is closed. If future
|
||||||
|
work touches this fleet again (a Phase 8 PKI elevation entrypoint, further hardware bring-up,
|
||||||
|
etc.), it gets its own new document, not a reopening of this one — matching the discipline
|
||||||
|
`FABRIC-3.5.md`'s own close just followed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> ## START HERE — session handoff (historical — reshuffle complete, see status above)
|
||||||
>
|
>
|
||||||
> **If you have just been told "go build it", read this section, then `FABRIC-3.5.md`'s §XLI,
|
> **If you have just been told "go build it", read this section, then `FABRIC-3.5.md`'s §XLI,
|
||||||
> then start at task 0.0 below. Do not re-derive the design — it is settled.**
|
> then start at task 0.0 below. Do not re-derive the design — it is settled.**
|
||||||
@@ -1345,7 +1363,7 @@ intermediate states.
|
|||||||
build its own entrypoint rather than resuming `SEND-ELEVATE-REQUEST`. No further action
|
build its own entrypoint rather than resuming `SEND-ELEVATE-REQUEST`. No further action
|
||||||
this phase.
|
this phase.
|
||||||
|
|
||||||
## Phase 5 — Close-out
|
## Phase 5 — Close-out — **COMPLETE, tag `v2.1.0` cut 2026-09-22**
|
||||||
|
|
||||||
**Note on commit granularity:** tasks 5.1–5.4 landed as one commit (`a4ad14a`), a deliberate
|
**Note on commit granularity:** tasks 5.1–5.4 landed as one commit (`a4ad14a`), a deliberate
|
||||||
deviation from the per-task-commit discipline this document has otherwise used since Phase 0.
|
deviation from the per-task-commit discipline this document has otherwise used since Phase 0.
|
||||||
@@ -1511,8 +1529,27 @@ opposite of what per-task commits are for. Tasks 5.5–5.7 return to one-commit-
|
|||||||
closed `2026-09-18T21:28:26Z` — 20 seconds later, before any of this reshuffle's actual
|
closed `2026-09-18T21:28:26Z` — 20 seconds later, before any of this reshuffle's actual
|
||||||
work existed on the branch. Already closed, already not merged, blocks nothing. Nothing
|
work existed on the branch. Already closed, already not merged, blocks nothing. Nothing
|
||||||
to resolve beyond recording what it is.
|
to resolve beyond recording what it is.
|
||||||
- [ ] **5.6** — Merge to `master`; tag `v2.1.0`.
|
- [x] **5.6** — Merge to `master`; tag `v2.1.0`.
|
||||||
- [ ] **5.7** — Archival close of `FABRIC-3.5.md` (§XXVI.5) and of this document.
|
2026-09-22 · **Closed, explicit approval obtained first (this is a shared-branch
|
||||||
|
operation on the sole production line — the plan authorizing it is not a standing
|
||||||
|
grant, per `.claude/CLAUDE.md`'s own scope rule).** `master` was a strict ancestor of
|
||||||
|
this branch (`git merge-base --is-ancestor origin/master HEAD` — true, 0 commits unique
|
||||||
|
to `master`, 85 unique to this branch), so this was a clean fast-forward, not a merge
|
||||||
|
commit: `git push origin HEAD:refs/heads/master` (`e56974e..8edbd3c`), never checking out
|
||||||
|
`master` locally, so the uncommitted `FABRIC-3.md` WIP on this branch's working tree was
|
||||||
|
never touched. Tagged `v2.1.0` (annotated, `8edbd3c`) and pushed. `refs/heads/v2.0.1`
|
||||||
|
(task 5.5's finding — fully merged, safe to delete) also deleted this same pass, explicit
|
||||||
|
approval obtained first.
|
||||||
|
- [x] **5.7** — Archival close of `FABRIC-3.5.md` (§XXVI.5) and of this document.
|
||||||
|
2026-09-22 · **Closed.** `FABRIC-3.5.md`'s provisional "design phase closed, not yet
|
||||||
|
archival" header replaced with the real CLOSED/ARCHIVAL form, naming `v2.1.0`, per its
|
||||||
|
own §XXVI.5 spec — prior status headers kept underneath, not deleted, matching this
|
||||||
|
series' own no-silent-rewrite rule. This document (`FABRIC-3.6.md`) closed the same way:
|
||||||
|
a CLOSED/ARCHIVAL status banner added above the `START HERE` section, which is kept as
|
||||||
|
historical record of how the session began rather than removed. Neither closure triggers
|
||||||
|
the `FABRIC-0 → -1 → -2 → -3` carry-forward chain (both documents are standalone topic
|
||||||
|
documents, not part of it) and neither touches `FABRIC-3.md`, which stays open for its
|
||||||
|
own topic. **Phase 5, and the Tripod/kernel reshuffle itself, are complete.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
# FABRIC-3.7.md — Phase 8 PKI: the elevation entrypoint
|
||||||
|
|
||||||
|
**Status: OPEN — design only, no code written or authorized.**
|
||||||
|
|
||||||
|
**CORRECTION (2026-09-22, before any code was written against this document): §2's central
|
||||||
|
claim — that the old `SEND-ELEVATE-REQUEST` passed a raw cross-VM address into `ELEVATE-GRANT`'s
|
||||||
|
`waddr` — is wrong.** Found while starting Part A's implementation: reading the actual deleted
|
||||||
|
source (`git show 3e201c8^:capsules/common/messaging.4th`, block 5040) shows
|
||||||
|
`SEND-ELEVATE-REQUEST` copied the target word's **name as literal character bytes** (via
|
||||||
|
`ELEVATE-REQ-APPEND`'s `CMOVE`) into a scratch buffer, building the text `S" <name-text>" <pk0>
|
||||||
|
<pk1> <pk2> <pk3> ELEVATE-GRANT`, and sent *that whole string* to Hera. When Hera's own
|
||||||
|
interpreter runs `S" <name-text>"`, it allocates a fresh string **in Hera's own memory** and
|
||||||
|
pushes Hera's own valid address — no numeric cross-VM address ever appears anywhere in this
|
||||||
|
flow. §2 was written from `ELEVATE-GRANT`'s signature alone, assuming the caller forwarded a raw
|
||||||
|
address, without first reading how the caller actually built its message. It didn't.
|
||||||
|
|
||||||
|
**What is still real, much narrower than originally claimed:** if a future caller ever spliced
|
||||||
|
*attacker-influenced* text into the name field without checking for an embedded `"` character,
|
||||||
|
that could break out of the `S" ... "` literal early and inject arbitrary FORTH source, executed
|
||||||
|
with Hera's privilege. That's an input-validation discipline question for whoever writes the new
|
||||||
|
caller (validate: no embedded `"`, or just always use a compile-time-fixed literal name, never a
|
||||||
|
runtime-supplied one) — not an architectural cross-VM-memory defect requiring the buffer/message
|
||||||
|
redesign §3 originally called for. **§3's proposed mechanism (Hera-side fixed receive buffer,
|
||||||
|
kernel-constructed integer-literal-only command) is not needed** — the original text-copy
|
||||||
|
design was already safe against the bug as actually diagnosed. Kept below, struck through, for
|
||||||
|
traceability, per this series' own rule against silently rewriting a prior state.
|
||||||
|
|
||||||
|
Successor to
|
||||||
|
`FABRIC-3.5.md`/`FABRIC-3.6.md` (both CLOSED/ARCHIVAL at `v2.1.0`) for exactly one topic: the
|
||||||
|
Ed25519-challenge-response elevation entrypoint that `.claude/CLAUDE.md`'s ACL section names as
|
||||||
|
Phase 8, the last open item in the word-level ACL system. This is a **new document**, not a
|
||||||
|
reopening of `FABRIC-3.6.md` — that document's own close header says future fleet work gets its
|
||||||
|
own document, and this is that.
|
||||||
|
|
||||||
|
**Provenance.** Written 2026-09-22, immediately after `FABRIC-3.6.md`'s close, from a design
|
||||||
|
conversation with Captain Bob about a security concern he raised directly: how to rebuild the
|
||||||
|
elevation entrypoint that Phase 4's Category B strip left dangling, without reopening a hole.
|
||||||
|
The design below was proposed, and Captain Bob asked for it in writing here rather than left
|
||||||
|
only in session memory.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What's dangling, and why
|
||||||
|
|
||||||
|
`FABRIC-3.6.md` Phase 4 (Category B strip, 2026-09-22) deleted `capsules/common/messaging.4th`
|
||||||
|
after every FORTH-owned message type had been cut over to kernel-Hermes. One casualty was
|
||||||
|
collateral, not intended: `SEND-ELEVATE-REQUEST` (`messaging.4th` block 5040) was the only
|
||||||
|
caller of both `KH-ELEVATE-SEND` (`src/starkernel/repl.c`) and, transitively, `ELEVATE-GRANT`
|
||||||
|
(`capsules/zuse-eligibility.4th`, blocks 4021–4022). All three still exist in the tree.
|
||||||
|
`ELEVATE-GRANT` is still loaded at boot (`capsules/init.4th:18`). Nothing can call it any more.
|
||||||
|
|
||||||
|
Captain Bob's decision at the time (`FABRIC-3.6.md`'s own Phase 4 entry): leave it unreachable,
|
||||||
|
don't patch a caller back in as part of that strip. Phase 8 builds its own entrypoint instead of
|
||||||
|
resuming this one. **This document is that entrypoint's design.**
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Original §2/§3 (WRONG — see the correction at the top of this document; kept for
|
||||||
|
traceability, not current design)</summary>
|
||||||
|
|
||||||
|
### 2. The security hole in the old mechanism — found before any code was written
|
||||||
|
|
||||||
|
`ELEVATE-GRANT`'s signature, unchanged since it was written:
|
||||||
|
|
||||||
|
```
|
||||||
|
ELEVATE-GRANT ( waddr wu pk0 pk1 pk2 pk3 -- )
|
||||||
|
```
|
||||||
|
|
||||||
|
`waddr`/`wu` are an address/length pair meant to point at the string naming the word to elevate.
|
||||||
|
`pk0`–`pk3` are the caller's Ed25519 pubkey, packed 8 bytes per cell (`ELEVATE-PUBKEY-UNPACK`,
|
||||||
|
`mama_forth_words.c`).
|
||||||
|
|
||||||
|
**The old `SEND-ELEVATE-REQUEST` computed `waddr` in the *sending* VM's own address space, but
|
||||||
|
`ELEVATE-GRANT` always executes on Hera** (`ELEVATE-GRANT always runs on Hera` — `repl.c`'s own
|
||||||
|
comment on `KH-ELEVATE-SEND`, still there). A word's name string lives in the sending VM's
|
||||||
|
memory. `ELEVATE-GRANT` dereferences `waddr` in Hera's memory. Those are not the same address
|
||||||
|
space by construction — `vaddr_t` is per-VM.
|
||||||
|
|
||||||
|
**Consequence:** whoever controls `waddr` controls what bytes `NAME>XT` reads and resolves as a
|
||||||
|
word name, in Hera's dictionary, not the caller's. This is not "the string might be malformed" —
|
||||||
|
it is a primitive for making Hera's own `ELEVATE-GRANT` grant `ACL-ALLOW!`/`ACL-TTL!` on
|
||||||
|
*whatever dictionary entry the attacker's chosen `waddr` happens to land on*, regardless of what
|
||||||
|
word name the caller claims to be requesting elevation for. A caller who can influence `waddr`
|
||||||
|
at all — not forge a signature, not defeat `zuse_eligibility_is_member()`, just choose a number
|
||||||
|
— has a privilege-escalation primitive against the fleet governor.
|
||||||
|
|
||||||
|
This was never exploited (the entrypoint has had zero live callers since the file that called it
|
||||||
|
was deleted), and is reported here as a design defect found by inspection, not a live incident.
|
||||||
|
|
||||||
|
### 3. The fix: never cross an address, only ever cross bytes
|
||||||
|
|
||||||
|
This project already solved the general version of this problem once, this same session
|
||||||
|
(`FABRIC-3.6.md` tasks 3.8/3.9, the payload-aliasing fix): a kernel-Hermes message's payload
|
||||||
|
must be **copied into the message's own storage**, never a pointer into the sender's memory that
|
||||||
|
might be reused or freed before the receiver drains it. `SkHermesMessage.payload_buf`
|
||||||
|
(`include/starkernel/vm/kernel_hermes.h:160`, `SK_HERMES_CHUNK_MAX_PAYLOAD` = 1024 bytes) is
|
||||||
|
exactly that fix, already built, already proven on all three architectures.
|
||||||
|
|
||||||
|
**The elevation entrypoint's hole is the same defect one level up: an address crossing a
|
||||||
|
boundary it isn't valid on the other side of.** The fix generalizes directly:
|
||||||
|
|
||||||
|
1. **Never send `waddr`/`wu` across the kernel-Hermes boundary.** Send the pubkey (32 bytes,
|
||||||
|
already the right shape for `payload_buf`) and the target word's **name, as literal bytes**,
|
||||||
|
copied inline into the message payload — not an address, the actual characters. This is
|
||||||
|
already how `CONSOLE-CMD-EVENT`'s payload works (a command string's bytes, not a pointer to
|
||||||
|
one), so this isn't a new pattern, it's applying the existing one to the one caller that
|
||||||
|
still passed a raw address.
|
||||||
|
|
||||||
|
2. **On receipt, kernel-Hermes's C drain-checkpoint copies those name bytes into a small,
|
||||||
|
fixed, kernel-owned buffer that already lives in Hera's own VM memory** — a receive-side
|
||||||
|
mirror of the existing send-side pattern (`g_kh_elevate_buf`, `repl.c:445`, is the
|
||||||
|
already-built precedent for "a static buffer this mechanism owns"; this needs its Hera-side
|
||||||
|
counterpart). The buffer's address is a compile-time constant, known to the kernel, never
|
||||||
|
computed from anything the caller supplied.
|
||||||
|
|
||||||
|
3. **The FORTH command handed to `vm_interpret()` on Hera references only that fixed buffer's
|
||||||
|
address and length as plain integer literals.** Both are always kernel-controlled. Neither is
|
||||||
|
ever derived from caller input. `ELEVATE-GRANT` itself does not change — same signature, same
|
||||||
|
`zuse_eligibility_is_member()` check, same `ACL-ALLOW!`/`ACL-TTL!` grant. Policy logic stays
|
||||||
|
in FORTH, per `ACL.4th`'s own rule (no new C primitive for policy) — this fix is entirely
|
||||||
|
about how bytes get from one VM to another, not about who is allowed to grant what.
|
||||||
|
|
||||||
|
**Why this closes the hole structurally, not by validation:** there is no string to sanitize and
|
||||||
|
no address to bounds-check, because the interpreted command never contains anything an attacker
|
||||||
|
touched except opaque data bytes that get copied, never dereferenced as a pointer, by the
|
||||||
|
receiving side. The class of bug (cross-address-space pointer confusion) becomes impossible by
|
||||||
|
construction, the same way `payload_buf` made use-after-free impossible by construction rather
|
||||||
|
than by careful lifetime tracking.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 2 (corrected). What the old mechanism actually did, and the one real gap in it
|
||||||
|
|
||||||
|
Re-read from the actual deleted source (`git show 3e201c8^:capsules/common/messaging.4th`,
|
||||||
|
blocks 5039–5040): `SEND-ELEVATE-REQUEST ( pk3 pk2 pk1 pk0 waddr wu -- )` used `waddr`/`wu` only
|
||||||
|
to `CMOVE` the target word's **name bytes**, as text, into a scratch buffer
|
||||||
|
(`ELEVATE-REQ-BUF`/`ELEVATE-REQ-APPEND`) it owned — building the literal string `S"
|
||||||
|
<name-text>" <pk0> <pk1> <pk2> <pk3> ELEVATE-GRANT` entirely in the *sending* VM's own memory.
|
||||||
|
Only that finished string — not `waddr` itself — went to `KH-ELEVATE-SEND` and across to Hera.
|
||||||
|
When Hera's interpreter runs `S" <name-text>"`, Hera's own `S"` allocates a fresh string **in
|
||||||
|
Hera's own memory** and pushes Hera's own valid address. `waddr`/`wu` never cross the VM
|
||||||
|
boundary as numbers at any point — only as copied character content. There is no cross-VM
|
||||||
|
pointer dereference anywhere in this flow.
|
||||||
|
|
||||||
|
**The one real, much narrower gap:** the name text is spliced into `S" ... "` with no check for
|
||||||
|
an embedded `"` character. If a future caller ever passed attacker-influenced text as the name
|
||||||
|
(none ever did — the word had zero live callers), a `"` in the name would close the string
|
||||||
|
literal early and let the rest of the name execute as raw FORTH source, with Hera's privilege.
|
||||||
|
This is a caller-discipline / input-validation question, not an architectural defect: either
|
||||||
|
always use a compile-time-fixed name literal at the call site (no runtime input, no risk at
|
||||||
|
all), or validate for an embedded `"` before building the command if a name ever does need to
|
||||||
|
come from something less trusted than the call site's own source code.
|
||||||
|
|
||||||
|
**Net effect on Phase 8 v1's scope:** Part A, as originally conceived in §3 above, is not
|
||||||
|
needed. If a `SEND-ELEVATE-REQUEST` replacement is ever built, it can follow the original
|
||||||
|
text-copy design as-is, with the one-line `"`-check added if and only if the name is ever
|
||||||
|
runtime-supplied rather than a fixed literal. No kernel-Hermes/`repl.c` changes required. Part B
|
||||||
|
(`capsules/zuse.4th`, gating `ZUSE-ELIGIBILITY-ADD`) stands on its own, independently verified,
|
||||||
|
unaffected by this correction.
|
||||||
|
|
||||||
|
## 4. What Phase 8 actually needs to build
|
||||||
|
|
||||||
|
Corrected per §2's re-read above. Concretely, when Phase 8 next picks this up:
|
||||||
|
|
||||||
|
- If a caller into `ELEVATE-GRANT` is ever needed again, rebuild it close to the original
|
||||||
|
`SEND-ELEVATE-REQUEST` shape (`ELEVATE-REQ-BUF`/`ELEVATE-REQ-APPEND`/text-copy into `S" ...
|
||||||
|
"`) — it was already safe. Add the one-line embedded-`"` check only if the name is ever
|
||||||
|
runtime-supplied rather than a call-site literal. No `kernel_hermes.c`/`kernel_hermes.h`/
|
||||||
|
`repl.c` changes needed for this.
|
||||||
|
- `ELEVATE-GRANT` unchanged either way.
|
||||||
|
- **Part B is done** (`capsules/zuse.4th`, committed and three-arch verified this session,
|
||||||
|
2026-09-22) — `ZUSE-ELIGIBILITY-ADD` denied by default, granted only inside `ACL-ZUSE-BOOT`'s
|
||||||
|
authenticated branch. This closes the actual "grant yourself eligibility with no real drive at
|
||||||
|
all" path — a real, independently-confirmed gap, unaffected by this correction.
|
||||||
|
- **Defending against a cloned drive — settled, 2026-09-23: not going to happen, by design.**
|
||||||
|
Today, WIREBIND/MINT trust whatever identity is stored on an attached thumbdrive with no
|
||||||
|
challenge at all (confirmed by grep: no `ed25519_sign`/`ed25519_verify` call anywhere in
|
||||||
|
`capsule_wirebind.c` or `capsule_mint.c`), and the private key seed itself is stored in
|
||||||
|
plaintext on the drive, read in the same devblock as the pubkey/cert. **This is accepted, not
|
||||||
|
a gap.** Captain Bob, directly: *"nothing like a pin or a password or secret code or any
|
||||||
|
bullshit... Everybody has secrets. There's only the drive."* Physical possession of the drive
|
||||||
|
is the entire, deliberate credential model — a byte-for-byte clone being equivalent to the
|
||||||
|
real drive is the accepted design, not a defect to close. A PIN/passphrase second factor was
|
||||||
|
built, live-tested on all three architectures, and fully reverted before commit
|
||||||
|
(`/home/rajames/.claude/plans/jiggly-cuddling-stallman.md`, now marked rejected; memory
|
||||||
|
`feedback_no_knowledge_factor_identity`) — **do not revisit a knowledge-factor approach here.**
|
||||||
|
Any future work in this space needs a fundamentally different mechanism (not something typed
|
||||||
|
and known) or stays an accepted limitation.
|
||||||
|
- **Phase 8 v3, 2026-09-23 — done, a distinct and narrower concern from the item above.** The
|
||||||
|
"accepted limitation" above is about a drive image copied *outside* StarshipOS entirely (e.g.
|
||||||
|
imaged on an external computer) — that's still accepted, unchanged by this item. Captain Bob
|
||||||
|
separately asked to close a narrower, different threat: **cloning a device's block content
|
||||||
|
from *within* StarshipOS's own console**, using its own stock, unpinned words
|
||||||
|
(`<src> BLOCK <dst> BUFFER 1024 MOVE`/`RELOCATE-BLOCK`). That's now closed — `MOVE`/`CMOVE`/
|
||||||
|
`CMOVE>`/`RELOCATE-BLOCK` all refuse a same-VM, cross-device copy, verified live on all three
|
||||||
|
architectures. See `/home/rajames/.claude/plans/jiggly-cuddling-stallman.md`'s "Phase 8 v3"
|
||||||
|
section for the full design and verification record. The identity record itself
|
||||||
|
(seed/pubkey/cert) was already unreachable from FORTH before this — this closes the one real
|
||||||
|
gap the research found: ordinary block content, not the identity record.
|
||||||
|
|
||||||
|
## 5. What this document is not
|
||||||
|
|
||||||
|
Not a reopening of `FABRIC-3.6.md`, not a change to anything currently built, not an
|
||||||
|
authorization to write code. Per this series' own convention: design here, execution gets its
|
||||||
|
own document when the work actually starts, the same relationship `FABRIC-3.5.md` had to
|
||||||
|
`FABRIC-3.6.md`.
|
||||||
@@ -0,0 +1,784 @@
|
|||||||
|
# StarForth v4.0.0 — Primitive Decomposition
|
||||||
|
|
||||||
|
This document assigns every C primitive in StarForth v3 (`admin/LithosAnanake` at `6302dcb`) a fate in
|
||||||
|
StarForth v4.0.0. v4 is built on a 32-instruction core derived from Chuck Moore's F18 (the GA144 node).
|
||||||
|
Everything that is not one of those 32 instructions becomes capsule code, a service message to another
|
||||||
|
node, a memory-mapped register, or is retired.
|
||||||
|
|
||||||
|
The rationale is in `JUSTIFICATION.md`. This document is the specification.
|
||||||
|
|
||||||
|
**Status.** Every colon definition below is written against the ISA in §1 and has been traced by hand,
|
||||||
|
but none has been executed. Each one becomes a POST test target: it is correct when the hosted v4 golden
|
||||||
|
model produces the same results as the v3 C primitive it replaces.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
0. [Fates](#0-fates)
|
||||||
|
1. [The v4 core ISA](#1-the-v4-core-isa)
|
||||||
|
2. [Compiler conventions](#2-compiler-conventions)
|
||||||
|
3. [Decisions](#3-decisions)
|
||||||
|
4. [Foundation layer (new words)](#4-foundation-layer-new-words)
|
||||||
|
5. [Fate of every v3 primitive](#5-fate-of-every-v3-primitive)
|
||||||
|
6. [Messaging between nodes](#6-messaging-between-nodes)
|
||||||
|
7. [Memory-mapped registers](#7-memory-mapped-registers)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Fates
|
||||||
|
|
||||||
|
| Code | Fate | Meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **OP** | Instruction | One of the 32 core opcodes. |
|
||||||
|
| **IN** | Inline macro | A short opcode sequence the compiler places in-line. Never called. Required for anything that touches the return stack, since a call would bury the return address. |
|
||||||
|
| **CAP** | Core capsule | A colon definition in the core capsule, built only from OP, IN, and earlier CAP words. |
|
||||||
|
| **CC** | Compiler capsule | Part of the compiler/interpreter capsule, which runs on the host node only. Mesh nodes receive compiled code. |
|
||||||
|
| **DEV** | Device service | A message to a device node that owns real hardware (console, storage, display, keyboard, log). |
|
||||||
|
| **HERA** | Hera service | A message to the Hera node, which owns lifecycle, identity, capsules, ACLs, and Stadium admission. |
|
||||||
|
| **MM** | Memory-mapped | An `@` or `!` against a hardware register (heat counters, anti-clock, governor, stack pointer). |
|
||||||
|
| **RET** | Retired | A diagnostic of v3's software machine that has no equivalent in v4. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. The v4 core ISA
|
||||||
|
|
||||||
|
### 1.1 Machine model
|
||||||
|
|
||||||
|
| Item | v4 node |
|
||||||
|
| --- | --- |
|
||||||
|
| Cell | 32 bits (mesh node). The host node's width is a build parameter; see D-5. |
|
||||||
|
| Addressing | Word-addressed (D-1). |
|
||||||
|
| Registers | `T` (top of data stack), `S` (second), `R` (top of return stack), `P` (program counter), `A` and `B` (address registers). |
|
||||||
|
| Stacks | F18 circular hardware stacks, not visible to code (D-2): data stack `T`, `S` + 8 circular (10 deep), return stack `R` + 8 circular (9 deep). No overflow or underflow; pushing past the bottom silently overwrites the oldest entry. |
|
||||||
|
| Instruction word | Six 5-bit slots, plus 2 spare bits. |
|
||||||
|
|
||||||
|
### 1.2 Instruction word layout
|
||||||
|
|
||||||
|
```
|
||||||
|
31 27 26 22 21 17 16 12 11 7 6 2 1 0
|
||||||
|
+--------+--------+--------+--------+--------+--------+----+
|
||||||
|
| slot 0 | slot 1 | slot 2 | slot 3 | slot 4 | slot 5 | xx |
|
||||||
|
+--------+--------+--------+--------+--------+--------+----+
|
||||||
|
```
|
||||||
|
|
||||||
|
Slots execute left to right. A branch (`jump`, `call`, `next`, `if`, `-if`) takes its target from all
|
||||||
|
bits to the right of its slot, and that target replaces the same low bits of `P` (page-relative, as in
|
||||||
|
the F18). Branches are therefore legal in slots 0–3 only:
|
||||||
|
|
||||||
|
| Branch in slot | Address bits | Reach |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 0 | 27 | whole address space |
|
||||||
|
| 1 | 22 | 4M words |
|
||||||
|
| 2 | 17 | 128K words |
|
||||||
|
| 3 | 12 | 4K words (node-local) |
|
||||||
|
|
||||||
|
### 1.3 The 32 opcodes
|
||||||
|
|
||||||
|
Opcode numbering follows the F18. Two names differ from Moore's: F18 `-` is renamed `inv` and F18 `or`
|
||||||
|
(which is exclusive-or) is renamed `xor`, so instruction names never collide with FORTH-79 word names.
|
||||||
|
|
||||||
|
| Op | Name | Effect |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 00 | `;` | Return: `P ← R`, pop `R`. |
|
||||||
|
| 01 | `ex` | Swap `P` and `R` (co-routine / execute). |
|
||||||
|
| 02 | `jump a` | `P ← a`. |
|
||||||
|
| 03 | `call a` | Push `P` to `R`, `P ← a`. |
|
||||||
|
| 04 | `unext` | If `R ≠ 0`: decrement `R`, restart the current instruction word at slot 0. Else pop `R`. |
|
||||||
|
| 05 | `next a` | If `R ≠ 0`: decrement `R`, `P ← a`. Else pop `R`. |
|
||||||
|
| 06 | `if a` | If `T = 0`: `P ← a`. **Does not pop `T`.** |
|
||||||
|
| 07 | `-if a` | If `T ≥ 0` (sign bit clear): `P ← a`. **Does not pop `T`.** |
|
||||||
|
| 08 | `@p` | Push the word at `P`, `P ← P+1` (literal). |
|
||||||
|
| 09 | `@+` | Push the word at `A`, `A ← A+1`. |
|
||||||
|
| 0A | `@b` | Push the word at `B`. |
|
||||||
|
| 0B | `@` | Push the word at `A`. |
|
||||||
|
| 0C | `!p` | Store `T` at `P`, pop, `P ← P+1`. |
|
||||||
|
| 0D | `!+` | Store `T` at `A`, pop, `A ← A+1`. |
|
||||||
|
| 0E | `!b` | Store `T` at `B`, pop. |
|
||||||
|
| 0F | `!` | Store `T` at `A`, pop. |
|
||||||
|
| 10 | `+*` | Multiply step: if bit 0 of `A` is set, `T ← T+S`; then shift `T:A` right one bit. See D-3. |
|
||||||
|
| 11 | `2*` | `T ← T << 1`. |
|
||||||
|
| 12 | `2/` | `T ← T >> 1`, arithmetic. |
|
||||||
|
| 13 | `inv` | `T ← ~T`. |
|
||||||
|
| 14 | `+` | `T ← S + T`, pop. |
|
||||||
|
| 15 | `and` | `T ← S & T`, pop. |
|
||||||
|
| 16 | `xor` | `T ← S ^ T`, pop. |
|
||||||
|
| 17 | `drop` | Pop `T`. |
|
||||||
|
| 18 | `dup` | Push a copy of `T`. |
|
||||||
|
| 19 | `pop` | Pop `R` onto the data stack. |
|
||||||
|
| 1A | `over` | Push a copy of `S`. |
|
||||||
|
| 1B | `a` | Push `A`. |
|
||||||
|
| 1C | `nop` | Nothing. |
|
||||||
|
| 1D | `push` | Pop `T` onto the return stack. |
|
||||||
|
| 1E | `b!` | `B ← T`, pop. |
|
||||||
|
| 1F | `a!` | `A ← T`, pop. |
|
||||||
|
|
||||||
|
Note that `@` and `!` address through `A`, not through `T`. The FORTH words `@` and `!` therefore become
|
||||||
|
`a! @` and `a! !`.
|
||||||
|
|
||||||
|
### 1.4 Side effects that are not instructions
|
||||||
|
|
||||||
|
Execution itself drives the physics. Every retired instruction increments that opcode's heat counter and
|
||||||
|
advances the anti-clock; every `call` increments the heat counter of its target. None of this costs an
|
||||||
|
instruction. Capsule code reads it through the memory-mapped registers in §7.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Compiler conventions
|
||||||
|
|
||||||
|
**Literals.** A number in source compiles to `@p` followed by the value in the next word.
|
||||||
|
|
||||||
|
**Capsule `IF`.** Native `if` and `-if` leave the flag on the stack. The capsule-level `IF` consumes it,
|
||||||
|
so the compiler drops it on both paths:
|
||||||
|
|
||||||
|
```
|
||||||
|
IF body THEN → if L1 drop body jump L2
|
||||||
|
L1: drop
|
||||||
|
L2:
|
||||||
|
|
||||||
|
IF body1 ELSE body2 THEN → if L1 drop body1 jump L2
|
||||||
|
L1: drop body2
|
||||||
|
L2:
|
||||||
|
```
|
||||||
|
|
||||||
|
`BEGIN ... WHILE ... REPEAT` and `BEGIN ... UNTIL` expand the same way.
|
||||||
|
|
||||||
|
**`FOR ... NEXT`.** `n FOR ... NEXT` compiles `push` then the body then `next`. The body runs `n+1`
|
||||||
|
times, as on the F18. `FOR ... UNEXT` is the same but the body must fit in one instruction word.
|
||||||
|
|
||||||
|
**Register conventions.** `A` and `B` are caller-saved. A word that uses them says so. Words in this
|
||||||
|
document that clobber `A`: `@ ! +! -! 2@ 2! C@ C! UM* * UM/MOD Q.FROM-INT Q.TO-INT Q.* Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS SEND RECV`. Words that clobber `B`:
|
||||||
|
`Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS SEND RECV`.
|
||||||
|
|
||||||
|
**Return-stack words** (`>R R> R@ 2>R 2R> 2R@ I J UNLOOP` and the loop runtimes) are always IN.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Decisions
|
||||||
|
|
||||||
|
Ruled by Captain Bob, 2026-10-01, except D-4 and D-6, which are deferred to development step 2 (the
|
||||||
|
hosted mesh, `JUSTIFICATION.md` §10) because only the mesh and the RTL depend on them. Where a
|
||||||
|
definition below depends on one, it says so.
|
||||||
|
|
||||||
|
| ID | Decision | Ruling |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **D-1** | Word addressing (pure Moore) or byte addressing. | **Word addressing.** `C@`/`C!` are CAP; `CELLS` is a no-op. |
|
||||||
|
| **D-2** | Stack depth, and whether the stacks are visible. | **F18 circular stacks, hidden.** Data stack 10 deep (`T`, `S` + 8 circular), return stack 9 deep (`R` + 8 circular), exactly as the F18. No stack pointer is visible to code, so `DEPTH`, `PICK`, `ROLL`, `.S`, `SP@` and `SP!` are **retired everywhere**, host node included. |
|
||||||
|
| **D-3** | Exact `+*` semantics at 32 bits: whether the add carries out of `T` into the shift. | **Plain F18 semantics.** The carry out of `T` is not kept. `UM*` in §4 is written for this and is exact over the full range (revised and proven on the golden model, 2026-10-02). |
|
||||||
|
| **D-4** | Node memory map, including port and register addresses. | **Deferred** to step 2. Symbolic names only for now (§6, §7). |
|
||||||
|
| **D-5** | Host node cell width. | **Match the host CPU:** 32 on a Zynq-7000 (Cortex-A9), 64 on an aarch64 host. The compiler capsule is written width-independent. |
|
||||||
|
| **D-6** | Which heat structures exist in hardware: per-opcode counters only, or also per-call-target and word-to-word transition counters. | **Deferred** to step 2. The golden model implements per-opcode and per-call-target heat in the meantime. |
|
||||||
|
| **D-7** | `ROLL` semantics. | **Moot.** `ROLL` is retired under D-2. |
|
||||||
|
| **D-8** | Q48.16 signedness. | **Signed.** v3's unsigned comparisons and `Q.FROM-INT` clamping are retired. |
|
||||||
|
| **D-9** | Instruction word width on a 64-bit-cell host (ruled 2026-10-02). | **32 bits at every cell width.** Six 5-bit slots plus 2 spare bits, as in §1.2. On a 64-bit host the instruction word is the low 32 bits of the cell and the high half is ignored, so compiled code is identical at both widths. Only data and `@p` literals are a full cell wide. |
|
||||||
|
| **D-10** | Q48.16 width on a 64-bit-cell node (ruled 2026-10-02). | **Two cells at every cell width.** A Q value is a signed double on 32- and 64-bit nodes alike, so every Q word is the same double word at both widths. At 32-bit cells this is bit-for-bit v3's 64-bit Q. At 64-bit cells the low cell is v3's value, and where v3 wraps on Q48.16 overflow (a sum past Q max, `ABS` or `NEG` of Q min) the high cell carries the true result instead. |
|
||||||
|
| **D-11** | `Q./` semantics: division by zero, rounding, overflow (ruled 2026-10-02). | **Saturate and flag; round toward zero; saturate on overflow.** The quotient of `a * 2^16 / b` is rounded toward zero, like `SM/REM`. When it does not fit a Q value it is clamped to Q max or Q min by its sign. Division by zero returns Q max or Q min by the sign of the dividend (`0 / 0` gives 0) and sets the node's `NODE-ERROR` register (§7), which `VM-ERROR?` reads. v3 returned 0 on division by zero and saturated whenever the dividend was 2^48 or more, even when the quotient would have fit. |
|
||||||
|
| **D-12** | Q approximations outside their domain (ruled 2026-10-02). | **Return 0 and set `NODE-ERROR`.** `Q.SQRT` of a negative value and `Q.LOG` of zero or a negative value return 0 and set `NODE-ERROR` (§7), as `Q./` does on division by zero (D-11). v3 returned 0 for `ln(0)` without a flag and read negative arguments as large unsigned values. |
|
||||||
|
|
||||||
|
**Consequences of D-2 that every definition must respect.** The data stack holds 10 items and the
|
||||||
|
return stack 9, and every `call`, `FOR`, `DO` loop frame and `push` uses return-stack slots. Nesting
|
||||||
|
is therefore shallow, and exceeding either depth corrupts silently rather than faulting. Every CAP
|
||||||
|
definition in §4 and §5 must be re-checked against these limits before it is accepted as a POST
|
||||||
|
target; the hand traces so far assumed unbounded stacks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Foundation layer (new words)
|
||||||
|
|
||||||
|
These words do not exist as primitives in v3 but everything else is built on them. They are listed in
|
||||||
|
dependency order.
|
||||||
|
|
||||||
|
```forth
|
||||||
|
\ ---- helpers ------------------------------------------------------------
|
||||||
|
: NIP ( a b -- b ) push drop pop ;
|
||||||
|
: SWAP ( a b -- b a ) over push push drop pop pop ;
|
||||||
|
: OR ( a b -- a|b ) over inv and xor ;
|
||||||
|
: NEGATE ( n -- -n ) inv 1 + ;
|
||||||
|
: ROT ( a b c -- b c a ) push SWAP pop SWAP ;
|
||||||
|
|
||||||
|
\ ---- sign and zero tests (raw branches, labels shown) ------------------
|
||||||
|
: 0< ( n -- flag ) -if L1 drop -1 ; L1: drop 0 ;
|
||||||
|
: 0= ( n -- flag ) if L1 drop 0 ; L1: drop -1 ;
|
||||||
|
|
||||||
|
\ ---- unsigned compare ---------------------------------------------------
|
||||||
|
: U< ( u1 u2 -- flag ) 2DUP xor 0< IF NIP 0< ELSE - 0< THEN ;
|
||||||
|
|
||||||
|
\ ---- multiply (D-3) -----------------------------------------------------
|
||||||
|
\ Plain F18 +* loses the carry out of T, and its shift keeps T's sign bit, so
|
||||||
|
\ the loop is exact only while the multiplicand in S and the running T both
|
||||||
|
\ lie in [-2^(n-2), 2^(n-2)). UM* therefore multiplies by s = u1 2/, which
|
||||||
|
\ always does, starting T at t0 = u2 2/ when u1 is odd, and gets
|
||||||
|
\ hi:lo = t0 + s*u2. Then u1*u2 = 2*(hi:lo) + c_lo + c_hi*2^n, where
|
||||||
|
\ c_lo = u1 & u2 & 1
|
||||||
|
\ c_hi = (u1<0 ? u2 : 0) + (u1 odd and u2<0 ? 1 : 0)
|
||||||
|
\ restore the bits the two halvings dropped and the unsigned reading of
|
||||||
|
\ both top bits. Exact over the full range; proven on the golden model.
|
||||||
|
: UM* ( u1 u2 -- ulo uhi )
|
||||||
|
over 1 and inv 1 + over 2/ and \ u1 u2 t0 t0 = u2 2/ if u1 odd
|
||||||
|
over a! 31 push \ A: u2 R: loop count (FOR's push, early)
|
||||||
|
push over 2/ pop \ u1 u2 s t0 s = u1 2/
|
||||||
|
L: +* unext \ u1 u2 s hi A: lo
|
||||||
|
push drop pop \ u1 u2 hi
|
||||||
|
2* a -if L0 drop 1 + jump L1 L0: drop L1: push \ R: 2hi + top bit of lo
|
||||||
|
over -if L2 drop dup jump L3 L2: drop 0 L3: push \ R: + (u1<0 ? u2 : 0)
|
||||||
|
dup -if L4 drop over 1 and jump L5 L4: drop 0 L5: \ u2<0 ? u1&1 : 0
|
||||||
|
pop + pop + push \ u1 u2 R: hi'
|
||||||
|
and 1 and a 2* + pop ; \ lo' hi'
|
||||||
|
\ u1 and u2 wait on the data stack under the loop (+* touches only T, S and
|
||||||
|
\ A), so the corrections are made after it, with -if in place of 0< calls,
|
||||||
|
\ and the return stack holds at most two temporaries. The loop count is
|
||||||
|
\ pushed before s is made, so the data stack never holds more than four.
|
||||||
|
\ Leaves its caller 6 data cells under its arguments and 6 return entries
|
||||||
|
\ (revised 2026-10-02; the first full-range version parked its three
|
||||||
|
\ corrections on the return stack and left 4).
|
||||||
|
|
||||||
|
\ ---- divide: 32-step restoring division, divisor held in A --------------
|
||||||
|
\ Each step shifts hi:lo left one bit and, when the shifted-out bit of hi was
|
||||||
|
\ set or hi' >= d, subtracts d from hi' and sets the new low bit of lo. The
|
||||||
|
\ unsigned compare is done by sign tests in line (as U< does), and x - d is
|
||||||
|
\ `inv a + inv`, so the loop body makes no calls: it needs one return-stack
|
||||||
|
\ entry beyond its own count.
|
||||||
|
: UM/MOD ( ulo uhi ud -- urem uquot )
|
||||||
|
a! 31 FOR
|
||||||
|
-if L0 \ hi's top bit set:
|
||||||
|
2* over -if L1 drop 1 + jump L2 \ hi' = 2hi + top bit of lo
|
||||||
|
L1: drop L2: push 2* pop \ lo' = 2lo
|
||||||
|
jump SUB \ true hi' >= 2^n > d
|
||||||
|
L0:
|
||||||
|
2* over -if L3 drop 1 + jump L4
|
||||||
|
L3: drop L4: push 2* pop
|
||||||
|
dup a xor -if L5 \ top bits of hi' and d differ:
|
||||||
|
drop -if NOSUB jump SUB \ hi' >= d iff hi' has it
|
||||||
|
L5: drop dup inv a + inv -if L6 \ same: hi' >= d iff hi'-d >= 0
|
||||||
|
drop jump NOSUB
|
||||||
|
L6: push drop pop jump SETBIT
|
||||||
|
SUB: inv a + inv \ hi' - d
|
||||||
|
SETBIT: push 1 + pop \ lo' is even: + 1 sets bit 0
|
||||||
|
NOSUB:
|
||||||
|
NEXT
|
||||||
|
over push push drop pop pop ; \ SWAP in line
|
||||||
|
\ Executed on the golden model (2026-10-02): exact, q*d + r = uhi:ulo with
|
||||||
|
\ r < d, for every uhi < ud at 32- and 64-bit cells. Outside that range
|
||||||
|
\ (uhi >= ud, including ud = 0) the result is unspecified, as in FORTH-79; it
|
||||||
|
\ always terminates after 32 steps. Stack use (D-2): up to 6 data cells may
|
||||||
|
\ lie under the three arguments and up to 6 return-stack entries under its
|
||||||
|
\ return address. For UM* the same limits are 6 and 6. This replaces a
|
||||||
|
\ first version built on U<, 0<, SWAP and OR calls, which was also exact but
|
||||||
|
\ left only 3 and 3 (too few for SM/REM inside /MOD) and stepped about five
|
||||||
|
\ times as many instruction words.
|
||||||
|
|
||||||
|
\ ---- signed division, truncating toward zero (v3 semantics) -------------
|
||||||
|
\ The quotient takes the sign of d xor n, the remainder the sign of d. Sign
|
||||||
|
\ tests are native -if (as in 0<) and NEGATE is in line, so the only calls
|
||||||
|
\ are DNEGATE and UM/MOD, both call-free inside.
|
||||||
|
: SM/REM ( d n -- rem quot )
|
||||||
|
over over xor push \ R: quotient sign (top bit)
|
||||||
|
over push \ R: + remainder sign (top bit of d)
|
||||||
|
-if L0 inv 1 + L0: push \ R: + |n|
|
||||||
|
-if L1 DNEGATE L1: \ |d|
|
||||||
|
pop UM/MOD \ urem uquot
|
||||||
|
pop -if L2 drop push inv 1 + pop jump L3 L2: drop L3:
|
||||||
|
pop -if L4 drop inv 1 + ; L4: drop ;
|
||||||
|
\ Executed on the golden model (2026-10-02): exact whenever the truncated
|
||||||
|
\ quotient fits a signed cell, at 32- and 64-bit cells. Stack use (D-2): up
|
||||||
|
\ to 5 data cells under the three arguments and 3 return-stack entries under
|
||||||
|
\ its return address; /MOD, one call further out, leaves 2. The first
|
||||||
|
\ version, built on ABS, DABS, 0< and NEGATE calls, overflowed the return
|
||||||
|
\ stack inside DABS -> DNEGATE -> D+ -> U> and never returned correctly for
|
||||||
|
\ a negative dividend.
|
||||||
|
```
|
||||||
|
|
||||||
|
`2DUP`, `-`, and `DNEGATE` are defined in §5; the compiler resolves forward references within the
|
||||||
|
core capsule.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Fate of every v3 primitive
|
||||||
|
|
||||||
|
Section numbers match the v3 primitive reference.
|
||||||
|
|
||||||
|
### 5.1 Stack
|
||||||
|
|
||||||
|
| Word | Fate | v4 definition / notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `DROP` | OP | `drop` |
|
||||||
|
| `DUP` | OP | `dup` |
|
||||||
|
| `OVER` | OP | `over` |
|
||||||
|
| `SWAP` | CAP | §4 |
|
||||||
|
| `?DUP` | CAP | `dup IF dup THEN` |
|
||||||
|
| `ROT` | CAP | §4 |
|
||||||
|
| `-ROT` | CAP | `ROT ROT` |
|
||||||
|
| `DEPTH` | RET | No visible stack pointer (D-2). |
|
||||||
|
| `PICK` | RET | No visible stack pointer (D-2). |
|
||||||
|
| `ROLL` | RET | No visible stack pointer (D-2); D-7 is moot. |
|
||||||
|
|
||||||
|
### 5.2 Return stack
|
||||||
|
|
||||||
|
| Word | Fate | v4 definition |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `>R` | IN | `push` |
|
||||||
|
| `R>` | IN | `pop` |
|
||||||
|
| `R@` | IN | `pop dup push` |
|
||||||
|
|
||||||
|
### 5.3 Memory
|
||||||
|
|
||||||
|
| Word | Fate | v4 definition / notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `@` | IN | `a! @` |
|
||||||
|
| `!` | IN | `a! !` |
|
||||||
|
| `+!` | CAP | `a! @ + !` |
|
||||||
|
| `-!` | CAP | `a! NEGATE @ + !` |
|
||||||
|
| `2@` | CAP | `a! @+ @` (low cell at `addr`, high at `addr+1`, as in v3) |
|
||||||
|
| `2!` | CAP | `a! SWAP !+ !` |
|
||||||
|
| `C@` | CAP | See below (D-1). Executed on the golden model (2026-10-03). |
|
||||||
|
| `C!` | CAP | See below (D-1). Executed on the golden model (2026-10-03). |
|
||||||
|
| `FILL` | CAP | See below. |
|
||||||
|
| `MOVE` | CAP | `push 2DUP U< IF pop CMOVE> ELSE pop CMOVE THEN` |
|
||||||
|
| `ERASE` | CAP | `0 FILL` |
|
||||||
|
| `CELLS` | IN | Empty (word-addressed). Becomes `2* 2*` if D-1 chooses bytes. |
|
||||||
|
|
||||||
|
```forth
|
||||||
|
\ byte access on a word-addressed node, little-endian: four bytes to a cell at
|
||||||
|
\ either cell width (so compiled code is the same, D-9), byte address = 4 * word
|
||||||
|
\ address + byte index. `@` and `!` inside are the opcodes, addressing through A.
|
||||||
|
: C@ ( baddr -- c )
|
||||||
|
dup 3 and 3 LSHIFT SWAP 2 RSHIFT a! @ SWAP RSHIFT 255 and ;
|
||||||
|
|
||||||
|
: C! ( c baddr -- )
|
||||||
|
dup 2 RSHIFT a! \ A = word address
|
||||||
|
3 and 3 LSHIFT \ c bits
|
||||||
|
SWAP 255 and over LSHIFT \ bits c'
|
||||||
|
SWAP 255 SWAP LSHIFT inv \ c' ~mask
|
||||||
|
@ and OR ! ;
|
||||||
|
|
||||||
|
: FILL ( baddr u c -- )
|
||||||
|
SWAP BEGIN dup WHILE 1- push 2DUP SWAP C! SWAP 1+ SWAP pop REPEAT 2DROP drop ;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Arithmetic
|
||||||
|
|
||||||
|
| Word | Fate | v4 definition / notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `+` | OP | `+` |
|
||||||
|
| `-` | CAP | `NEGATE +` |
|
||||||
|
| `*` | CAP | `UM* drop` |
|
||||||
|
| `/` | CAP | `/MOD NIP` |
|
||||||
|
| `MOD` `/MOD` `*/` `*/MOD` (§4 versions) | RET | Shadowed duplicates. Only the §6 versions survive. |
|
||||||
|
| `1+` `1-` `2+` `2-` | IN | `1 +`, `-1 +`, `2 +`, `-2 +` |
|
||||||
|
| `2*` | OP | `2*` |
|
||||||
|
| `2/` | OP | `2/` |
|
||||||
|
| `ABS` | CAP | `dup 0< IF NEGATE THEN` — executed on the golden model (2026-10-02). |
|
||||||
|
| `NEGATE` | CAP | §4 |
|
||||||
|
| `MIN` | CAP | `2DUP > IF SWAP THEN drop` |
|
||||||
|
| `MAX` | CAP | `2DUP < IF SWAP THEN drop` |
|
||||||
|
|
||||||
|
### 5.5 Logic and comparison
|
||||||
|
|
||||||
|
| Word | Fate | v4 definition / notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `AND` | OP | `and` |
|
||||||
|
| `XOR` | OP | `xor` |
|
||||||
|
| `OR` | CAP | §4 |
|
||||||
|
| `INVERT` | OP | `inv` |
|
||||||
|
| `NOT` | CAP | `0=` (FORTH-79 logical not, as in v3) |
|
||||||
|
| `LSHIFT` | CAP | `BEGIN dup WHILE 1- SWAP 2* SWAP REPEAT drop` — executed on the golden model (2026-10-03). |
|
||||||
|
| `RSHIFT` | CAP | `BEGIN dup WHILE 1- SWAP 2/ MSB inv and SWAP REPEAT drop` (clears the sign bit each step; `MSB` is the cell-width top-bit constant) — executed on the golden model (2026-10-03). |
|
||||||
|
| `0=` `0<` | CAP | §4 |
|
||||||
|
| `0<>` | CAP | `0= 0=` |
|
||||||
|
| `0>` | CAP | `dup 0< SWAP 0= OR 0=` (correct for the most negative number) |
|
||||||
|
| `=` | CAP | `xor 0=` — executed on the golden model (2026-10-02). |
|
||||||
|
| `<>` | CAP | `xor 0<>` |
|
||||||
|
| `<` | CAP | `2DUP xor 0< IF drop 0< ELSE - 0< THEN` (overflow-safe) — executed on the golden model (2026-10-02). |
|
||||||
|
| `>` | CAP | `SWAP <` |
|
||||||
|
| `<=` | CAP | `> 0=` |
|
||||||
|
| `>=` | CAP | `< 0=` |
|
||||||
|
| `U<` | CAP | §4 |
|
||||||
|
| `U>` | CAP | `SWAP U<` — executed on the golden model (2026-10-02). |
|
||||||
|
| `WITHIN` | CAP | `over - push - pop U<` |
|
||||||
|
| `TRUE` | IN | `-1` |
|
||||||
|
| `FALSE` | IN | `0` |
|
||||||
|
|
||||||
|
### 5.6 Mixed-precision arithmetic
|
||||||
|
|
||||||
|
A double is two 32-bit cells on a mesh node.
|
||||||
|
|
||||||
|
| Word | Fate | v4 definition |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `M+` | CAP | `S>D D+` — executed on the golden model (2026-10-02). |
|
||||||
|
| `M-` | CAP | `NEGATE M+` |
|
||||||
|
| `M*` | CAP | `2DUP xor push ABS SWAP ABS UM* pop 0< IF DNEGATE THEN` |
|
||||||
|
| `M/MOD` | CAP | `SM/REM` |
|
||||||
|
| `MOD` | CAP | `/MOD drop` |
|
||||||
|
| `/MOD` | CAP | `push S>D pop SM/REM` — executed on the golden model (2026-10-02). |
|
||||||
|
| `*/` | CAP | `*/MOD NIP` |
|
||||||
|
| `*/MOD` | CAP | `push M* pop SM/REM` |
|
||||||
|
|
||||||
|
### 5.7 Double-cell numbers
|
||||||
|
|
||||||
|
| Word | Fate | v4 definition |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `S>D` | CAP | `dup 0<` — executed on the golden model (2026-10-02). |
|
||||||
|
| `D+` | CAP | `push over push push drop pop over over xor -if L1 drop + -if C1 jump C0 L1: drop over -if L2 drop + jump C1 L2: drop + C0: pop pop + ; C1: pop pop + 1 + ;` — call-free. The carry out of the low cells is found by sign tests: if their top bits differ, there is a carry exactly when the sum's top bit is clear; if they match, exactly when both are set. Executed on the golden model (2026-10-02); leaves its caller 6 data cells under its arguments and 5 return entries. Replaces `push SWAP push over + 2DUP U> ROT drop NEGATE pop pop + +`, which was exact but left 1 return entry. |
|
||||||
|
| `DNEGATE` | CAP | `inv over if L1 drop push inv 1 + pop ; L1: drop 1 + ;` — call-free: `~d + 1`, carrying into the high cell exactly when the low cell is 0. Executed on the golden model (2026-10-02). Replaces `inv SWAP inv SWAP 1 0 D+`, which left a caller no return-stack room, so `DABS` could not run at all. |
|
||||||
|
| `D-` | CAP | `DNEGATE D+` — executed on the golden model (2026-10-02). |
|
||||||
|
| `DABS` | CAP | `dup 0< IF DNEGATE THEN` — executed on the golden model (2026-10-02). |
|
||||||
|
| `D0=` | CAP | `OR 0=` — executed on the golden model (2026-10-02). |
|
||||||
|
| `D0<` | CAP | `NIP 0<` |
|
||||||
|
| `D=` | CAP | `D- D0=` — executed on the golden model (2026-10-02). |
|
||||||
|
| `D<` | CAP | `ROT 2DUP = IF 2DROP U< ELSE SWAP < NIP NIP THEN` — executed on the golden model (2026-10-02). |
|
||||||
|
| `(D<)` | CAP | `( d1 d2 -- d1 d2 flag )`, call-free; internal to `DMAX` and `DMIN`. Copies of the high cells go on top; if they differ the flag comes from them, else from the low cells unsigned. The value tested always has its top bit set exactly when `d1 < d2`: `ah` (high signs differ), `ah - bh` (agree), `bl` (low top bits differ), `al - bl` (agree); `x - y` with `y` on top is `push inv pop + inv`. `dup push push over pop over over xor if TIE drop over over xor -if HS drop drop jump S1 HS: drop push inv pop + inv S1: -if N1 drop -1 jump D1 N1: drop 0 D1: pop SWAP ; TIE: drop drop drop dup push push over pop over over xor -if LS drop NIP jump S2 LS: drop push inv pop + inv S2: -if N2 drop -1 jump D2 N2: drop 0 D2: pop pop ROT ;` with `SWAP`, `NIP` and `ROT` in line. Executed on the golden model (2026-10-02). |
|
||||||
|
| `DMAX` | CAP | `(D<) if L drop push push drop drop pop pop ; L: drop drop drop ;` Executed on the golden model (2026-10-02). Replaces `2OVER 2OVER D< IF 2SWAP THEN 2DROP`, which needs 8 data cells plus `D<`'s 2: the whole 10-deep data stack, so it failed whenever the caller held anything at all (D-2). |
|
||||||
|
| `DMIN` | CAP | `(D<) if L drop drop drop ; L: drop push push drop drop pop pop ;` Executed on the golden model (2026-10-02); replaces `2OVER 2OVER D< 0= IF 2SWAP THEN 2DROP` for the same reason as `DMAX`. |
|
||||||
|
| `D2*` | CAP | `2* over 0< NEGATE OR SWAP 2* SWAP` |
|
||||||
|
| `D2/` | CAP | `dup 1 and push 2/ SWAP 1 RSHIFT pop IF MSB OR THEN SWAP` |
|
||||||
|
| `2DROP` | IN | `drop drop` |
|
||||||
|
| `2DUP` | IN | `over over` |
|
||||||
|
| `2SWAP` | CAP | `ROT push ROT pop`, with `ROT` and `SWAP` in line so it makes no calls. Executed on the golden model (2026-10-02). Called, `ROT` and `SWAP` left its caller 2 return entries; in line, 4. |
|
||||||
|
| `2OVER` | CAP | `push push 2DUP pop pop 2SWAP` — executed on the golden model (2026-10-02). |
|
||||||
|
| `2ROT` | CAP | `2>R 2SWAP 2R> 2SWAP` |
|
||||||
|
| `2>R` | IN | `SWAP push push` |
|
||||||
|
| `2R>` | IN | `pop pop SWAP` |
|
||||||
|
| `2R@` | IN | `pop pop 2DUP push push SWAP` |
|
||||||
|
|
||||||
|
### 5.8 Number formatting and output
|
||||||
|
|
||||||
|
All output reaches the console through `EMIT` (DEV).
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `<#` `#` `#S` `HOLD` `SIGN` `#>` | CAP | Standard pictured-output definitions over `UM/MOD` and a hold buffer. v3's tolerant `#>` (pops `ud` only if present) is not kept; v4 follows the standard stack effect. |
|
||||||
|
| `.` `.R` `U.` `U.R` `D.` `D.R` | CAP | Built on pictured output and `TYPE`. |
|
||||||
|
| `.S` | RET | No visible stack pointer (D-2). |
|
||||||
|
| `?` | CAP | `@ .` |
|
||||||
|
| `DUMP` | CAP | Loop over `@`/`C@` with pictured output. |
|
||||||
|
| `BASE` | CAP | Variable. |
|
||||||
|
| `DECIMAL` `HEX` `OCTAL` | CAP | `10 BASE !` and so on. |
|
||||||
|
|
||||||
|
### 5.9 Strings, parsing, and input
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `COUNT` | CAP | `dup 1+ SWAP C@` |
|
||||||
|
| `CMOVE` | CAP | See below. |
|
||||||
|
| `CMOVE>` | CAP | See below. |
|
||||||
|
| `BLANK` | CAP | `32 FILL` |
|
||||||
|
| `-TRAILING` | CAP | Loop from the end while the character is a space. |
|
||||||
|
| `COMPARE` | CAP | Byte loop returning `-1`, `0`, or `1`. v3's counted-string auto-detection is not kept. |
|
||||||
|
| `SEARCH` | CAP | Nested loop over `COMPARE`. Same note. |
|
||||||
|
| `SCAN` `SKIP` | CAP | Byte loops. |
|
||||||
|
| `BL` | IN | `32` |
|
||||||
|
| `EXPECT` `QUERY` | CC | Built on `KEY` (DEV). |
|
||||||
|
| `SPAN` `TIB` `>IN` `SOURCE` | CC | Interpreter state on the host node. |
|
||||||
|
| `WORD` `ENCLOSE` | CC | Parser. |
|
||||||
|
| `NUMBER` `CONVERT` | CC | Rewritten to honour `BASE` (v3's `NUMBER` is base 10 only). |
|
||||||
|
| `S"` `(s")` `[']` | CC | Compiler words. |
|
||||||
|
| `LITERAL` `[LITERAL]` (placeholders) | RET | The working `LITERAL` is in §5.17. |
|
||||||
|
|
||||||
|
```forth
|
||||||
|
: CMOVE ( src dst u -- )
|
||||||
|
BEGIN dup WHILE 1- push over C@ over C! 1+ SWAP 1+ SWAP pop REPEAT drop 2DROP ;
|
||||||
|
|
||||||
|
: CMOVE> ( src dst u -- )
|
||||||
|
BEGIN dup WHILE 1- push over R@ + C@ over R@ + C! pop REPEAT drop 2DROP ;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.10 Terminal I/O
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `EMIT` | DEV | Console service: one-character message. |
|
||||||
|
| `KEY` | DEV | Console service: blocking receive. |
|
||||||
|
| `?TERMINAL` | DEV | Console service: non-blocking status. |
|
||||||
|
| `TYPE` | CAP | Loop of `C@ EMIT`, or one string message to the console node. |
|
||||||
|
| `CR` | CAP | `10 EMIT` |
|
||||||
|
| `SPACE` | CAP | `BL EMIT` |
|
||||||
|
| `SPACES` | CAP | `BEGIN dup 0> WHILE SPACE 1- REPEAT drop` |
|
||||||
|
| `."` `(do-string)` | CC | Compiler words. |
|
||||||
|
|
||||||
|
### 5.11 Blocks and mass storage
|
||||||
|
|
||||||
|
The storage service belongs to the Artemis role, now a device node.
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `BLOCK` `BUFFER` `UPDATE` `SAVE-BUFFERS` `EMPTY-BUFFERS` `FLUSH` | DEV | Block-service messages. Buffers live in the requesting node's RAM or in DDR. |
|
||||||
|
| `LOAD` `THRU` `-->` | CC | The interpreter reads blocks through the service. |
|
||||||
|
| `LIST` | CAP | `BLOCK` plus `TYPE`. |
|
||||||
|
| `SCR` | CAP | Variable. |
|
||||||
|
| `BLK-CONFIRM-FORMAT` `RELOCATE-BLOCK` | DEV | Owner-only storage messages. |
|
||||||
|
| `BLK-ACL-ALLOW@` `BLK-ACL-ALLOW!` `BLK-ACL-TTL@` `BLK-ACL-TTL!` | HERA | ACL state is held by Hera. |
|
||||||
|
| `BLK-OWNER@` | HERA | Read-only, as in v3. |
|
||||||
|
| `BLK-ATTACH` | RET | Took a raw host pointer. Replaced by an attach message from the device node. |
|
||||||
|
|
||||||
|
### 5.12 Dictionary space
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `HERE` `ALIGN` `ALLOT` `,` `C,` `2,` `PAD` `LATEST` | CC | The dictionary lives on the host node. |
|
||||||
|
| `SP@` `SP!` | RET | No visible stack pointer (D-2). |
|
||||||
|
|
||||||
|
### 5.13 Dictionary manipulation
|
||||||
|
|
||||||
|
| Word | Fate |
|
||||||
|
| --- | --- |
|
||||||
|
| `'` `FIND` `SMUDGE` `HIDDEN` `>BODY` `>NAME` `NAME>` `>LINK` `LINK>` `CFA` `LFA` `NFA` `PFA` `TRAVERSE` `INTERPRET` | CC |
|
||||||
|
|
||||||
|
### 5.14 Vocabularies
|
||||||
|
|
||||||
|
| Word | Fate |
|
||||||
|
| --- | --- |
|
||||||
|
| `VOCABULARY` `DEFINITIONS` `CONTEXT` `CURRENT` `FORTH` `ORDER` `(FIND)` | CC |
|
||||||
|
|
||||||
|
### 5.15 System
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `(` `\` | CC | |
|
||||||
|
| `EXECUTE` | CAP | `push ;` (tail-jumps to the xt; the xt returns to `EXECUTE`'s caller) |
|
||||||
|
| `NOP` | OP | `nop` |
|
||||||
|
| `QUIT` `ABORT` `ABORT"` `(ABORT")` `COLD` `WARM` | CC | |
|
||||||
|
| `BYE` `REBOOT` | HERA | |
|
||||||
|
| `SAVE-SYSTEM` | HERA | Snapshot becomes a capsule-image request. |
|
||||||
|
| `WORDS` `VLIST` `SEE` | CC | |
|
||||||
|
| `PAGE` | DEV | |
|
||||||
|
| `79-STANDARD` | CC | |
|
||||||
|
|
||||||
|
### 5.16 Line editor
|
||||||
|
|
||||||
|
| Word | Fate |
|
||||||
|
| --- | --- |
|
||||||
|
| `L` `S` `SHOW` `EDIT` | CC |
|
||||||
|
|
||||||
|
### 5.17 Defining words and the compiler
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `:` `;` `CREATE` `VARIABLE` `CONSTANT` `DOES>` `IMMEDIATE` `STATE` `[` `]` `LITERAL` `COMPILE` `[COMPILE]` `FORGET` `FENCE` | CC | |
|
||||||
|
| `LIT` | OP | `@p` |
|
||||||
|
| `does_rt` | RET | Internal helper; `DOES>` is implemented by the compiler capsule. |
|
||||||
|
|
||||||
|
### 5.18 Control flow
|
||||||
|
|
||||||
|
| Word | Fate | v4 definition / notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `IF` `ELSE` `THEN` `BEGIN` `UNTIL` `AGAIN` `WHILE` `REPEAT` `DO` `?DO` `LOOP` `+LOOP` `LEAVE` `CASE` `OF` `ENDOF` `ENDCASE` | CC | Compile-time structure words. Expansions per §2 and below. |
|
||||||
|
| `EXIT` | OP | `;` |
|
||||||
|
| `(BRANCH)` | OP | `jump` |
|
||||||
|
| `(0BRANCH)` | IN | `if L … drop` (§2) |
|
||||||
|
| `(DO)` | IN | `SWAP push push` (R: limit index) |
|
||||||
|
| `(?DO)` | IN | `2DUP = IF 2DROP jump past-loop THEN SWAP push push` |
|
||||||
|
| `(LOOP)` | IN | See below. |
|
||||||
|
| `(+LOOP)` | IN | Sign-aware boundary-crossing test; specified in the compiler capsule. |
|
||||||
|
| `(LEAVE)` | IN | `pop drop pop dup push push jump loop-test` (sets index to limit) |
|
||||||
|
| `I` | IN | `pop dup push` |
|
||||||
|
| `J` | IN | `pop pop pop dup push SWAP push SWAP push` |
|
||||||
|
| `UNLOOP` | IN | `pop pop drop drop` |
|
||||||
|
|
||||||
|
```
|
||||||
|
(LOOP) expansion:
|
||||||
|
pop 1 + pop \ index' limit
|
||||||
|
2DUP xor \ index' limit flag (0 when equal)
|
||||||
|
if Lexit
|
||||||
|
drop push push \ R: limit index'
|
||||||
|
jump Lbody
|
||||||
|
Lexit: drop drop drop
|
||||||
|
```
|
||||||
|
|
||||||
|
`(LOOP)` terminates when the index reaches the limit, which matches v3 for every loop where
|
||||||
|
`start < limit`.
|
||||||
|
|
||||||
|
**New in v4:** `FOR`, `NEXT`, and `UNEXT` are native (§2). Counted loops that don't need an ascending
|
||||||
|
index should use them; they cost one instruction per iteration.
|
||||||
|
|
||||||
|
### 5.19 StarForth extensions
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ENTROPY@` `ENTROPY!` | MM | Per-call-target heat table (D-6). |
|
||||||
|
| `WORD-ENTROPY` `RESET-ENTROPY` `TOP-WORDS` | CC | Reports over the heat registers. |
|
||||||
|
| `(-` `INIT` | CC | |
|
||||||
|
| `VERSION` | CC | |
|
||||||
|
| `SEED` `RANDOM` | CAP | Deterministic PRNG (xorshift) in capsule code, reproducible from the seed. |
|
||||||
|
| `WAIT` | CAP | Loop until the `ANTICLOCK` register has advanced `n`. |
|
||||||
|
| `HEARTBEAT-TICKS@` | MM | `HEARTBEAT` register. |
|
||||||
|
| `ZUSE-AUTHENTICATE` `ZUSE-SESSION?` `ZUSE-PUBKEY@` `ZUSE-CERT-INSTALLED?` | HERA | |
|
||||||
|
|
||||||
|
### 5.20 Word-level ACL
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ACL-MODE@` `ACL-MODE!` `ACL-TTL@` `ACL-TTL!` `ACL-ALLOW@` `ACL-ALLOW!` `ACL-PINNED?` `ACL-PIN` `ACL-INHERIT` `ACL-INIT-PRIMITIVES` | HERA | Hera holds the ACL table. In the fabric, per-message ACL checks happen in the router (§6). |
|
||||||
|
| `ACL-HEAT@` | MM | |
|
||||||
|
| `ACL-WORD-ID` | CC | |
|
||||||
|
|
||||||
|
### 5.21 Physics: benchmark and diagnostics
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `BENCH-DICT-LOOKUP` `PHYSICS-CACHE-STATS` `PHYSICS-TOGGLE-CACHE` `PHYSICS-RESET-STATS` `PHYSICS-BUILD-INFO` | RET | Measure v3's software dictionary and hot-words cache, which do not exist on a node. |
|
||||||
|
| `PHYSICS-BAYESIAN-REPORT` | CAP | Host node only; kept as an analysis tool. |
|
||||||
|
| `PHYSICS-WORD-METRICS` `PHYSICS-CALC-KNOBS` `PHYSICS-BURN` `PHYSICS-SHOW-FEEDBACK` | RET | Never registered in v3. |
|
||||||
|
|
||||||
|
### 5.22 Physics: pipelining diagnostics
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `PIPELINING-*` (all six) | RET | Return as MM reports if D-6 adds transition counters. |
|
||||||
|
|
||||||
|
### 5.23 Physics: freeze, heat, and decay
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `FREEZE-WORD` `UNFREEZE-WORD` `FROZEN?` | MM | Freeze bit in the heat table. |
|
||||||
|
| `HEAT@` `HEAT!` | MM | Test-only write retained. |
|
||||||
|
| `SHOW-HEAT` `ALL-HEATS` | CC | |
|
||||||
|
| `DECAY-RATE@` | MM | Governor parameter register. |
|
||||||
|
| `FREEZE-CRITICAL` | CAP | Freezes the core set; the list is rewritten for v4 names. |
|
||||||
|
|
||||||
|
### 5.24 Dictionary heat optimisation
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `HEAT-PERCENTILES` `LOOKUP-STRATEGY@` `LOOKUP-STRATEGY!` `REORG-BUCKETS` `SHOW-HEAT-OPTIMIZATION` `COMPARE-LOOKUPS` | RET | Lookup strategy is a software-dictionary concern. The host compiler may keep a heat-ordered dictionary internally, but these words are not carried forward. |
|
||||||
|
|
||||||
|
### 5.25 Logging
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `LOG-ERROR` … `LOG-DEBUG` (levels) | IN | Constants. |
|
||||||
|
| `LOG-LEVEL!` `LOG-LEVEL@` | CAP | Variable. |
|
||||||
|
| `LOG-ERROR"` … `LOG-DEBUG"` | CC | |
|
||||||
|
| `LOG-*-STR` | DEV | Log-ring service on the recorder (the ARM). |
|
||||||
|
| `(do-log-*)` | CC | |
|
||||||
|
| `(LOG-APPEND-RAW)` | DEV | |
|
||||||
|
|
||||||
|
### 5.26 Q48.16 fixed-point math
|
||||||
|
|
||||||
|
A Q48.16 value is 64 bits, so on a 32-bit node it occupies **two cells** and every Q word is a double
|
||||||
|
word. Per D-10 it occupies two cells on a 64-bit node too. Per D-8, v4 Q values are signed.
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `Q.+` `Q.-` | CAP | `D+`, `D-` — executed on the golden model (2026-10-02) against v3's `q48_add`/`q48_sub` (`uint64_t` wrapping): bit-for-bit at 32-bit cells; at 64-bit cells the low cell is v3's, per D-10. |
|
||||||
|
| `Q.*` | CAP | `SWAP push over over UM* drop push push over pop -if L1 SWAP jump L2 L1: SWAP drop 0 L2: pop SWAP - push push over pop UM* pop + ROT pop dup push over -if L3 drop dup jump L4 L3: drop 0 L4: push UM* pop - D+ ROT pop UM* SWAP push 0 D+ pop push over pop SWAP Q.TO-INT push Q.TO-INT pop SWAP` (`SWAP` and `ROT` in line) — `floor(a*b / 2^16)`, signed (D-8), cut to two cells. Cells 0–2 of the product come from the unsigned cell products `a1*b1` (low cell), `a0*b1`, `a1*b0`, `a0*b0`, in that order, each input dropped after its last use and `b0` waiting on the return stack. Reading `a1` and `b1` as signed takes `(b1<0 ? a0 : 0)` and `(a1<0 ? b0 : 0)` off cell 2; each is folded in as soon as its operands are adjacent. The result is cells 0–2 shifted right 16 with `Q.TO-INT` twice. Executed on the golden model (2026-10-02) against an independent limb-by-limb reference, and against v3's `q48_mul` for non-negative operands. Leaves its caller 3 data cells under its arguments and 3 return entries. Clobbers `A`. |
|
||||||
|
| `Q./` | CAP | `over over OR if ZERO drop push over pop SWAP over xor (Q/) 4 + b! !b -if L1 DNEGATE L1: push push -if L2 DNEGATE L2: pop pop dup if CHK drop jump DIV CHK: drop over -131072 and if CHK2 drop jump DIV CHK2: drop push push dup pop dup push N-18 FOR 2* UNEXT over over xor -if SAME drop drop -if NOOV jump OV SAME: drop push inv pop + inv -if OV NOOV: drop pop pop jump DIV OV: drop drop drop pop pop drop drop (Q/) 4 + b! @b -if OVP drop 0 MSB ; OVP: drop -1 MAXHI ; DIV: (UQ/) (Q/) 4 + b! @b -if L3 drop DNEGATE ; L3: drop ; ZERO: drop drop drop NODE-ERROR b! -1 !b over over OR if Z0 drop -if ZP drop drop 0 MSB ; ZP: drop drop -1 MAXHI ; Z0: drop ;` — D-11: `a * 2^16 / b`, rounded toward zero; saturated to Q max / Q min on overflow; on division by zero, Q max / Q min by the dividend's sign (0 for `0 / 0`) and `NODE-ERROR` set. The sign of the result waits in `(Q/)`. Overflow needs no division: the quotient reaches `2^(2N-1)` exactly when `|a| * 2^16 >= |b| * 2^(2N-1)`, possible only for `|b| < 2^17`, where it is `a1 >= b0 << (N-17)` unsigned. `MSB` and `MAXHI` are the high cells of Q min and Q max. Executed on the golden model (2026-10-02) against an independent limb-by-limb reference (including `NODE-ERROR`), on width-native edge values, and against v3's `q48_div` for non-negative `a < 2^48`. Leaves its caller 3 data cells under its arguments and 3 return entries. Clobbers `A` and `B`. |
|
||||||
|
| `(UQ/)` | CAP | Internal to `Q./`: unsigned `floor(a * 2^16 / b)` for `b != 0` and no overflow. Restoring division, `2N+16` steps, on one shifting register — quotient (starting as `a`) above remainder — with the quotient and divisor in `(Q/)` so the stacks carry only the remainder, the quotient bit and the loop count. Each step's quotient bit enters at the next step's shift; with no overflow the bits leaving the quotient in the last 16 steps are zeros. Trial subtraction by in-line sign tests (`x - y` with `y` on top is `push inv pop + inv`), including the full double subtraction with its borrow, so the loop calls nothing but `D2*C`. Executed on the golden model (2026-10-02). Clobbers `A` and `B`. |
|
||||||
|
| `D2*C` | CAP | `( lo hi cin -- lo' hi' cout )`: the double shifted left one bit, `cin` entering at the bottom and `cout` the bit leaving the top (both 0 or 1). `a! dup -if L1 drop 1 jump L2 L1: drop 0 L2: push over -if L3 drop 1 jump L4 L3: drop 0 L4: over + + push 2* a + pop pop` — `cin` waits in `A`, and `2hi + m` is `over + +`, so it needs one return entry. Executed on the golden model (2026-10-02). Clobbers `A`. |
|
||||||
|
| `(Q/)` | CAP | Variable, 5 cells: `Q./`'s quotient register, divisor and result sign. Like `BASE` and the hold buffer, it lives in node memory. |
|
||||||
|
| `Q.ABS` `Q.NEG` | CAP | `DABS`, `DNEGATE` — executed on the golden model (2026-10-02) against v3's `q48_abs` and `0 - q`: bit-for-bit at 32-bit cells, including v3's wrap of Q min to itself; at 64-bit cells the low cell is v3's and the high cell is the true sign, per D-10. |
|
||||||
|
| `Q.COS` | CAP | v3's `q48_cos_approx`: on `xu = |(Q.REDUCE) x|`, `1 - xu^2/2! + xu^4/4! - …` to `n = 10`, run by `Q.SIN`'s loop: `(Q.REDUCE) -if A1 inv 1 + A1: 0 (QT) b! !b 0 over 0 Q.* drop (QT) 1 + b! !b 65536 (QT) 2 + b! !b 65536 (QT) 3 + b! !b 2 (QT) 4 + b! !b -1 (QT) 5 + b! !b jump TRIG` — term and sum start at 1.0, `n` at 2, and the sign cell is 0 so nothing is negated at the end. It jumps into the loop rather than calling it, so it costs no extra return entry. Executed on the golden model (2026-10-03): bit for bit v3's result on angles of every size and sign. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
|
||||||
|
| `Q.SIN` | CAP | v3's `q48_sin_approx`: on `r = (Q.REDUCE) x` and `xu = |r|`, `xu - xu^3/3! + xu^5/5! - …` to `n = 11`, each term the last times `xu^2` over `n(n-1)`, stopping once a term is below 10 ulp; negated if `r < 0`. After the reduction every value fits one cell, so the variable `(QT)` holds single cells and the only calls are `(Q.REDUCE)`, `Q.*`, `Q./` and `UM*`. The Taylor loop is written so that `Q.COS` can jump into it. Executed on the golden model (2026-10-03): bit for bit v3's result on angles of every size and sign. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
|
||||||
|
| `(Q.REDUCE)` | CAP | `( x -- r )`, v3's `q48_reduce_angle`: the angle reduced to one cell, `-pi <= r <= pi` with `pi = 205887`, `2pi = 411774` — the remainder of `x / 2pi` with the sign of `x`, then one step of `2pi` back into range. `x / 2pi` does not fit a cell, but only the remainder is wanted: `|x| mod 2pi` is two `UM/MOD` steps, the high cell first and its remainder then leading the low cell. `dup (QT) b! !b -if L1 DNEGATE L1: 0 411774 UM/MOD drop 411774 UM/MOD drop (QT) b! @b -if P1 drop inv 1 + jump J1 P1: drop J1: dup -205888 + -if BIG drop dup 205887 + -if OK drop 411774 + ; OK: drop ; BIG: drop -411774 + ;` Executed on the golden model (2026-10-03), bit for bit v3's. Clobbers `A` and `B`. |
|
||||||
|
| `(QT)` | CAP | Variable, 6 cells: `Q.SIN` / `Q.COS`'s sign, `x^2`, term, sum, `n` and subtract flag. |
|
||||||
|
| `Q.LOG` | CAP | v3's `q48_log_approx`: `x = 2^k * m` with `1.0 <= m < 2.0`; `ln m` by up to 6 Newton rounds on `e^y = m` from `y = m - 1.0`, stopping once `|m - e^y| < 100` ulp; result `y + k * ln 2` with `ln 2 = 45426`. `e^y` is `Q.EXP`'s Taylor loop **written in line** (flattened, ruled 2026-10-02), so `Q.LOG` calls only `Q.*`, `Q./` and `UM*` and the call chain is one level shorter than through `Q.EXP`. After the reduction `m`, `y`, the Taylor term and its sum each fit one cell at either width, so the variable `(QL)` holds single cells. `x <= 0` returns 0 and sets `NODE-ERROR` (D-12). Executed on the golden model (2026-10-03): bit for bit v3's result for every `x > 0` tested, including a sweep of the reduced `m`. v3's clamp `y = max(y - corr, 0)` is kept for fidelity but cannot fire: over all 65536 values of `m` it is never reached, and the iteration never needs more than 4 rounds. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
|
||||||
|
| `(QL)` | CAP | Variable, 7 cells: `Q.LOG`'s `m`, `y`, `k`, rounds left, Taylor term (then `delta`), sum, and `n * 1.0`. |
|
||||||
|
| `Q.SQRT` | CAP | v3's `q48_sqrt_approx`: Newton from `x0 = q/2 + 0.25`, up to 8 rounds of `x' = (x + q/x) / 2`, returning `x` as soon as `|x' - x| < 10` ulp; `sqrt(0) = 0`, `sqrt(1.0) = 1.0`. `q < 0` returns 0 and sets `NODE-ERROR` (D-12). `q`, `x` and the rounds left live in the variable `(QR)`. The halving is a logical double shift, `push a! 0 pop +* MAXHI and push drop a pop`. Executed on the golden model (2026-10-02): bit for bit v3's result for `0 <= q < 2^48`; above that v3's `q48_div` saturates and v4 divides correctly, so they part. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
|
||||||
|
| `(QR)` | CAP | Variable, 5 cells: `Q.SQRT`'s `q`, `x` and rounds left. |
|
||||||
|
| `Q.EXP` | CAP | v3's `q48_exp_approx`: `1 + x + x^2/2! + …` to 10 terms on `x = |q|`, stopping once a term is below 50 ulp; `1.0 Q./ e^|q|` for `q < 0`; `e^0 = 1.0`; `|q| >= 16.0` gives 0 (`q < 0`) or **Q max** (v3 returned all ones, which reads as −ulp under D-8). `x`, the term, the sum, the sign and the term index live in the variable `(QE)`, so only `Q.*` and `Q./` arguments sit on the stacks. Executed on the golden model (2026-10-02): bit for bit v3's result for `|q| < 16.0` (at 64-bit cells as the same value, high cell 0). Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
|
||||||
|
| `(QE)` | CAP | Variable, 8 cells: `Q.EXP`'s `x`, term, sum, sign and term index. |
|
||||||
|
| `Q.FROM-INT` | CAP | `push 0 a! 0 pop 15 FOR +* UNEXT push drop a pop` — `n * 2^16` as a signed double. `+*` with `S = 0` never adds, so each step is an exact arithmetic right shift of `T:A`; starting from `T:A = n:0` (that is, `n * 2^N`) and shifting `N-16` bits leaves `n * 2^16`. The count is `N-17`: 15 at 32-bit cells, 47 at 64. Negative values are no longer clamped to 0. Executed on the golden model (2026-10-02): matches v3's `q48_from_u64` for `n >= 0` (at 64-bit cells in the low cell, where v3 wraps for `n >= 2^47`, per D-10). Clobbers `A`. |
|
||||||
|
| `Q.TO-INT` | CAP | `push a! 0 pop 15 FOR +* UNEXT drop drop a` — the same `+*` shift, 16 bits at every cell width, leaving the low cell in `A`. Rounds toward minus infinity, as v3's arithmetic shift does (−1.5 gives −2). Executed on the golden model (2026-10-02): v3's `q48_to_u64` exactly at 64-bit cells, its low cell at 32. Clobbers `A`. |
|
||||||
|
| `Q.1` `Q.0` `Q.SCALE` | IN | Double-cell constants, placed in line as two literals, low cell first: `Q.1` and `Q.SCALE` are `65536 0` (1.0), `Q.0` is `0 0`. v3's are 65536, 0 and 65536. Executed on the golden model (2026-10-03): the values match v3's, `Q.1 Q.TO-INT` is 1, `1 Q.FROM-INT` is `Q.1`, and `q Q.1 Q.*` and `q Q.0 Q.+` return `q`. |
|
||||||
|
| `Q.=` `Q.<` `Q.>` `Q.0=` `Q.MAX` `Q.MIN` | CAP | `D=`, `D<`, `2SWAP D<` (for `Q.>`), `D0=`, `DMAX`, `DMIN`. Signed (D-8); v3 compared unsigned, so v3 agrees only when both values have the same sign. Executed on the golden model (2026-10-02). `Q.>` was given here as `SWAP D<`, which swaps single cells, not Q values, and gave a wrong answer in 11702 of 20169 test cases. |
|
||||||
|
| `Q.PRINT` | CAP | Pictured output, five fractional digits. |
|
||||||
|
|
||||||
|
### 5.27 Inference engine
|
||||||
|
|
||||||
|
The runtime governor moves into hardware as the multi-level Rolling Window of Truth (see
|
||||||
|
`JUSTIFICATION.md`). These words survive on the host node as analysis tools.
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `INFER-RUN` `INFER-WINDOW@` `INFER-DECAY@` `INFER-VARIANCE@` `INFER-FIT@` `INFER-EARLY-EXIT@` | CAP | Host node only. Ported from `inference_words.c`. |
|
||||||
|
| `Q.VARIANCE` `INFER-DECAY-SLOPE` `INFER-WINDOW-WIDTH` `WINDOW-DIVERSITY` | CAP | Host node only. |
|
||||||
|
| `L8-MODE` `L8-UPDATE` `L8-APPLY` `L8-TABLE-FORCE` | MM | Jacquard selector state becomes governor registers. `L8-TABLE-FORCE` remains the DoE override. |
|
||||||
|
| `BAYES-*` (all six) | RET | Model the v3 hot-words cache and bucket search. |
|
||||||
|
|
||||||
|
### 5.28 DEFER and IS
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `DEFER` `IS` `DEFER@` | CC | A deferred word's runtime is `@p push ;` followed by the stored xt. |
|
||||||
|
|
||||||
|
### 5.29 – 5.32 Framebuffer, keyboard, TrueType, scrollback
|
||||||
|
|
||||||
|
| Word | Fate |
|
||||||
|
| --- | --- |
|
||||||
|
| `PLOT` `FB-WIDTH` `FB-HEIGHT` | DEV |
|
||||||
|
| `KBD-SCAN` `KBD-DEBUG` `VKBD-EVENT` `VKBD-DEBUG` `KEY-EVENT` `ALT+TAB` | DEV |
|
||||||
|
| `TTF-TEXT` | DEV |
|
||||||
|
| `SCROLL-BACK` `SCROLL-FWD` | DEV |
|
||||||
|
|
||||||
|
### 5.33 Kernel REPL and DoE hooks
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `HB-ON` `HB-OFF` | MM | Recorder-enable register. The fabric pushes DoE rows into a FIFO; the ARM drains it to storage. |
|
||||||
|
| `BLK-ATTACH-ACK` `KH-BLK-ATTACH-SEND` `KH-ELEVATE-SEND` | RET | Kernel-Hermes calls. Hermes is now the fabric; these become ordinary packets (§6). |
|
||||||
|
|
||||||
|
### 5.34 Hera and child-VM words
|
||||||
|
|
||||||
|
| Word | Fate | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `BIRTH` `KILL` `START` `STOP` `USE` `EXEC` `EJECT` `CONNECT-HERMES` `CONNECT-ARTEMIS` `BYE` | HERA | A VM becomes a node (or a group of nodes). `BIRTH` loads a capsule into a node and releases it. |
|
||||||
|
| `VM-EXEC` `VM-CALL` `VM-STEP` | CAP | Built on `SEND` and `RECV` (§6). |
|
||||||
|
| `VM-HEAT` `VM-ERROR?` `VM-COUNT` `VM-CONSERVED?` `VM-PHYSICS-STATUS` | MM + HERA | Per-node heat is a register; fleet totals are Hera's. |
|
||||||
|
| `SWITCH-MARK-WORK` | RET | Nodes run concurrently; there is no context switch to mark. |
|
||||||
|
| `MAMA-VM-ID` `NAME>XT` | HERA, CC | |
|
||||||
|
| `CAPSULE-*` `WORKER-BIRTH` `UNATTENDED-BIRTH` `CONSOLE-ATTACH` | HERA | |
|
||||||
|
| `MINT` `MINT-SCRATCH` `MINT-SCRATCH-EMIT` `ZUSE-ELIGIBILITY-ADD` `ZUSE-ELIGIBLE?` `ELEVATE-PUBKEY-UNPACK` | HERA | |
|
||||||
|
| `RUNCAP-TEST` `PAIR-TEST` | HERA | Diagnostics, kept. |
|
||||||
|
| `STADIUM-ADMIT` `STADIUM-EVICT` `STADIUM-RES-PULL` `STADIUM-RES-PUSH` | HERA | Admission and reservoir are Hera's. |
|
||||||
|
| `STADIUM-HEAT@` `STADIUM-HEAT!` `STADIUM-RES@` `STADIUM-WORD-HEAT` | MM | |
|
||||||
|
|
||||||
|
### 5.35 Hosted lifecycle stubs
|
||||||
|
|
||||||
|
| Word | Fate |
|
||||||
|
| --- | --- |
|
||||||
|
| `BIRTH` `KILL` `PAUSE` `RESUME` `USE` (hosted) | RET — the hosted v4 golden model implements the real HERA messages. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Messaging between nodes
|
||||||
|
|
||||||
|
Each node has four neighbour ports (`PORT-UP`, `PORT-DOWN`, `PORT-LEFT`, `PORT-RIGHT`) mapped into its
|
||||||
|
address space. A read from a port blocks until the neighbour writes; a write blocks until the neighbour
|
||||||
|
reads. This is the GA144 model. No instruction is added: a port is an address.
|
||||||
|
|
||||||
|
### 6.1 Packet format (proposal)
|
||||||
|
|
||||||
|
| Word | Contents |
|
||||||
|
| --- | --- |
|
||||||
|
| 0 | Header: destination node (8 bits), type (8), TTL/heat (8), payload length in words (8). |
|
||||||
|
| 1 | ACL tag: sender identity fingerprint, checked by the router on every packet. |
|
||||||
|
| 2 … | Payload. |
|
||||||
|
|
||||||
|
The router in each node forwards packets not addressed to it, decrements TTL, and drops a packet whose
|
||||||
|
TTL reaches zero or whose ACL tag fails. This is v3 Hermes's per-message ACL check and unconditional TTL
|
||||||
|
expiry, moved into logic. A packet type `SOS` is reserved for any node to emit.
|
||||||
|
|
||||||
|
### 6.2 Send and receive
|
||||||
|
|
||||||
|
```forth
|
||||||
|
: SEND ( addr u port -- ) b! SWAP a! BEGIN dup WHILE 1- @+ !b REPEAT drop ;
|
||||||
|
: RECV ( addr u port -- ) b! SWAP a! BEGIN dup WHILE 1- @b !+ REPEAT drop ;
|
||||||
|
```
|
||||||
|
|
||||||
|
`A` walks the buffer and `B` holds the port, so each word moved costs one fetch and one store.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Memory-mapped registers
|
||||||
|
|
||||||
|
Addresses are assigned in the node memory map (D-4). Names only here.
|
||||||
|
|
||||||
|
| Register | Access | Contents |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `HEAT-OP[0..31]` | R | Per-opcode retirement heat. |
|
||||||
|
| `HEAT-CALL[...]` | R/W | Per-call-target heat, with freeze bit (D-6). |
|
||||||
|
| `ANTICLOCK` | R | Virtual tick: a pure function of the execution stream. |
|
||||||
|
| `HEARTBEAT` | R | Adaptive heartbeat count. |
|
||||||
|
| `GOV-*` | R/W | Governor parameters and Jacquard selector state (`L8-*`, decay rate). |
|
||||||
|
| `REC-ENABLE` | R/W | DoE recorder on/off (`HB-ON` / `HB-OFF`). |
|
||||||
|
| `PORT-STATUS` | R | Per-port ready flags, for non-blocking polls. |
|
||||||
|
| `NODE-ERROR` | R/W | Arithmetic error flag: set to −1 by `Q./` on division by zero (D-11) and by `Q.SQRT` / `Q.LOG` outside their domain (D-12), cleared by writing 0. `VM-ERROR?` reads it. |
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
# StarForth v4.0.0 — Justification
|
||||||
|
|
||||||
|
This document records why StarForth v4.0.0 exists, what it changes, and why each major decision was
|
||||||
|
made. The specification is `DECOMPOSITION.md`. Per project practice, this document is written before
|
||||||
|
any v4 code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. The problem with v3
|
||||||
|
|
||||||
|
StarForth v3 is a successful software machine. It boots on amd64, aarch64, and riscv64, runs the
|
||||||
|
Tripod fleet on LithosAnanke, and has held K≡1.0 across 38,400+ experimental runs. But it was designed
|
||||||
|
for a large host, and it shows:
|
||||||
|
|
||||||
|
- **More than 300 C primitives.** Most are not primitive in any hardware sense. Double-cell arithmetic,
|
||||||
|
string handling, comparisons, pictured output, and Q48.16 transcendentals are all expressible in a
|
||||||
|
handful of machine operations.
|
||||||
|
- **64-bit cells and 5 MB of linear memory per VM.** Reasonable on a PC; far too large for a node in a
|
||||||
|
fabric.
|
||||||
|
- **Diagnostics of its own implementation.** A significant block of words (hot-words cache statistics,
|
||||||
|
lookup strategies, pipelining metrics, Bayesian cache models) measures v3's software dictionary, not
|
||||||
|
the computation the dictionary performs.
|
||||||
|
- **Hermes in software.** Message routing, per-message ACL checks, and TTL expiry are C code executed by
|
||||||
|
a CPU that is also doing everything else.
|
||||||
|
|
||||||
|
None of this is wrong for a hosted or bare-metal OS. It is wrong for silicon. The project's direction
|
||||||
|
is now an FPGA embodiment, and eventually an ASIC, and v3 cannot be carried there by porting.
|
||||||
|
|
||||||
|
## 2. What v4 is
|
||||||
|
|
||||||
|
StarForth v4 is a Forth machine designed to be the same thing in software and in hardware:
|
||||||
|
|
||||||
|
1. **A 32-instruction core ISA** derived from Chuck Moore's F18, the node of the GA144. Every core word
|
||||||
|
is a mnemonic; every mnemonic is one 5-bit opcode.
|
||||||
|
2. **Everything else is capsule code**, compiled from those 32 instructions, or a message to a node that
|
||||||
|
owns a service, or a memory-mapped register, or retired.
|
||||||
|
3. **A mesh of small nodes** that talk to their four neighbours through blocking ports. Hermes becomes
|
||||||
|
the network itself: routing, ACL checks, and TTL expiry move into logic in every node's router.
|
||||||
|
4. **Compudynamics as a side effect of execution.** Heat counters, the anti-clock, and the heartbeat are
|
||||||
|
driven by instruction retirement in hardware. They cost no instructions.
|
||||||
|
5. **A power-aware governor** built from a multi-level Rolling Window of Truth, controlling timing only.
|
||||||
|
|
||||||
|
v4 is a new implementation, not a refactor. v3 remains the reference system for LithosAnanke until v4
|
||||||
|
reaches parity.
|
||||||
|
|
||||||
|
## 3. Why Moore's F18 instruction set
|
||||||
|
|
||||||
|
**It is proven minimal.** Moore spent decades removing instructions from his stack machines. The F18's
|
||||||
|
32 opcodes are the result: enough to build a complete Forth, nothing that can be composed from the
|
||||||
|
rest. There is no multiply, no divide, no compare, and no `SWAP`; each is a short sequence (for example
|
||||||
|
`SWAP` is `over push push drop pop pop`).
|
||||||
|
|
||||||
|
**It fits a 32-bit word exactly.** 32 opcodes need 5 bits. Six slots fill 30 bits of a 32-bit
|
||||||
|
instruction word, with 2 spare. One fetch feeds six instructions.
|
||||||
|
|
||||||
|
**It matches the project's formal-verification plan.** Proving 32 instruction semantics in Isabelle/HOL
|
||||||
|
is a bounded task. Proving 300 C primitives is not. Every higher word then inherits correctness from its
|
||||||
|
definition, which is itself a checkable object.
|
||||||
|
|
||||||
|
**It matches the dictionary-shrink plan that was already underway.** The existing POST suite, which
|
||||||
|
exercises every dictionary word, was to be used as a regression gate while C primitives were replaced by
|
||||||
|
colon definitions. v4 carries that plan to its end point: the surviving primitives are the ISA.
|
||||||
|
|
||||||
|
**It comes with a mesh precedent.** The GA144 places 144 F18 nodes on one die, each talking to its
|
||||||
|
neighbours through blocking ports. v4 adopts that topology directly.
|
||||||
|
|
||||||
|
## 4. Why a mesh, and why Hermes goes into the fabric
|
||||||
|
|
||||||
|
v3's Tripod is several VMs sharing one CPU, with Hermes arbitrating messages between them in software.
|
||||||
|
The mesh replaces time-sharing with space: each VM role runs on its own node or group of nodes,
|
||||||
|
concurrently.
|
||||||
|
|
||||||
|
Moving Hermes into the fabric has three consequences:
|
||||||
|
|
||||||
|
- **The message semantics become hardware.** Per-message ACL checks and unconditional TTL expiry, which
|
||||||
|
v3 already treats as rules rather than options, become router logic that cannot be bypassed.
|
||||||
|
- **Contention disappears as a scheduling problem.** A blocking port is flow control. There is no
|
||||||
|
scheduler to write, which honours the existing design goal of avoiding one.
|
||||||
|
- **The SOS mechanism generalises.** Any node can emit an `SOS` packet. A node whose router fails can
|
||||||
|
only stop forwarding, which its neighbours detect as blocked ports; this is the hardware form of v3's
|
||||||
|
"Hermes raises a semaphore while sinking" rule.
|
||||||
|
|
||||||
|
## 5. Why cell width becomes a parameter
|
||||||
|
|
||||||
|
The Zynq-7000's processing system is a Cortex-A9, a 32-bit ARMv7-A core. Rather than maintain a separate
|
||||||
|
32-bit fork, v4 makes cell width a build parameter of one VM (32 or 64). This has three benefits:
|
||||||
|
|
||||||
|
1. **The ARM becomes a real StarForth host**, not just a bootloader, at 32 bits.
|
||||||
|
2. **Mesh nodes use 32-bit cells**, roughly halving stack and ALU cost in the fabric.
|
||||||
|
3. **It opens a second invariance axis.** K≡1.0 has been shown invariant across amd64, aarch64, and
|
||||||
|
riscv64. If it also holds across cell widths, the conservation law is shown not to depend on word
|
||||||
|
size either. That is a stronger claim than ISA invariance alone.
|
||||||
|
|
||||||
|
The physics does not shrink with the cell. Heat and K arithmetic remain 64-bit (`int64_t` in C99 on
|
||||||
|
every host; double cells on a 32-bit node). Changing only the payload width keeps the experiment clean:
|
||||||
|
any difference in K can be attributed to cell width and not to lost precision.
|
||||||
|
|
||||||
|
## 6. Why the physics splits into "what" and "when"
|
||||||
|
|
||||||
|
The fabric can measure real power: the Zynq's XADC reads on-die temperature and supply voltages, and a
|
||||||
|
current sensor on the core rail gives true power draw. This makes "heat" a physical quantity rather than
|
||||||
|
a metaphor.
|
||||||
|
|
||||||
|
Physical measurements are noisy and never reproducible run to run. If they controlled which code
|
||||||
|
executes, parity hashes would break and formal proofs of behaviour would become impossible. v4
|
||||||
|
therefore splits the physics:
|
||||||
|
|
||||||
|
| Layer | Driven by | Controls | Property |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| **Virtual heat** | Instruction retirement and call counts | What executes (selection, promotion, eviction) | Deterministic and provable |
|
||||||
|
| **Physical power** | XADC and rail current | When things happen (clock gating, node sleep, message pacing) | Adaptive, never affects results |
|
||||||
|
|
||||||
|
This settles a question left open in the original FPGA concept: whether compudynamic feedback into the
|
||||||
|
control unit should affect only timing or also the execution path. The answer is both, through separate
|
||||||
|
channels: logic chooses *what*, physics chooses *when*.
|
||||||
|
|
||||||
|
## 7. Why the governor is a multi-level Rolling Window of Truth
|
||||||
|
|
||||||
|
The timing governor uses the project's own Rolling Window of Truth mechanism at three timescales:
|
||||||
|
|
||||||
|
| Window | Timescale | Governs |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Short | microseconds | Clock gating on one node |
|
||||||
|
| Medium | milliseconds | Node sleep and wake |
|
||||||
|
| Long | seconds | Thermal trend and mesh-wide message pacing |
|
||||||
|
|
||||||
|
Positive feedback (rising message load) wakes neighbouring nodes and raises the clock. Negative feedback
|
||||||
|
(rising temperature or power) throttles pacing and puts cool nodes to sleep. Each level reacts much more
|
||||||
|
slowly than the one below it, so the loops do not fight; hysteresis at each level prevents flapping at
|
||||||
|
thresholds. Hard limits (thermal ceiling, minimum clock) sit outside the adaptive layer as fixed logic.
|
||||||
|
|
||||||
|
A small neural network is a later candidate. Because the governor only controls timing, a poor governor
|
||||||
|
costs power or speed and never correctness, so it is a safe place to experiment. The DoE recorder
|
||||||
|
(below) produces exactly the training data such a network would need, so the two approaches can be
|
||||||
|
compared on identical workloads.
|
||||||
|
|
||||||
|
## 8. Division of labour on the Zynq
|
||||||
|
|
||||||
|
| Component | Runs on | Role |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Mesh nodes | Fabric | All StarForth execution, the anti-clock, heat counters, routers |
|
||||||
|
| Governor | Fabric | Multi-level RWT, single clock domain, cycle-exact |
|
||||||
|
| Host node | ARM (32-bit) | Boot and bitstream load, compiler capsule, console bridge, DoE recorder |
|
||||||
|
|
||||||
|
The anti-clock stays in the fabric because it is defined as a pure function of the execution stream and
|
||||||
|
must live where execution happens. The heartbeat's adaptive loop stays in the fabric because a loop
|
||||||
|
crossing the PS–PL boundary would inherit ARM-side jitter (caches, interrupts, bus latency).
|
||||||
|
|
||||||
|
The ARM's recorder role keeps measurement separate from the thing being measured: the fabric pushes
|
||||||
|
DoE rows into a FIFO, the ARM drains them to storage, and if the ARM falls behind rows are dropped
|
||||||
|
rather than execution stalled. This is the fabric form of the planned `HB-ON`/`HB-OFF` disk recording.
|
||||||
|
|
||||||
|
## 9. Why the compiler lives on the host node
|
||||||
|
|
||||||
|
GA144 nodes have 64 words of RAM and 64 of ROM, and arrayForth compiles on a host. v4 follows the same
|
||||||
|
split. The outer interpreter, dictionary, vocabularies, and defining words form the compiler capsule,
|
||||||
|
which runs on the host node. Mesh nodes receive compiled code. Large capsules stay in DDR and are
|
||||||
|
streamed to nodes as needed, so capsule size is not limited by node memory.
|
||||||
|
|
||||||
|
This is also why so many v3 words become CC rather than CAP in `DECOMPOSITION.md`: they are compiler
|
||||||
|
machinery, not computation.
|
||||||
|
|
||||||
|
## 10. Development path
|
||||||
|
|
||||||
|
Each stage is checked against the one before it. Nothing proceeds on trust.
|
||||||
|
|
||||||
|
1. **Hosted golden model.** A C99 implementation of the v4 ISA and node model, with cell width, node
|
||||||
|
count, and node memory as parameters. The POST suite, rewritten against v4 capsules, must pass at
|
||||||
|
both 32 and 64 bits, and K≡1.0 must hold.
|
||||||
|
2. **Hosted mesh.** Several golden-model nodes wired through simulated ports, running the Tripod roles
|
||||||
|
as nodes. The 144-node configuration is exercised here, since the host is not limited by fabric size.
|
||||||
|
3. **Co-simulation.** The node RTL is compiled with Verilator and run in lockstep with the golden model.
|
||||||
|
After every instruction, stacks, registers, and heat counters are compared. The first mismatch
|
||||||
|
identifies the faulty mnemonic exactly.
|
||||||
|
4. **FPGA.** A 2×2 mesh on the PZ7020, then the largest grid that fits. The bitstream only has to match
|
||||||
|
the co-simulation.
|
||||||
|
5. **ASIC.** A single v4 node, not the mesh, as a proof of silicon through an open-source shuttle
|
||||||
|
(currently Tiny Tapeout on IHP's SG13G2 130 nm open PDK). The same RTL is reused; block RAM is
|
||||||
|
replaced by the process's SRAM macros.
|
||||||
|
|
||||||
|
## 11. Scaling beyond the PZ7020
|
||||||
|
|
||||||
|
Node count, node memory, and cell width are parameters, and the mesh is generated by a loop over rows
|
||||||
|
and columns, so a larger board changes numbers, not design.
|
||||||
|
|
||||||
|
| Part | Approximate resources | Estimated nodes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Zynq-7020 | ~53K LUTs, 140 BRAM36 | ~8–16 |
|
||||||
|
| Zynq-7045 | ~218K LUTs, 545 BRAM36 | ~50–70 |
|
||||||
|
| Zynq UltraScale+ (e.g. Kria K26) | ~117K LUTs, 144 BRAM36, 64 UltraRAM | ~30–40, with much larger node memory |
|
||||||
|
| Larger UltraScale+ / Versal | Several hundred K LUTs and up | A full 144 |
|
||||||
|
|
||||||
|
Node counts are estimates. The first hardware measurement to take is the LUT cost of one node plus its
|
||||||
|
router on the 7020; every other board's capacity follows from that number.
|
||||||
|
|
||||||
|
UltraScale+ parts also change the host: their Cortex-A53 cores are aarch64, so the host node can run
|
||||||
|
64-bit StarForth while the mesh runs 32-bit cells, which the cell-width parameter already supports.
|
||||||
|
Larger meshes will need registered router-to-router links to close timing, and the free edition of
|
||||||
|
Vivado supports only smaller devices, so tool licensing must be checked before choosing a board.
|
||||||
|
|
||||||
|
## 12. Risks
|
||||||
|
|
||||||
|
| Risk | Mitigation |
|
||||||
|
| --- | --- |
|
||||||
|
| Capsule-level arithmetic is much slower than v3's C primitives on a hosted build. | Accepted. v4's measure of performance is the fabric, where each instruction is one cycle. The hosted build is a correctness oracle. |
|
||||||
|
| Word addressing makes byte and string operations expensive. | D-1 in `DECOMPOSITION.md` keeps the choice open; colorForth's packed, pre-parsed source is a proven alternative for text. |
|
||||||
|
| K≡1.0 may behave differently at 32-bit cell width. | That is an experimental result either way, and the hosted golden model finds it before any hardware exists. |
|
||||||
|
| Hand-traced definitions contain errors. | Every CAP definition is a POST target against the v3 C primitive it replaces. |
|
||||||
|
| The mesh does not fit the 7020 at a useful size. | Measure one node first; the design scales to larger parts unchanged. |
|
||||||
|
|
||||||
|
## 13. Relationship to intellectual property
|
||||||
|
|
||||||
|
v4 strengthens rather than replaces the existing claims. The Jacquard Selector, the Rolling Window of
|
||||||
|
Truth, and the Steady State Machine all survive, now as hardware structures. The new elements a filing
|
||||||
|
could draw on are: compudynamic heat as a zero-cost side effect of instruction retirement; the split of
|
||||||
|
deterministic virtual heat (selection) from physical power (timing); per-packet ACL and TTL enforcement
|
||||||
|
in a mesh router; and conservation invariance across cell width. Whether any of these belong in the
|
||||||
|
LithosAnanke filing is a question for counsel.
|
||||||
|
|
||||||
|
## 14. Definition of done for v4.0.0
|
||||||
|
|
||||||
|
- The 32-instruction ISA is specified, with every open decision in `DECOMPOSITION.md` §3 settled.
|
||||||
|
- The hosted golden model passes the rewritten POST suite at 32-bit and 64-bit cell widths.
|
||||||
|
- K≡1.0 holds on the golden model at both widths, on all three host ISAs.
|
||||||
|
- A hosted mesh runs the Tripod roles as nodes, with Hermes as the network.
|
||||||
|
- Verilator co-simulation of one node matches the golden model instruction for instruction.
|
||||||
|
|
||||||
|
## 15. Products
|
||||||
|
|
||||||
|
Ruled by Captain Bob, 2026-10-03. v4 turns one project into three products built on the same engine:
|
||||||
|
|
||||||
|
1. **Hosted StarForth F18.** The v4 engine as a native Linux build for amd64, arm64 and riscv64, on
|
||||||
|
real hardware.
|
||||||
|
2. **FPGA StarForth F18.** A 32-bit build of the engine loaded into the FPGA. This is the gateway into
|
||||||
|
building the whole system, and its foundation.
|
||||||
|
3. **StarshipOS.** The bare-metal product: LithosAnanke running the v4 F18 engine.
|
||||||
|
|
||||||
|
The FORTH-79 vocabulary, recomposed on the F18 engine (`DECOMPOSITION.md`), is stored as a capsule. The
|
||||||
|
StarshipOS-specific portions are recomposed and stored as capsules in the same way. The source tree
|
||||||
|
will be reorganised around this split.
|
||||||
|
|
||||||
|
## 16. Acceptance criteria
|
||||||
|
|
||||||
|
Ruled by Captain Bob, 2026-10-03. Until then v4 had none: this document and `v4/README.md` named a
|
||||||
|
POST suite and K≡1.0 on three host ISAs, neither of which exists yet, so nothing could be called
|
||||||
|
accepted.
|
||||||
|
|
||||||
|
- **v4 is equivalent to v3 at any point in time.** It must provide the same vocabularies and behave
|
||||||
|
the same way. The only difference is the machine underneath: the F18-derived engine instead of the
|
||||||
|
original StarForth VM.
|
||||||
|
- **Every ISA, hosted and bare metal, must still reach its `ok` prompt.** That is amd64, aarch64 and
|
||||||
|
riscv64, as a hosted build and as a bare-metal boot.
|
||||||
|
|
||||||
|
The existing acceptance tests keep their force and their rules: the three-architecture QEMU boot for
|
||||||
|
anything bare metal (`.claude/CLAUDE.md`), and the hosted three-architecture test
|
||||||
|
(`docs/lithosananke/hosted-acceptance-test/README.md`). Passing `make -C v4 test` is a development
|
||||||
|
check on the golden model, not acceptance.
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# Makefile.starkernel — StarKernel UEFI Build System
|
# kernel/Makefile — StarKernel UEFI Build System
|
||||||
# Multi-architecture: amd64 (x86_64), aarch64 (ARM64), riscv64 (RISC-V 64)
|
# Multi-architecture: amd64 (x86_64), aarch64 (ARM64), riscv64 (RISC-V 64)
|
||||||
#
|
#
|
||||||
# Quick start:
|
# Quick start:
|
||||||
# make -f Makefile.starkernel — build for host arch
|
# make -f kernel/Makefile — build for host arch
|
||||||
# make -f Makefile.starkernel ARCH=amd64 qemu — build + boot (x86_64)
|
# make -f kernel/Makefile ARCH=amd64 qemu — build + boot (x86_64)
|
||||||
# make -f Makefile.starkernel ARCH=aarch64 qemu — build + boot (aarch64)
|
# make -f kernel/Makefile ARCH=aarch64 qemu — build + boot (aarch64)
|
||||||
# make -f Makefile.starkernel ARCH=riscv64 qemu — build + boot (riscv64)
|
# make -f kernel/Makefile ARCH=riscv64 qemu — build + boot (riscv64)
|
||||||
# make -f Makefile.starkernel help — show all targets
|
# make -f kernel/Makefile help — show all targets
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -64,7 +64,7 @@ $(eval $(call kconfig_bool,STARFORTH_ENABLE_VM,1))
|
|||||||
MONOLITHIC ?= 1
|
MONOLITHIC ?= 1
|
||||||
|
|
||||||
# Parallel build — use all available cores when not cleaning
|
# Parallel build — use all available cores when not cleaning
|
||||||
# (adding -j when 'clean' is also a goal causes a race on include/version.h)
|
# (adding -j when 'clean' is also a goal causes a race on v3/include/version.h)
|
||||||
NPROC := $(shell nproc 2>/dev/null || echo 4)
|
NPROC := $(shell nproc 2>/dev/null || echo 4)
|
||||||
ifeq ($(filter clean,$(MAKECMDGOALS)),)
|
ifeq ($(filter clean,$(MAKECMDGOALS)),)
|
||||||
MAKEFLAGS += -j$(NPROC)
|
MAKEFLAGS += -j$(NPROC)
|
||||||
@@ -106,9 +106,9 @@ KERNEL_OBJ_DIR := $(OBJ_DIR)/kernel
|
|||||||
LOADER_EFI := $(BUILD_DIR)/starkernel_loader.efi
|
LOADER_EFI := $(BUILD_DIR)/starkernel_loader.efi
|
||||||
KERNEL_ELF := $(BUILD_DIR)/starkernel_kernel.elf
|
KERNEL_ELF := $(BUILD_DIR)/starkernel_kernel.elf
|
||||||
|
|
||||||
KERNEL_SRC := src/starkernel
|
KERNEL_SRC := kernel/src
|
||||||
KERNEL_INC := include/starkernel
|
KERNEL_INC := kernel/include/starkernel
|
||||||
STARFORTH_CONFIG_HEADER := include/starforth_config.h
|
STARFORTH_CONFIG_HEADER := v3/include/starforth_config.h
|
||||||
|
|
||||||
QEMU_LOG_DIR := logs
|
QEMU_LOG_DIR := logs
|
||||||
QEMU_ISO := $(BUILD_DIR)/starkernel.iso
|
QEMU_ISO := $(BUILD_DIR)/starkernel.iso
|
||||||
@@ -129,17 +129,17 @@ ARTDISK ?= disk/artemis.img
|
|||||||
# headroom above the current 9-device identity roster (Zuse + 8 minted identities)
|
# headroom above the current 9-device identity roster (Zuse + 8 minted identities)
|
||||||
# rather than matching it exactly, per Bob's standing ruling against hardcoding a bound
|
# rather than matching it exactly, per Bob's standing ruling against hardcoding a bound
|
||||||
# to today's scale (FABRIC-3.md §VII.4). Override if a future test needs more, e.g.
|
# to today's scale (FABRIC-3.md §VII.4). Override if a future test needs more, e.g.
|
||||||
# `make -f Makefile.starkernel qemu XHCI_PORTS=32`.
|
# `make -f kernel/Makefile qemu XHCI_PORTS=32`.
|
||||||
XHCI_PORTS ?= 16
|
XHCI_PORTS ?= 16
|
||||||
# ZUSEDISK -- Zuse's own minted USB thumbdrive, attached by default so a
|
# ZUSEDISK -- Zuse's own minted USB thumbdrive, attached by default so a
|
||||||
# plain `make qemu` lands already authenticated into the Zuse identity
|
# plain `make qemu` lands already authenticated into the Zuse identity
|
||||||
# (Captain Bob, 2026-08-28). Attached at QEMU launch time via -drive/
|
# (Captain Bob, 2026-08-28). Attached at QEMU launch time via -drive/
|
||||||
# -device on the qemu-xhci controller each arch's qemu target already
|
# -device on the qemu-xhci controller each arch's qemu target already
|
||||||
# creates; xhci_bringup()'s initial port scan (src/starkernel/usb/xhci.c)
|
# creates; xhci_bringup()'s initial port scan (kernel/src/usb/xhci.c)
|
||||||
# is what makes an already-connected-at-launch device visible at all --
|
# is what makes an already-connected-at-launch device visible at all --
|
||||||
# xhci_poll_events() alone is purely hotplug-event-driven and would never
|
# xhci_poll_events() alone is purely hotplug-event-driven and would never
|
||||||
# see a device present before controller reset. Override to "" (empty) to
|
# see a device present before controller reset. Override to "" (empty) to
|
||||||
# boot without Zuse attached, e.g. `make -f Makefile.starkernel qemu ZUSEDISK=`.
|
# boot without Zuse attached, e.g. `make -f kernel/Makefile qemu ZUSEDISK=`.
|
||||||
ZUSEDISK ?= disk/thumbdrives/zuse-thumb-ident.img
|
ZUSEDISK ?= disk/thumbdrives/zuse-thumb-ident.img
|
||||||
# Precomputed (not inlined as $(if ...,...)) because the drive/device specs
|
# Precomputed (not inlined as $(if ...,...)) because the drive/device specs
|
||||||
# below are comma-heavy and GNU make's $(if) function splits its own
|
# below are comma-heavy and GNU make's $(if) function splits its own
|
||||||
@@ -153,10 +153,10 @@ else
|
|||||||
ZUSEDISK_QEMU_ARGS :=
|
ZUSEDISK_QEMU_ARGS :=
|
||||||
endif
|
endif
|
||||||
MKCAPSULE_SRC = tools/mkcapsule.c tools/pkcs8_ed25519.c \
|
MKCAPSULE_SRC = tools/mkcapsule.c tools/pkcs8_ed25519.c \
|
||||||
src/starkernel/crypto/ed25519.c \
|
kernel/src/crypto/ed25519.c \
|
||||||
src/starkernel/crypto/fe25519.c \
|
kernel/src/crypto/fe25519.c \
|
||||||
src/starkernel/crypto/scalar25519.c \
|
kernel/src/crypto/scalar25519.c \
|
||||||
src/starkernel/crypto/sha512.c
|
kernel/src/crypto/sha512.c
|
||||||
MKCAPSULE_BIN = $(BUILD_DIR)/tools/mkcapsule
|
MKCAPSULE_BIN = $(BUILD_DIR)/tools/mkcapsule
|
||||||
# Milestone 6 (Phase 8): the snakeoil intermediate's private key, generated
|
# Milestone 6 (Phase 8): the snakeoil intermediate's private key, generated
|
||||||
# offline outside this repo entirely (see FABRIC-2.md's Phase 8 §Milestone 6
|
# offline outside this repo entirely (see FABRIC-2.md's Phase 8 §Milestone 6
|
||||||
@@ -169,6 +169,28 @@ CAPSULE_GENERATED = $(BUILD_DIR)/capsule_generated.c
|
|||||||
CAPSULE_GENERATED_OBJ = $(BUILD_DIR)/capsule_generated.o
|
CAPSULE_GENERATED_OBJ = $(BUILD_DIR)/capsule_generated.o
|
||||||
CAPSULE_GENERATED_KOBJ = $(KERNEL_OBJ_DIR)/capsule_generated.o
|
CAPSULE_GENERATED_KOBJ = $(KERNEL_OBJ_DIR)/capsule_generated.o
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# BOARD (boot_image)
|
||||||
|
# ==============================================================================
|
||||||
|
# BOARD names a directory under boards/ (ser5, raspi, milkv, ...). Its
|
||||||
|
# board.mk sets BOARD_ARCH, BOARD_BOOT (how the firmware finds us) and
|
||||||
|
# BOARD_IMAGE (the single file written to the boot medium). The root
|
||||||
|
# Makefile's `make boot_image TARGET=<NAME>` maps NAME to BOARD and passes
|
||||||
|
# ARCH=$(BOARD_ARCH); the kernel objects themselves are the ordinary
|
||||||
|
# build/$(ARCH)/kernel ones, shared with `make qemu`.
|
||||||
|
BOARD ?=
|
||||||
|
ifneq ($(strip $(BOARD)),)
|
||||||
|
ifeq ($(wildcard boards/$(BOARD)/board.mk),)
|
||||||
|
$(error Unknown BOARD='$(BOARD)': no boards/$(BOARD)/board.mk)
|
||||||
|
endif
|
||||||
|
include boards/$(BOARD)/board.mk
|
||||||
|
ifneq ($(BOARD_ARCH),$(ARCH))
|
||||||
|
$(error BOARD=$(BOARD) is ARCH=$(BOARD_ARCH), but ARCH=$(ARCH) was requested)
|
||||||
|
endif
|
||||||
|
BOARD_OUT := build/boards/$(BOARD)
|
||||||
|
endif
|
||||||
|
BOOT_IMAGE_SIZE_MIB ?= 128
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# TOOLCHAIN
|
# TOOLCHAIN
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -195,8 +217,8 @@ ifeq ($(ARCH),amd64)
|
|||||||
# correctly. These flags come after COMMON_CFLAGS on the command line so
|
# correctly. These flags come after COMMON_CFLAGS on the command line so
|
||||||
# they win for amd64 only; riscv64/aarch64 keep -fPIC.
|
# they win for amd64 only; riscv64/aarch64 keep -fPIC.
|
||||||
ARCH_CFLAGS := -m64 -march=x86-64 -mno-red-zone -DARCH_AMD64 -fno-pic -fno-pie
|
ARCH_CFLAGS := -m64 -march=x86-64 -mno-red-zone -DARCH_AMD64 -fno-pic -fno-pie
|
||||||
LOADER_LINKER_SCRIPT := linker/starkernel-loader-amd64.ld
|
LOADER_LINKER_SCRIPT := kernel/linker/starkernel-loader-amd64.ld
|
||||||
KERNEL_LINKER_SCRIPT := linker/starkernel-kernel-amd64.ld
|
KERNEL_LINKER_SCRIPT := kernel/linker/starkernel-kernel-amd64.ld
|
||||||
|
|
||||||
else ifeq ($(ARCH),aarch64)
|
else ifeq ($(ARCH),aarch64)
|
||||||
ifneq ($(shell which aarch64-linux-gnu-gcc 2>/dev/null),)
|
ifneq ($(shell which aarch64-linux-gnu-gcc 2>/dev/null),)
|
||||||
@@ -211,14 +233,16 @@ else ifeq ($(ARCH),aarch64)
|
|||||||
$(error No ARM64 toolchain found. Install aarch64-linux-gnu-gcc or aarch64-none-elf-gcc)
|
$(error No ARM64 toolchain found. Install aarch64-linux-gnu-gcc or aarch64-none-elf-gcc)
|
||||||
endif
|
endif
|
||||||
# aarch64 loader uses clang + lld-link (COFF target — GCC cannot produce aarch64 PE)
|
# aarch64 loader uses clang + lld-link (COFF target — GCC cannot produce aarch64 PE)
|
||||||
LOADER_CC := clang-18
|
# Any clang with the aarch64 COFF backend works; the version is not
|
||||||
# Debian/Ubuntu package only installs the versioned lld-link-18 on PATH by
|
# pinned (Ubuntu 26.04 ships clang-21, older hosts clang-18).
|
||||||
# default (unversioned lld-link lives under /usr/lib/llvm-18/bin, which
|
LOADER_CC := $(shell which clang 2>/dev/null || ls /usr/bin/clang-[0-9]* 2>/dev/null | sort -V | tail -1 || echo clang)
|
||||||
|
# Debian/Ubuntu package only installs the versioned lld-link-NN on PATH by
|
||||||
|
# default (unversioned lld-link lives under /usr/lib/llvm-NN/bin, which
|
||||||
# isn't on PATH unless explicitly prepended) — detect whichever resolves.
|
# isn't on PATH unless explicitly prepended) — detect whichever resolves.
|
||||||
LOADER_LD := $(shell which lld-link 2>/dev/null || which lld-link-18 2>/dev/null || echo lld-link)
|
LOADER_LD := $(shell which lld-link 2>/dev/null || ls /usr/bin/lld-link-[0-9]* /usr/lib/llvm-*/bin/lld-link 2>/dev/null | sort -V | tail -1 || echo lld-link)
|
||||||
ARCH_CFLAGS := -march=armv8-a -mcpu=cortex-a72 -DARCH_AARCH64 -mno-outline-atomics
|
ARCH_CFLAGS := -march=armv8-a -mcpu=cortex-a72 -DARCH_AARCH64 -mno-outline-atomics
|
||||||
LOADER_LINKER_SCRIPT := linker/starkernel-loader-aarch64.ld
|
LOADER_LINKER_SCRIPT := kernel/linker/starkernel-loader-aarch64.ld
|
||||||
KERNEL_LINKER_SCRIPT := linker/starkernel-kernel-aarch64.ld
|
KERNEL_LINKER_SCRIPT := kernel/linker/starkernel-kernel-aarch64.ld
|
||||||
|
|
||||||
else ifeq ($(ARCH),riscv64)
|
else ifeq ($(ARCH),riscv64)
|
||||||
ifneq ($(shell which riscv64-unknown-elf-gcc 2>/dev/null),)
|
ifneq ($(shell which riscv64-unknown-elf-gcc 2>/dev/null),)
|
||||||
@@ -233,8 +257,8 @@ else ifeq ($(ARCH),riscv64)
|
|||||||
$(error No RISC-V toolchain found. Install riscv64-unknown-elf-gcc or riscv64-linux-gnu-gcc)
|
$(error No RISC-V toolchain found. Install riscv64-unknown-elf-gcc or riscv64-linux-gnu-gcc)
|
||||||
endif
|
endif
|
||||||
ARCH_CFLAGS := -march=rv64gc -mabi=lp64d -DARCH_RISCV64
|
ARCH_CFLAGS := -march=rv64gc -mabi=lp64d -DARCH_RISCV64
|
||||||
LOADER_LINKER_SCRIPT := linker/starkernel-loader-riscv64.ld
|
LOADER_LINKER_SCRIPT := kernel/linker/starkernel-loader-riscv64.ld
|
||||||
KERNEL_LINKER_SCRIPT := linker/starkernel-kernel-riscv64.ld
|
KERNEL_LINKER_SCRIPT := kernel/linker/starkernel-kernel-riscv64.ld
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Default loader toolchain = kernel toolchain (overridden for aarch64 above)
|
# Default loader toolchain = kernel toolchain (overridden for aarch64 above)
|
||||||
@@ -260,13 +284,13 @@ COMMON_CFLAGS := \
|
|||||||
-ffreestanding -nostdlib -fno-builtin \
|
-ffreestanding -nostdlib -fno-builtin \
|
||||||
-fPIC -fvisibility=hidden -fno-stack-protector -fshort-wchar \
|
-fPIC -fvisibility=hidden -fno-stack-protector -fshort-wchar \
|
||||||
$(ARCH_CFLAGS) \
|
$(ARCH_CFLAGS) \
|
||||||
-I$(KERNEL_INC) -Iinclude -Isrc \
|
-I$(KERNEL_INC) -Iv3/include -Ikernel/include -Iv3/src \
|
||||||
-include $(STARFORTH_CONFIG_HEADER) \
|
-include $(STARFORTH_CONFIG_HEADER) \
|
||||||
-DPARITY_MODE=$(PARITY_MODE)
|
-DPARITY_MODE=$(PARITY_MODE)
|
||||||
|
|
||||||
VMCORE_CFLAGS_COMMON := \
|
VMCORE_CFLAGS_COMMON := \
|
||||||
$(filter-out -I$(KERNEL_INC),$(COMMON_CFLAGS)) \
|
$(filter-out -I$(KERNEL_INC),$(COMMON_CFLAGS)) \
|
||||||
-Iinclude -I. -Isrc/word_source -Isrc/test_runner/include \
|
-Iv3/include -Ikernel/include -I. -Iv3/src/word_source -Iv3/src/test_runner/include \
|
||||||
-Wno-error=unused-parameter -Wno-error=shift-negative-value \
|
-Wno-error=unused-parameter -Wno-error=shift-negative-value \
|
||||||
-Wno-error=sign-compare -Wno-error=missing-field-initializers
|
-Wno-error=sign-compare -Wno-error=missing-field-initializers
|
||||||
|
|
||||||
@@ -281,8 +305,8 @@ LOADER_BASE_CFLAGS := \
|
|||||||
-mno-stack-arg-probe \
|
-mno-stack-arg-probe \
|
||||||
-march=armv8-a \
|
-march=armv8-a \
|
||||||
-DARCH_AARCH64 \
|
-DARCH_AARCH64 \
|
||||||
-I$(KERNEL_INC) -Iinclude -Isrc \
|
-I$(KERNEL_INC) -Iv3/include -Ikernel/include -Iv3/src \
|
||||||
-Iinclude/starkernel/freestanding \
|
-Ikernel/include/starkernel/freestanding \
|
||||||
-include $(STARFORTH_CONFIG_HEADER) \
|
-include $(STARFORTH_CONFIG_HEADER) \
|
||||||
-DPARITY_MODE=$(PARITY_MODE) \
|
-DPARITY_MODE=$(PARITY_MODE) \
|
||||||
-DPLATFORM_TIME_NO_INLINE
|
-DPLATFORM_TIME_NO_INLINE
|
||||||
@@ -308,7 +332,7 @@ ifeq ($(ARCH),aarch64)
|
|||||||
else ifeq ($(ARCH),riscv64)
|
else ifeq ($(ARCH),riscv64)
|
||||||
LOADER_LDFLAGS_PE := # ELF→objcopy pipeline in link rule
|
LOADER_LDFLAGS_PE := # ELF→objcopy pipeline in link rule
|
||||||
else
|
else
|
||||||
LOADER_LINKER_SCRIPT_PE := linker/starkernel-loader-amd64-pe.ld
|
LOADER_LINKER_SCRIPT_PE := kernel/linker/starkernel-loader-amd64-pe.ld
|
||||||
LOADER_LDFLAGS_PE := \
|
LOADER_LDFLAGS_PE := \
|
||||||
-T $(LOADER_LINKER_SCRIPT_PE) -m i386pep -nostdlib \
|
-T $(LOADER_LINKER_SCRIPT_PE) -m i386pep -nostdlib \
|
||||||
--enable-reloc-section --image-base 0 --subsystem 10 -e efi_main
|
--enable-reloc-section --image-base 0 --subsystem 10 -e efi_main
|
||||||
@@ -429,7 +453,7 @@ LOADER_CFLAGS += $(VM_FEATURE_OVERRIDES)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
# SK_CMD — optional startup FORTH script injected before the interactive REPL.
|
# SK_CMD — optional startup FORTH script injected before the interactive REPL.
|
||||||
# Usage: make -f Makefile.starkernel qemu SK_CMD="TIME-TICKS . BYE"
|
# Usage: make -f kernel/Makefile qemu SK_CMD="TIME-TICKS . BYE"
|
||||||
ifdef SK_CMD
|
ifdef SK_CMD
|
||||||
KERNEL_CFLAGS += -DSK_STARTUP_FORTH='"$(SK_CMD)"'
|
KERNEL_CFLAGS += -DSK_STARTUP_FORTH='"$(SK_CMD)"'
|
||||||
LOADER_CFLAGS += -DSK_STARTUP_FORTH='"$(SK_CMD)"'
|
LOADER_CFLAGS += -DSK_STARTUP_FORTH='"$(SK_CMD)"'
|
||||||
@@ -439,7 +463,7 @@ endif
|
|||||||
# The loader reads this file from the EFI partition and parses it at boot.
|
# The loader reads this file from the EFI partition and parses it at boot.
|
||||||
# Supported flags: --doe --log-level=<debug|info|warn|error>
|
# Supported flags: --doe --log-level=<debug|info|warn|error>
|
||||||
# --stack=<N>[MG] --heap=<N>[MG]
|
# --stack=<N>[MG] --heap=<N>[MG]
|
||||||
# Usage: make -f Makefile.starkernel qemu KERNEL_ARGS="--doe --log-level=info"
|
# Usage: make -f kernel/Makefile qemu KERNEL_ARGS="--doe --log-level=info"
|
||||||
KERNEL_ARGS ?=
|
KERNEL_ARGS ?=
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -534,19 +558,19 @@ KERNEL_ASM := $(wildcard $(KERNEL_SRC)/arch/$(ARCH)/*.S)
|
|||||||
|
|
||||||
ifeq ($(STARFORTH_ENABLE_VM),1)
|
ifeq ($(STARFORTH_ENABLE_VM),1)
|
||||||
VM_ALL_SRCS := \
|
VM_ALL_SRCS := \
|
||||||
$(wildcard src/*.c) \
|
$(wildcard v3/src/*.c) \
|
||||||
$(wildcard src/word_source/*.c) \
|
$(wildcard v3/src/word_source/*.c) \
|
||||||
$(wildcard src/test_runner/*.c) \
|
$(wildcard v3/src/test_runner/*.c) \
|
||||||
$(wildcard src/test_runner/modules/*.c)
|
$(wildcard v3/src/test_runner/modules/*.c)
|
||||||
|
|
||||||
VM_EXCLUDE := \
|
VM_EXCLUDE := \
|
||||||
src/main.c src/cli.c src/repl.c \
|
v3/src/main.c v3/src/cli.c v3/src/repl.c \
|
||||||
src/platform/% \
|
v3/src/platform/% \
|
||||||
src/blkio_factory.c src/blkio_file.c \
|
v3/src/blkio_factory.c v3/src/blkio_file.c \
|
||||||
src/log.c src/doe_metrics.c \
|
v3/src/log.c v3/src/doe_metrics.c \
|
||||||
src/vm.c src/vm_core.c src/vm_bootstrap.c src/vm_runtime.c src/vm_time.c \
|
v3/src/vm.c v3/src/vm_core.c v3/src/vm_bootstrap.c v3/src/vm_runtime.c v3/src/vm_time.c \
|
||||||
src/word_source/q48_16_words.c \
|
v3/src/word_source/q48_16_words.c \
|
||||||
src/test_runner/modules/break_me_tests.c
|
v3/src/test_runner/modules/break_me_tests.c
|
||||||
|
|
||||||
VM_CORE_SRCS := $(filter-out $(VM_EXCLUDE),$(VM_ALL_SRCS))
|
VM_CORE_SRCS := $(filter-out $(VM_EXCLUDE),$(VM_ALL_SRCS))
|
||||||
|
|
||||||
@@ -569,8 +593,8 @@ LOADER_EXTRA_SRCS := \
|
|||||||
|
|
||||||
KERNEL_EXTRA_SRCS := $(LOADER_EXTRA_SRCS)
|
KERNEL_EXTRA_SRCS := $(LOADER_EXTRA_SRCS)
|
||||||
|
|
||||||
LOADER_VM_OBJS := $(patsubst src/%.c,$(LOADER_OBJ_DIR)/vmcore/%.o,$(VM_CORE_SRCS))
|
LOADER_VM_OBJS := $(patsubst v3/src/%.c,$(LOADER_OBJ_DIR)/vmcore/%.o,$(VM_CORE_SRCS))
|
||||||
KERNEL_VM_OBJS := $(patsubst src/%.c,$(KERNEL_OBJ_DIR)/vmcore/%.o,$(VM_CORE_SRCS))
|
KERNEL_VM_OBJS := $(patsubst v3/src/%.c,$(KERNEL_OBJ_DIR)/vmcore/%.o,$(VM_CORE_SRCS))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LOADER_SRCS := $(LOADER_SRCS_BASE) $(LOADER_EXTRA_SRCS)
|
LOADER_SRCS := $(LOADER_SRCS_BASE) $(LOADER_EXTRA_SRCS)
|
||||||
@@ -597,12 +621,13 @@ KERNEL_OBJS := \
|
|||||||
.PHONY: qemu qemu-esp qemu-gdb
|
.PHONY: qemu qemu-esp qemu-gdb
|
||||||
.PHONY: thumbdrive iso-usb
|
.PHONY: thumbdrive iso-usb
|
||||||
.PHONY: info help
|
.PHONY: info help
|
||||||
|
.PHONY: FORCE
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# MAIN TARGETS
|
# MAIN TARGETS
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
all: include/version.h $(CAPSULE_GENERATED_OBJ) $(CAPSULE_GENERATED_KOBJ) $(LOADER_EFI) $(KERNEL_ELF)
|
all: v3/include/version.h $(CAPSULE_GENERATED_OBJ) $(CAPSULE_GENERATED_KOBJ) $(LOADER_EFI) $(KERNEL_ELF)
|
||||||
@echo "StarKernel built successfully for $(ARCH)"
|
@echo "StarKernel built successfully for $(ARCH)"
|
||||||
@echo " Loader: $(LOADER_EFI)"
|
@echo " Loader: $(LOADER_EFI)"
|
||||||
@echo " Kernel: $(KERNEL_ELF)"
|
@echo " Kernel: $(KERNEL_ELF)"
|
||||||
@@ -610,13 +635,13 @@ all: include/version.h $(CAPSULE_GENERATED_OBJ) $(CAPSULE_GENERATED_KOBJ) $(LOAD
|
|||||||
kernel: all
|
kernel: all
|
||||||
|
|
||||||
kernel-all:
|
kernel-all:
|
||||||
@$(MAKE) -f Makefile.starkernel ARCH=amd64 TARGET=$(TARGET)
|
@$(MAKE) -f kernel/Makefile ARCH=amd64 TARGET=$(TARGET)
|
||||||
@$(MAKE) -f Makefile.starkernel ARCH=aarch64 TARGET=$(TARGET)
|
@$(MAKE) -f kernel/Makefile ARCH=aarch64 TARGET=$(TARGET)
|
||||||
@$(MAKE) -f Makefile.starkernel ARCH=riscv64 TARGET=$(TARGET)
|
@$(MAKE) -f kernel/Makefile ARCH=riscv64 TARGET=$(TARGET)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf build/$(ARCH)/$(TARGET)
|
rm -rf build/$(ARCH)/$(TARGET)
|
||||||
rm -f include/version.h
|
rm -f v3/include/version.h
|
||||||
|
|
||||||
clean-kernel:
|
clean-kernel:
|
||||||
@echo "Cleaning all StarKernel build artifacts..."
|
@echo "Cleaning all StarKernel build artifacts..."
|
||||||
@@ -627,8 +652,16 @@ clean-kernel:
|
|||||||
# BUILD RULES
|
# BUILD RULES
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
include/version.h:
|
FORCE:
|
||||||
@mkdir -p include
|
|
||||||
|
# FORCE prerequisite (matches the hosted Makefile's own pattern, Makefile:613/626):
|
||||||
|
# this file is also written by the hosted Makefile with different, incompatible
|
||||||
|
# content (no LITHOS_VERSION/LITHOS_VERSION_STR) -- without FORCE, a bare build
|
||||||
|
# here after a hosted `make` run silently reuses that stale/wrong-flavored file
|
||||||
|
# and fails with "LITHOS_VERSION_STR undeclared" deep in kernel_main.c, instead
|
||||||
|
# of regenerating its own correct version.
|
||||||
|
v3/include/version.h: FORCE
|
||||||
|
@mkdir -p v3/include
|
||||||
@BUILD_TS=$$(date -Iseconds 2>/dev/null || echo "unknown"); \
|
@BUILD_TS=$$(date -Iseconds 2>/dev/null || echo "unknown"); \
|
||||||
printf '#ifndef STARFORTH_VERSION_H\n#define STARFORTH_VERSION_H\n\n' > $@; \
|
printf '#ifndef STARFORTH_VERSION_H\n#define STARFORTH_VERSION_H\n\n' > $@; \
|
||||||
printf '#define STARFORTH_VERSION "%s"\n' "$(VERSION)" >> $@; \
|
printf '#define STARFORTH_VERSION "%s"\n' "$(VERSION)" >> $@; \
|
||||||
@@ -644,7 +677,7 @@ include/version.h:
|
|||||||
$(MKCAPSULE_BIN): $(MKCAPSULE_SRC)
|
$(MKCAPSULE_BIN): $(MKCAPSULE_SRC)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
@echo "HOSTCC $(MKCAPSULE_SRC)"
|
@echo "HOSTCC $(MKCAPSULE_SRC)"
|
||||||
@cc -std=c99 -Wall -Wextra -O2 -Iinclude -Itools -o $@ $(MKCAPSULE_SRC)
|
@cc -std=c99 -Wall -Wextra -O2 -Iv3/include -Ikernel/include -Itools -o $@ $(MKCAPSULE_SRC)
|
||||||
|
|
||||||
# Generate capsule_generated.c from capsules/
|
# Generate capsule_generated.c from capsules/
|
||||||
CAPSULE_SRCS := $(shell find $(CAPSULES_DIR) -type f ! -name '.*' 2>/dev/null)
|
CAPSULE_SRCS := $(shell find $(CAPSULES_DIR) -type f ! -name '.*' 2>/dev/null)
|
||||||
@@ -686,12 +719,12 @@ $(LOADER_OBJ_DIR)/%.o: $(KERNEL_SRC)/%.c | $(LOADER_OBJ_DIR)
|
|||||||
@$(LOADER_CC) $(LOADER_CFLAGS) -c $< -o $@
|
@$(LOADER_CC) $(LOADER_CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
# Compile loader VM core sources
|
# Compile loader VM core sources
|
||||||
$(LOADER_OBJ_DIR)/vmcore/%.o: src/%.c include/version.h | $(LOADER_OBJ_DIR)
|
$(LOADER_OBJ_DIR)/vmcore/%.o: v3/src/%.c v3/include/version.h | $(LOADER_OBJ_DIR)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
@echo "CC (loader) $<"
|
@echo "CC (loader) $<"
|
||||||
ifeq ($(ARCH),aarch64)
|
ifeq ($(ARCH),aarch64)
|
||||||
@$(LOADER_CC) $(LOADER_CFLAGS) \
|
@$(LOADER_CC) $(LOADER_CFLAGS) \
|
||||||
-Iinclude -I. -Isrc/test_runner/include \
|
-Iv3/include -Ikernel/include -I. -Iv3/src/test_runner/include \
|
||||||
-Wno-error=unused-parameter -Wno-error=shift-negative-value \
|
-Wno-error=unused-parameter -Wno-error=shift-negative-value \
|
||||||
-Wno-error=sign-compare -Wno-error=missing-field-initializers \
|
-Wno-error=sign-compare -Wno-error=missing-field-initializers \
|
||||||
-Wno-unused-function \
|
-Wno-unused-function \
|
||||||
@@ -713,10 +746,10 @@ $(KERNEL_OBJ_DIR)/%.o: $(KERNEL_SRC)/%.c | $(KERNEL_OBJ_DIR)
|
|||||||
@$(CC) $(KERNEL_CFLAGS) -c $< -o $@
|
@$(CC) $(KERNEL_CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
# Compile kernel VM core sources
|
# Compile kernel VM core sources
|
||||||
$(KERNEL_OBJ_DIR)/vmcore/%.o: src/%.c include/version.h | $(KERNEL_OBJ_DIR)
|
$(KERNEL_OBJ_DIR)/vmcore/%.o: v3/src/%.c v3/include/version.h | $(KERNEL_OBJ_DIR)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
@echo "CC (kernel) $<"
|
@echo "CC (kernel) $<"
|
||||||
@$(CC) $(VMCORE_CFLAGS_COMMON) $(filter-out -I$(KERNEL_INC),$(KERNEL_CFLAGS)) -Isrc/starkernel/vm -c $< -o $@
|
@$(CC) $(VMCORE_CFLAGS_COMMON) $(filter-out -I$(KERNEL_INC),$(KERNEL_CFLAGS)) -Ikernel/src/vm -c $< -o $@
|
||||||
|
|
||||||
# Compile kernel assembly sources
|
# Compile kernel assembly sources
|
||||||
$(KERNEL_OBJ_DIR)/%.o: $(KERNEL_SRC)/%.S | $(KERNEL_OBJ_DIR)
|
$(KERNEL_OBJ_DIR)/%.o: $(KERNEL_SRC)/%.S | $(KERNEL_OBJ_DIR)
|
||||||
@@ -793,7 +826,7 @@ OVMF_CODE := /usr/share/OVMF/OVMF_CODE_4M.fd
|
|||||||
OVMF_VARS_RO := /usr/share/OVMF/OVMF_VARS_4M.fd
|
OVMF_VARS_RO := /usr/share/OVMF/OVMF_VARS_4M.fd
|
||||||
|
|
||||||
# QEMU_DISPLAY — window backend for the framebuffer console (gtk/sdl/none/...).
|
# QEMU_DISPLAY — window backend for the framebuffer console (gtk/sdl/none/...).
|
||||||
# Usage: make -f Makefile.starkernel qemu QEMU_DISPLAY=none # headless/CI
|
# Usage: make -f kernel/Makefile qemu QEMU_DISPLAY=none # headless/CI
|
||||||
QEMU_DISPLAY ?= gtk
|
QEMU_DISPLAY ?= gtk
|
||||||
|
|
||||||
# QEMU_EXTRA — extra raw qemu-system-* args appended verbatim to every arch's
|
# QEMU_EXTRA — extra raw qemu-system-* args appended verbatim to every arch's
|
||||||
@@ -1063,38 +1096,98 @@ else ifeq ($(ARCH),riscv64)
|
|||||||
# fallback boot path EFI/BOOT/BOOT<ARCH>.EFI. Storage on real hardware is the
|
# fallback boot path EFI/BOOT/BOOT<ARCH>.EFI. Storage on real hardware is the
|
||||||
# USB BOT/xHCI path (already live), not virtio.
|
# USB BOT/xHCI path (already live), not virtio.
|
||||||
thumbdrive: all
|
thumbdrive: all
|
||||||
@mkdir -p $(BUILD_DIR)
|
@printf 'FS0:\\EFI\\BOOT\\%s\r\n' "$(EFI_BOOTNAME)" > $(BUILD_DIR)/thumbdrive-startup.nsh
|
||||||
@if ! which sgdisk >/dev/null 2>&1; then echo "Error: sgdisk not found. Install gdisk (apt-get install gdisk)."; exit 1; fi
|
@bash scripts/mkdiskimage.sh --out $(BUILD_DIR)/starkernel-thumbdrive.img --table gpt \
|
||||||
@if ! which mkfs.fat >/dev/null 2>&1; then echo "Error: mkfs.fat not found. Install dosfstools (apt-get install dosfstools)."; exit 1; fi
|
--size-mib 128 --label STARKERNEL \
|
||||||
@case "$(ARCH)" in \
|
$(LOADER_EFI):/EFI/BOOT/$(EFI_BOOTNAME) \
|
||||||
amd64) BOOTNAME="BOOTX64.EFI" ;; \
|
$(BUILD_DIR)/thumbdrive-startup.nsh:/startup.nsh
|
||||||
aarch64) BOOTNAME="BOOTAA64.EFI" ;; \
|
@rm -f $(BUILD_DIR)/thumbdrive-startup.nsh
|
||||||
riscv64) BOOTNAME="BOOTRISCV64.EFI" ;; \
|
@echo "Write to a USB stick with: dd if=$(BUILD_DIR)/starkernel-thumbdrive.img of=/dev/sdX bs=4M status=progress"
|
||||||
*) echo "Error: no thumbdrive EFI boot name for ARCH=$(ARCH)"; exit 1 ;; \
|
|
||||||
esac; \
|
|
||||||
# Geometry: 128 MiB disk (262144 sectors), GPT partition 1 spans sectors \
|
|
||||||
# 2048..262110 (the GPT last-usable sector for this disk size), i.e. \
|
|
||||||
# 260063 sectors. The FAT32 ESP image MUST match the partition size \
|
|
||||||
# exactly; a larger ESP overruns the disk (GPT grows corrupt) or \
|
|
||||||
# spills past the partition end, both of which made earlier builds \
|
|
||||||
# unbootable on hardware. \
|
|
||||||
DISK=$(BUILD_DIR)/starkernel-thumbdrive.img; \
|
|
||||||
ESP_SECTORS=260063; \
|
|
||||||
rm -f $$DISK $(BUILD_DIR)/thumbdrive-esp.img; \
|
|
||||||
dd if=/dev/zero of=$(BUILD_DIR)/thumbdrive-esp.img bs=512 count=$$ESP_SECTORS 2>/dev/null; \
|
|
||||||
mkfs.fat -F 32 -n "STARKERNEL" $(BUILD_DIR)/thumbdrive-esp.img >/dev/null 2>&1; \
|
|
||||||
mmd -i $(BUILD_DIR)/thumbdrive-esp.img ::/EFI; \
|
|
||||||
mmd -i $(BUILD_DIR)/thumbdrive-esp.img ::/EFI/BOOT; \
|
|
||||||
mcopy -i $(BUILD_DIR)/thumbdrive-esp.img $(LOADER_EFI) ::/EFI/BOOT/$$BOOTNAME; \
|
|
||||||
printf 'FS0:\\EFI\\BOOT\\%s\r\n' "$$BOOTNAME" > $(BUILD_DIR)/thumbdrive-startup.nsh; \
|
|
||||||
mcopy -i $(BUILD_DIR)/thumbdrive-esp.img $(BUILD_DIR)/thumbdrive-startup.nsh ::/startup.nsh; \
|
|
||||||
dd if=/dev/zero of=$$DISK bs=1M count=128 2>/dev/null; \
|
|
||||||
sgdisk -n 1:2048:0 -t 1:ef00 -c 1:"EFI System" $$DISK >/dev/null 2>&1; \
|
|
||||||
dd if=$(BUILD_DIR)/thumbdrive-esp.img of=$$DISK bs=512 seek=2048 conv=notrunc 2>/dev/null; \
|
|
||||||
rm -f $(BUILD_DIR)/thumbdrive-esp.img $(BUILD_DIR)/thumbdrive-startup.nsh; \
|
|
||||||
echo "Thumbdrive image: $$DISK"; \
|
|
||||||
echo "Write to a USB stick with: dd if=$$DISK of=/dev/sdX bs=4M status=progress"
|
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# BOOT IMAGES (one per board)
|
||||||
|
# ==============================================================================
|
||||||
|
# boot_image -- the single file written to a board's boot medium. Driven by
|
||||||
|
# BOARD (boards/<board>/board.mk); normally invoked from the repo root as
|
||||||
|
# make boot_image TARGET=SER5|RASPI|MILKV|ZYNQ7020
|
||||||
|
# Output: build/boards/<board>/$(BOARD_IMAGE).
|
||||||
|
#
|
||||||
|
# BOARD_BOOT selects the recipe:
|
||||||
|
# uefi-esp -- GPT disk, one FAT32 EFI System Partition holding the
|
||||||
|
# monolithic loader as EFI/BOOT/BOOT<ARCH>.EFI (+ startup.nsh
|
||||||
|
# for the UEFI shell, + starforth.cfg when KERNEL_ARGS is set).
|
||||||
|
# Real UEFI firmware (SER5) and U-Boot's bootefi (Milk-V) both
|
||||||
|
# boot it through the removable-media fallback path.
|
||||||
|
# rpi-native -- MBR disk, one FAT32 (LBA) partition holding config.txt,
|
||||||
|
# kernel_2712.img (kernel relinked at 0x80000 and flattened)
|
||||||
|
# and the stock BCM2712 DTB (+ cmdline.txt, which firmware
|
||||||
|
# copies into /chosen/bootargs, when KERNEL_ARGS is set).
|
||||||
|
ifeq ($(ARCH),amd64)
|
||||||
|
EFI_BOOTNAME := BOOTX64.EFI
|
||||||
|
else ifeq ($(ARCH),aarch64)
|
||||||
|
EFI_BOOTNAME := BOOTAA64.EFI
|
||||||
|
else ifeq ($(ARCH),riscv64)
|
||||||
|
EFI_BOOTNAME := BOOTRISCV64.EFI
|
||||||
|
endif
|
||||||
|
|
||||||
|
RPI5_NATIVE_LINKER_SCRIPT := kernel/linker/starkernel-native-rpi5.ld
|
||||||
|
RPI5_NATIVE_ELF := $(BUILD_DIR)/starkernel_rpi5.elf
|
||||||
|
RPI5_NATIVE_IMG := $(BUILD_DIR)/kernel_2712.img
|
||||||
|
|
||||||
|
$(RPI5_NATIVE_ELF): $(KERNEL_OBJS) $(RPI5_NATIVE_LINKER_SCRIPT) | $(BUILD_DIR)
|
||||||
|
@echo "LD (rpi5 native) $@"
|
||||||
|
@$(LD) -T $(RPI5_NATIVE_LINKER_SCRIPT) -nostdlib --build-id=none \
|
||||||
|
-e rpi5_native_start $(KERNEL_OBJS) -o $@
|
||||||
|
|
||||||
|
$(RPI5_NATIVE_IMG): $(RPI5_NATIVE_ELF)
|
||||||
|
@echo "OBJCOPY (raw) $@"
|
||||||
|
@$(OBJCOPY) -O binary $< $@
|
||||||
|
|
||||||
|
.PHONY: boot_image
|
||||||
|
ifeq ($(strip $(BOARD)),)
|
||||||
|
boot_image:
|
||||||
|
@echo "boot_image needs a board. From the repo root:"
|
||||||
|
@echo " make boot_image TARGET=SER5|RASPI|MILKV|ZYNQ7020"
|
||||||
|
@exit 1
|
||||||
|
else ifeq ($(BOARD_BOOT),uefi-esp)
|
||||||
|
boot_image: all
|
||||||
|
@mkdir -p $(BOARD_OUT)
|
||||||
|
@printf 'FS0:\\EFI\\BOOT\\%s\r\n' "$(EFI_BOOTNAME)" > $(BOARD_OUT)/startup.nsh
|
||||||
|
@rm -f $(BOARD_OUT)/starforth.cfg
|
||||||
|
$(if $(KERNEL_ARGS),@printf '%s\n' '$(KERNEL_ARGS)' > $(BOARD_OUT)/starforth.cfg)
|
||||||
|
@bash scripts/mkdiskimage.sh --out $(BOARD_OUT)/$(BOARD_IMAGE) --table gpt \
|
||||||
|
--size-mib $(BOOT_IMAGE_SIZE_MIB) --label LITHOS \
|
||||||
|
$(LOADER_EFI):/EFI/BOOT/$(EFI_BOOTNAME) \
|
||||||
|
$(BOARD_OUT)/startup.nsh:/startup.nsh \
|
||||||
|
$(if $(KERNEL_ARGS),$(BOARD_OUT)/starforth.cfg:/starforth.cfg)
|
||||||
|
@echo "$(BOARD_DESC)"
|
||||||
|
@echo "Write with: dd if=$(BOARD_OUT)/$(BOARD_IMAGE) of=/dev/sdX bs=4M conv=fsync status=progress"
|
||||||
|
else ifeq ($(BOARD_BOOT),rpi-native)
|
||||||
|
$(RPI5_DTB):
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
@echo " FETCH $(RPI5_DTB_URL)"
|
||||||
|
@curl -fsSL -o $@.tmp $(RPI5_DTB_URL) || { rm -f $@.tmp; \
|
||||||
|
echo "Error: could not fetch the Pi 5 DTB. Copy bcm2712-rpi-5-b.dtb from a Raspberry Pi"; \
|
||||||
|
echo "firmware release and pass RPI5_DTB=/path/to/bcm2712-rpi-5-b.dtb"; exit 1; }
|
||||||
|
@mv $@.tmp $@
|
||||||
|
|
||||||
|
boot_image: v3/include/version.h $(RPI5_NATIVE_IMG) $(RPI5_DTB)
|
||||||
|
@mkdir -p $(BOARD_OUT)
|
||||||
|
@rm -f $(BOARD_OUT)/cmdline.txt
|
||||||
|
$(if $(KERNEL_ARGS),@printf '%s\n' '$(KERNEL_ARGS)' > $(BOARD_OUT)/cmdline.txt)
|
||||||
|
@bash scripts/mkdiskimage.sh --out $(BOARD_OUT)/$(BOARD_IMAGE) --table mbr \
|
||||||
|
--size-mib $(BOOT_IMAGE_SIZE_MIB) --label LITHOS \
|
||||||
|
boards/$(BOARD)/config.txt:/config.txt \
|
||||||
|
$(RPI5_NATIVE_IMG):/kernel_2712.img \
|
||||||
|
$(RPI5_DTB):/bcm2712-rpi-5-b.dtb \
|
||||||
|
$(if $(KERNEL_ARGS),$(BOARD_OUT)/cmdline.txt:/cmdline.txt)
|
||||||
|
@echo "$(BOARD_DESC)"
|
||||||
|
@echo "Write with: dd if=$(BOARD_OUT)/$(BOARD_IMAGE) of=/dev/mmcblkX bs=4M conv=fsync status=progress"
|
||||||
|
else
|
||||||
|
boot_image:
|
||||||
|
@echo "Error: BOARD=$(BOARD) has BOARD_BOOT='$(BOARD_BOOT)', which has no boot_image recipe"
|
||||||
|
@exit 1
|
||||||
|
endif
|
||||||
|
|
||||||
# iso-usb — build a PURE UEFI isohybrid ISO for the Iso Image Writer workflow
|
# iso-usb — build a PURE UEFI isohybrid ISO for the Iso Image Writer workflow
|
||||||
# (GNOME Disks "Restore Disk Image...", or dd). Writes a single .iso to a USB
|
# (GNOME Disks "Restore Disk Image...", or dd). Writes a single .iso to a USB
|
||||||
@@ -1141,7 +1234,7 @@ iso-usb: all
|
|||||||
# yourself with Ctrl-A X or by closing the window).
|
# yourself with Ctrl-A X or by closing the window).
|
||||||
# amd64/aarch64: serial → stdio (bidirectional, you can type into the REPL);
|
# amd64/aarch64: serial → stdio (bidirectional, you can type into the REPL);
|
||||||
# riscv64: uses proper GPT (esp dir not supported)
|
# riscv64: uses proper GPT (esp dir not supported)
|
||||||
qemu-esp: include/version.h $(LOADER_EFI) $(KERNEL_ELF)
|
qemu-esp: v3/include/version.h $(LOADER_EFI) $(KERNEL_ELF)
|
||||||
ifeq ($(ARCH),amd64)
|
ifeq ($(ARCH),amd64)
|
||||||
@rm -rf $(BUILD_DIR)/esp
|
@rm -rf $(BUILD_DIR)/esp
|
||||||
@mkdir -p $(BUILD_DIR)/esp/EFI/BOOT
|
@mkdir -p $(BUILD_DIR)/esp/EFI/BOOT
|
||||||
@@ -1197,7 +1290,7 @@ else ifeq ($(ARCH),riscv64)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
# qemu-gdb — launch QEMU with GDB stub on port 1234
|
# qemu-gdb — launch QEMU with GDB stub on port 1234
|
||||||
qemu-gdb: include/version.h $(LOADER_EFI) $(KERNEL_ELF)
|
qemu-gdb: v3/include/version.h $(LOADER_EFI) $(KERNEL_ELF)
|
||||||
ifeq ($(ARCH),amd64)
|
ifeq ($(ARCH),amd64)
|
||||||
@mkdir -p $(BUILD_DIR)/esp/EFI/BOOT
|
@mkdir -p $(BUILD_DIR)/esp/EFI/BOOT
|
||||||
@cp $(LOADER_EFI) $(BUILD_DIR)/esp/EFI/BOOT/BOOTX64.EFI
|
@cp $(LOADER_EFI) $(BUILD_DIR)/esp/EFI/BOOT/BOOTX64.EFI
|
||||||
@@ -1250,7 +1343,7 @@ help:
|
|||||||
@echo "StarKernel Build System"
|
@echo "StarKernel Build System"
|
||||||
@echo "======================="
|
@echo "======================="
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Usage: make -f Makefile.starkernel [ARCH=<arch>] [TARGET=<target>] <goal>"
|
@echo "Usage: make -f kernel/Makefile [ARCH=<arch>] [TARGET=<target>] <goal>"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Architectures (aliases accepted):"
|
@echo "Architectures (aliases accepted):"
|
||||||
@echo " ARCH=amd64 / x86_64 — x86-64 (default on x86 host)"
|
@echo " ARCH=amd64 / x86_64 — x86-64 (default on x86 host)"
|
||||||
@@ -1280,8 +1373,8 @@ help:
|
|||||||
@echo " help — show this message"
|
@echo " help — show this message"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Examples:"
|
@echo "Examples:"
|
||||||
@echo " make -f Makefile.starkernel ARCH=amd64 qemu"
|
@echo " make -f kernel/Makefile ARCH=amd64 qemu"
|
||||||
@echo " make -f Makefile.starkernel ARCH=aarch64 clean qemu"
|
@echo " make -f kernel/Makefile ARCH=aarch64 clean qemu"
|
||||||
@echo " make -f Makefile.starkernel ARCH=riscv64 clean qemu"
|
@echo " make -f kernel/Makefile ARCH=riscv64 clean qemu"
|
||||||
@echo " make -f Makefile.starkernel ARCH=x86_64 qemu # alias works"
|
@echo " make -f kernel/Makefile ARCH=x86_64 qemu # alias works"
|
||||||
@echo " make -f Makefile.starkernel kernel-all"
|
@echo " make -f kernel/Makefile kernel-all"
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user