Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure

Source tree reorganization:
- Move StarForth v3 engine to v3/ (src/, include/, Makefile)
- Move kernel to kernel/ (src/, include/, linker/, Makefile)
- Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md)
- Move FABRIC-0..4.md to docs/fabric/
- Move ONTOLOGY.md and ROADMAP.md to docs/

Board infrastructure:
- Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/
- Each board has board.mk (ISA, CPU flags, boot recipe) and README.md
- Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET
- make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board
- ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet)
- scripts/mkdiskimage.sh builds disk images for all boards

Docs pipeline:
- docs/book/ with LaTeX master (main.tex) and Makefile
- pandoc converts Markdown to LaTeX at build time
- Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks)
- make docs builds single PDF (754 pages, 0 missing characters)
- make docs TARGET=<board> adds board appendix
- build/docs/<book|board>/meta.tex stamps git commit into PDF

Bug fixes:
- 42 include paths that only worked by accident now use correct relative paths
- clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build)
- Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved
- Doxyfile, .clang-tidy, README.md, Kconfig paths updated

Verified:
- Hosted v3 build passes 1012 tests, 0 failures
- SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE)
- Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes
- make clean TARGET=<board> removes only that board and its ISA objects
- make all builds all boards, hosted v3, and docs in one run

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
rajames
2026-10-01 15:40:09 -04:00
co-authored by Junie
parent 3c709c115b
commit a8b70e88d3
630 changed files with 2646 additions and 2036 deletions
+54
View File
@@ -0,0 +1,54 @@
# include/
Public headers for the hosted StarForth VM (`src/`). Kernel-only headers
live under `include/starkernel/`.
**Core VM**
- `vm.h` — the `VM` struct and all core types (`DictEntry`, `DictPhysics`,
stacks, dictionary state); the central header most other files include.
- `vm_api.h` — external VM API surface.
- `vm_host.h`, `vm_debug.h`, `startup.h`, `version.h` — host integration,
debug utilities, startup sequencing, version string.
- `cli.h`, `repl.h`, `io.h`, `log.h` — CLI parsing, REPL loop, I/O, logging.
- `word_registry.h` — word registration system shared by every
`src/word_source/*.c` file.
- `compudynamics.h` — the generic compudynamics module: tuning-word/config
lookups (`cd_tuning_word()`, `cd_tuning_vm()`) consumed by `ssm_jacquard.c`.
**Memory / blocks**
- `memory_management.h`, `dictionary_management.h` — dictionary allocator
and search.
- `block_subsystem.h`, `blkcfg.h`, `blkio.h`, `blkio_factory.h` — logical→
physical block mapper and pluggable block I/O backends (file/RAM).
- `platform_alloc.h`, `platform_lock.h`, `platform_time.h` — platform
abstraction shims (hosted vs. kernel allocation/locking/timing).
**Physics-driven adaptive runtime (7 feedback loops)**
- `physics_runtime.h` — main physics coordinator.
- `physics_hotwords_cache.h` — Loop #1, execution-heat hot-words cache.
- `physics_metadata.h` — per-word `DictPhysics` metadata tracking.
- `physics_pipelining_metrics.h` — Loop #4, word-transition prediction.
- `physics_execution_hooks.h` — execution instrumentation hook points.
- `rolling_window_of_truth.h`, `rolling_window_knobs.h` — Loop #2 circular
execution-history buffer and its tuning knobs.
- `inference_engine.h` — Loops #5/#6, window-width and decay-slope
statistical inference.
- `dictionary_heat_optimization.h` — Loop #1 heat counters.
- `ssm_jacquard.h` — L8 Jacquard steady-state mode selector.
- `doe_metrics.h` — Design of Experiments (2^7 factorial) metrics.
- `profiler.h` — performance profiling hooks.
**Arithmetic / codegen**
- `q48_16.h` — Q48.16 deterministic fixed-point arithmetic (used instead of
IEEE-754 float specifically to keep cross-architecture behavior
bit-identical).
- `math_portable.h` — portable math helpers.
- `arch_detect.h`, `starforth_config.h` — architecture detection and the
build-flag fallback-default layer (used when a `.c` file is compiled by
hand without `make`).
- `vm_asm_opt.h`, `vm_asm_opt_arm64.h`, `vm_asm_opt_riscv64.h`,
`vm_inner_interp_asm.h`, `vm_inner_interp_arm64.h`,
`vm_inner_interp_riscv64.h` — per-architecture assembler-optimized inner
interpreter (`USE_ASM_OPT=1`).
See `include/starkernel/README.md` for the bare-metal kernel headers.
+512
View File
@@ -0,0 +1,512 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @file arch_detect.h
* @brief Architecture Detection and Unified API for StarForth
* @details Automatically selects x86_64 or ARM64 optimizations and provides
* a unified interface for architecture-specific optimizations.
*
* This work is released under license terms in LICENSE file.
* No warranty. Use at your own risk.
*/
#ifndef ARCH_DETECT_H
#define ARCH_DETECT_H
/**
* @defgroup arch_detect Architecture Detection
* @{
*
* @brief Automatic detection of target architecture and inclusion of optimization headers
*
* This module automatically detects the target architecture at compile time and includes
* the appropriate assembly optimization headers.
*
* Supported architectures:
* - x86_64 (AMD64, Intel 64)
* - ARM64 (AArch64, ARMv8-A)
*
* Unsupported architectures fall back to pure C implementations.
* @}
*/
/* Detect architecture at compile time */
#if defined(__x86_64__) || defined(_M_X64) || defined(__amd64__)
#define ARCH_X86_64 1
#define ARCH_ARM64 0
#define ARCH_RISCV64 0
#define ARCH_NAME "x86_64"
#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm64__)
#define ARCH_X86_64 0
#define ARCH_ARM64 1
#define ARCH_RISCV64 0
#define ARCH_NAME "ARM64"
#elif defined(__riscv) && (__riscv_xlen == 64)
#define ARCH_X86_64 0
#define ARCH_ARM64 0
#define ARCH_RISCV64 1
#define ARCH_NAME "RISC-V 64"
#else
#define ARCH_X86_64 0
#define ARCH_ARM64 0
#define ARCH_RISCV64 0
#define ARCH_NAME "Unknown"
#warning "Unknown architecture, assembly optimizations disabled"
#endif
/* Include architecture-specific headers */
#if ARCH_X86_64 && USE_ASM_OPT
#include "vm_asm_opt.h"
#if USE_DIRECT_THREADING
#include "vm_inner_interp_asm.h"
#endif
#elif ARCH_ARM64 && USE_ASM_OPT
#include "vm_asm_opt_arm64.h"
#if USE_DIRECT_THREADING
#include "vm_inner_interp_arm64.h"
#endif
#elif ARCH_RISCV64 && USE_ASM_OPT
#include "vm_asm_opt_riscv64.h"
#if USE_DIRECT_THREADING
#include "vm_inner_interp_riscv64.h"
#endif
#endif
/**
* @defgroup unified_api Unified API Wrappers
* @{
*
* @brief Consistent API macros across all architectures
*
* These macros provide a consistent API across all architectures by automatically
* dispatching to the appropriate optimized or fallback implementation based on
* the target architecture and available optimizations.
* @}
*/
/* ========== Stack Operations ========== */
#if USE_ASM_OPT && (ARCH_X86_64 || ARCH_ARM64 || ARCH_RISCV64)
/* Use optimized assembly implementations */
#define vm_push_opt(vm, val) vm_push_asm(vm, val)
#define vm_pop_opt(vm) vm_pop_asm(vm)
#define vm_rpush_opt(vm, val) vm_rpush_asm(vm, val)
#define vm_rpop_opt(vm) vm_rpop_asm(vm)
#else
/* Fall back to standard C implementations */
#define vm_push_opt(vm, val) vm_push(vm, val)
#define vm_pop_opt(vm) vm_pop(vm)
#define vm_rpush_opt(vm, val) vm_rpush(vm, val)
#define vm_rpop_opt(vm) vm_rpop(vm)
#endif
/* ========== Arithmetic Operations ========== */
#if USE_ASM_OPT && (ARCH_X86_64 || ARCH_ARM64 || ARCH_RISCV64)
#define vm_add_overflow_opt(a, b, res) vm_add_check_overflow(a, b, res)
#define vm_sub_overflow_opt(a, b, res) vm_sub_check_overflow(a, b, res)
#define vm_mul_double_opt(a, b, hi, lo) vm_mul_double(a, b, hi, lo)
#define vm_divmod_opt(dividend, divisor, quot, rem) vm_divmod(dividend, divisor, quot, rem)
#else
/* Fallback implementations */
static inline int vm_add_overflow_opt(cell_t a, cell_t b, cell_t *res) {
*res = a + b;
/* Simple overflow check (not perfect) */
return ((a > 0 && b > 0 && *res < 0) || (a < 0 && b < 0 && *res > 0));
}
static inline int vm_sub_overflow_opt(cell_t a, cell_t b, cell_t *res) {
*res = a - b;
return ((a > 0 && b < 0 && *res < 0) || (a < 0 && b > 0 && *res > 0));
}
static inline void vm_mul_double_opt(cell_t a, cell_t b, cell_t *hi, cell_t *lo) {
/* Simplified - assumes 64-bit cell_t */
*lo = a * b;
*hi = 0; /* Would need 128-bit arithmetic for proper high word */
}
static inline void vm_divmod_opt(cell_t dividend, cell_t divisor, cell_t *quot, cell_t *rem) {
*quot = dividend / divisor;
*rem = dividend % divisor;
}
#endif
/* ========== String Operations ========== */
#if USE_ASM_OPT && (ARCH_X86_64 || ARCH_ARM64 || ARCH_RISCV64)
#define vm_strcmp_opt(s1, s2, len) vm_strcmp_asm(s1, s2, len)
#define vm_memcpy_opt(dst, src, len) vm_memcpy_asm(dst, src, len)
#define vm_memzero_opt(dst, len) vm_memzero_asm(dst, len)
#else
#include <string.h>
static inline int vm_strcmp_opt(const char *s1, const char *s2, size_t len) {
return memcmp(s1, s2, len) != 0;
}
static inline void vm_memcpy_opt(void *dst, const void *src, size_t len) {
memcpy(dst, src, len);
}
static inline void vm_memzero_opt(void *dst, size_t len) {
memset(dst, 0, len);
}
#endif
/* ========== Min/Max Operations ========== */
#if USE_ASM_OPT && (ARCH_X86_64 || ARCH_ARM64 || ARCH_RISCV64)
#define vm_min_opt(a, b) vm_min_asm(a, b)
#define vm_max_opt(a, b) vm_max_asm(a, b)
#else
static inline cell_t vm_min_opt(cell_t a, cell_t b) {
return (a < b) ? a : b;
}
static inline cell_t vm_max_opt(cell_t a, cell_t b) {
return (a > b) ? a : b;
}
#endif
/* ========== Absolute Value ========== */
#if USE_ASM_OPT && (ARCH_ARM64 || ARCH_RISCV64)
#define vm_abs_opt(a) vm_abs_asm(a)
#else
static inline cell_t vm_abs_opt(cell_t a) {
return (a < 0) ? -a : a;
}
#endif
/* ========== Bit Operations ========== */
#if USE_ASM_OPT && (ARCH_ARM64 || ARCH_RISCV64)
#define vm_clz_opt(x) vm_clz(x)
#define vm_ctz_opt(x) vm_ctz(x)
#define vm_popcnt_opt(x) vm_popcnt(x)
#elif USE_ASM_OPT && ARCH_X86_64
static inline int vm_clz_opt(cell_t x) {
return __builtin_clzll((unsigned long long) x);
}
static inline int vm_ctz_opt(cell_t x) {
return __builtin_ctzll((unsigned long long) x);
}
static inline int vm_popcnt_opt(cell_t x) {
return __builtin_popcountll((unsigned long long) x);
}
#else
/* Software fallback */
static inline int vm_clz_opt(cell_t x) {
if (x == 0) return 64;
int n = 0;
if ((x & 0xFFFFFFFF00000000ULL) == 0) {
n += 32;
x <<= 32;
}
if ((x & 0xFFFF000000000000ULL) == 0) {
n += 16;
x <<= 16;
}
if ((x & 0xFF00000000000000ULL) == 0) {
n += 8;
x <<= 8;
}
if ((x & 0xF000000000000000ULL) == 0) {
n += 4;
x <<= 4;
}
if ((x & 0xC000000000000000ULL) == 0) {
n += 2;
x <<= 2;
}
if ((x & 0x8000000000000000ULL) == 0) { n += 1; }
return n;
}
static inline int vm_ctz_opt(cell_t x) {
if (x == 0) return 64;
int n = 0;
if ((x & 0x00000000FFFFFFFFULL) == 0) {
n += 32;
x >>= 32;
}
if ((x & 0x000000000000FFFFULL) == 0) {
n += 16;
x >>= 16;
}
if ((x & 0x00000000000000FFULL) == 0) {
n += 8;
x >>= 8;
}
if ((x & 0x000000000000000FULL) == 0) {
n += 4;
x >>= 4;
}
if ((x & 0x0000000000000003ULL) == 0) {
n += 2;
x >>= 2;
}
if ((x & 0x0000000000000001ULL) == 0) { n += 1; }
return n;
}
static inline int vm_popcnt_opt(cell_t x) {
int count = 0;
while (x) {
count += x & 1;
x >>= 1;
}
return count;
}
#endif
/* ========== Cache Operations ========== */
#if USE_ASM_OPT && (ARCH_ARM64 || ARCH_RISCV64)
#define vm_prefetch_opt(addr) vm_prefetch(addr)
#define vm_prefetch_stream_opt(addr) vm_prefetch_stream(addr)
#elif USE_ASM_OPT && ARCH_X86_64
static inline void vm_prefetch_opt(const void *addr) {
__builtin_prefetch(addr, 0, 3); /* Read, high temporal locality */
}
static inline void vm_prefetch_stream_opt(const void *addr) {
__builtin_prefetch(addr, 0, 0); /* Read, no temporal locality */
}
#else
#define vm_prefetch_opt(addr) ((void)0)
#define vm_prefetch_stream_opt(addr) ((void)0)
#endif
/* ========== Inner Interpreter (Direct Threading) ========== */
#if USE_DIRECT_THREADING && ARCH_RISCV64
#define NEXT_OPT() NEXT_RV64()
#define vm_setup_registers_opt(vm) vm_setup_registers_rv64(vm)
#define vm_save_registers_opt(vm) vm_save_registers_rv64(vm)
#define PRIM_DUP_OPT() PRIM_DUP_RV64()
#define PRIM_DROP_OPT() PRIM_DROP_RV64()
#define PRIM_SWAP_OPT() PRIM_SWAP_RV64()
#define PRIM_OVER_OPT() PRIM_OVER_RV64()
#define PRIM_ROT_OPT() PRIM_ROT_RV64()
#define PRIM_PLUS_OPT() PRIM_PLUS_RV64()
#define PRIM_MINUS_OPT() PRIM_MINUS_RV64()
#define PRIM_STAR_OPT() PRIM_STAR_RV64()
#define PRIM_SLASH_OPT() PRIM_SLASH_RV64()
#define PRIM_MOD_OPT() PRIM_MOD_RV64()
#define PRIM_AND_OPT() PRIM_AND_RV64()
#define PRIM_OR_OPT() PRIM_OR_RV64()
#define PRIM_XOR_OPT() PRIM_XOR_RV64()
#define PRIM_INVERT_OPT() PRIM_INVERT_RV64()
#define PRIM_NEGATE_OPT() PRIM_NEGATE_RV64()
#define PRIM_FETCH_OPT() PRIM_FETCH_RV64()
#define PRIM_STORE_OPT() PRIM_STORE_RV64()
#define PRIM_C_FETCH_OPT() PRIM_C_FETCH_RV64()
#define PRIM_C_STORE_OPT() PRIM_C_STORE_RV64()
#define PRIM_TO_R_OPT() PRIM_TO_R_RV64()
#define PRIM_R_FROM_OPT() PRIM_R_FROM_RV64()
#define PRIM_R_FETCH_OPT() PRIM_R_FETCH_RV64()
#define PRIM_2DUP_OPT() PRIM_2DUP_RV64()
#define PRIM_2DROP_OPT() PRIM_2DROP_RV64()
#define PRIM_ZERO_EQUALS_OPT() PRIM_ZERO_EQUALS_RV64()
#define PRIM_ZERO_LESS_OPT() PRIM_ZERO_LESS_RV64()
#define PRIM_EQUALS_OPT() PRIM_EQUALS_RV64()
#define PRIM_LESS_OPT() PRIM_LESS_RV64()
#define PRIM_GREATER_OPT() PRIM_GREATER_RV64()
#define PRIM_BRANCH_OPT() PRIM_BRANCH_RV64()
#define PRIM_ZBRANCH_OPT() PRIM_ZBRANCH_RV64()
#define PRIM_EXECUTE_OPT() PRIM_EXECUTE_RV64()
#elif USE_DIRECT_THREADING && ARCH_X86_64
#define NEXT_OPT() NEXT_ASM()
#define vm_setup_registers_opt(vm) vm_setup_registers(vm)
#define vm_save_registers_opt(vm) vm_save_registers(vm)
/* Primitive word macros */
#define PRIM_DUP_OPT() PRIM_DUP()
#define PRIM_DROP_OPT() PRIM_DROP()
#define PRIM_SWAP_OPT() PRIM_SWAP()
#define PRIM_PLUS_OPT() PRIM_PLUS()
#define PRIM_MINUS_OPT() PRIM_MINUS()
#define PRIM_STAR_OPT() PRIM_STAR()
#define PRIM_FETCH_OPT() PRIM_FETCH()
#define PRIM_STORE_OPT() PRIM_STORE()
#define PRIM_TO_R_OPT() PRIM_TO_R()
#define PRIM_R_FROM_OPT() PRIM_R_FROM()
#define PRIM_R_FETCH_OPT() PRIM_R_FETCH()
#define PRIM_BRANCH_OPT() PRIM_BRANCH()
#define PRIM_ZBRANCH_OPT() PRIM_ZBRANCH()
#elif USE_DIRECT_THREADING && ARCH_ARM64
#define NEXT_OPT() NEXT_ARM64()
#define vm_setup_registers_opt(vm) vm_setup_registers_arm64(vm)
#define vm_save_registers_opt(vm) vm_save_registers_arm64(vm)
/* Primitive word macros */
#define PRIM_DUP_OPT() PRIM_DUP_ARM64()
#define PRIM_DROP_OPT() PRIM_DROP_ARM64()
#define PRIM_SWAP_OPT() PRIM_SWAP_ARM64()
#define PRIM_OVER_OPT() PRIM_OVER_ARM64()
#define PRIM_ROT_OPT() PRIM_ROT_ARM64()
#define PRIM_PLUS_OPT() PRIM_PLUS_ARM64()
#define PRIM_MINUS_OPT() PRIM_MINUS_ARM64()
#define PRIM_STAR_OPT() PRIM_STAR_ARM64()
#define PRIM_SLASH_OPT() PRIM_SLASH_ARM64()
#define PRIM_MOD_OPT() PRIM_MOD_ARM64()
#define PRIM_AND_OPT() PRIM_AND_ARM64()
#define PRIM_OR_OPT() PRIM_OR_ARM64()
#define PRIM_XOR_OPT() PRIM_XOR_ARM64()
#define PRIM_INVERT_OPT() PRIM_INVERT_ARM64()
#define PRIM_NEGATE_OPT() PRIM_NEGATE_ARM64()
#define PRIM_FETCH_OPT() PRIM_FETCH_ARM64()
#define PRIM_STORE_OPT() PRIM_STORE_ARM64()
#define PRIM_C_FETCH_OPT() PRIM_C_FETCH_ARM64()
#define PRIM_C_STORE_OPT() PRIM_C_STORE_ARM64()
#define PRIM_TO_R_OPT() PRIM_TO_R_ARM64()
#define PRIM_R_FROM_OPT() PRIM_R_FROM_ARM64()
#define PRIM_R_FETCH_OPT() PRIM_R_FETCH_ARM64()
#define PRIM_2DUP_OPT() PRIM_2DUP_ARM64()
#define PRIM_2DROP_OPT() PRIM_2DROP_ARM64()
#define PRIM_ZERO_EQUALS_OPT() PRIM_ZERO_EQUALS_ARM64()
#define PRIM_ZERO_LESS_OPT() PRIM_ZERO_LESS_ARM64()
#define PRIM_EQUALS_OPT() PRIM_EQUALS_ARM64()
#define PRIM_LESS_OPT() PRIM_LESS_ARM64()
#define PRIM_GREATER_OPT() PRIM_GREATER_ARM64()
#define PRIM_BRANCH_OPT() PRIM_BRANCH_ARM64()
#define PRIM_ZBRANCH_OPT() PRIM_ZBRANCH_ARM64()
#define PRIM_EXECUTE_OPT() PRIM_EXECUTE_ARM64()
#else
/* No direct threading - use standard C function calls */
#define NEXT_OPT() do {} while(0)
#define vm_setup_registers_opt(vm) do {} while(0)
#define vm_save_registers_opt(vm) do {} while(0)
/* Primitives fall back to function calls */
#define PRIM_DUP_OPT() do {} while(0)
#define PRIM_DROP_OPT() do {} while(0)
/* ... etc ... */
#endif
/**
* @defgroup runtime_detect Runtime Architecture Detection
* @{
*
* @brief Runtime detection of CPU features
*
* Functions for detecting available CPU features at runtime, such as SIMD
* support (SSE4.2, AVX, NEON) and other architecture-specific capabilities.
* @}
*/
#if ARCH_X86_64
/* Check for SSE4.2, AVX, etc. */
static inline int vm_has_sse42_opt(void) {
#if USE_ASM_OPT
return vm_has_sse42();
#else
return 0;
#endif
}
#elif ARCH_ARM64
/* Check for NEON, CRC32, etc. */
static inline int vm_has_neon_opt(void) {
#if USE_ASM_OPT
return vm_has_neon();
#else
return 0;
#endif
}
#else
static inline int vm_has_sse42_opt(void) { return 0; }
static inline int vm_has_neon_opt(void) { return 0; }
#endif
/**
* @defgroup build_info Build Information
* @{
*
* @brief Functions for querying build configuration
*
* Provides information about the build configuration, including target
* architecture, enabled optimizations, and threading model.
* @}
*/
static inline const char *vm_get_arch_name(void) {
return ARCH_NAME;
}
static inline int vm_has_asm_opt(void) {
#if USE_ASM_OPT
return 1;
#else
return 0;
#endif
}
static inline int vm_has_direct_threading(void) {
#if USE_DIRECT_THREADING
return 1;
#else
return 0;
#endif
}
static inline const char *vm_get_build_info(void) {
static char info[256];
snprintf(info, sizeof(info),
"Architecture: %s, ASM: %s, Direct Threading: %s",
vm_get_arch_name(),
vm_has_asm_opt() ? "enabled" : "disabled",
vm_has_direct_threading() ? "enabled" : "disabled"
);
return info;
}
#endif /* ARCH_DETECT_H */
+144
View File
@@ -0,0 +1,144 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
*** StarForth ***
Block Subsystem Configuration API
----------------------------------
Purpose:
- Provide a common, reusable configuration interface for selecting
a "block subsystem" (FILE image vs RAM) and its geometry WITHOUT
touching any real I/O or backend code.
Notes:
- No file system calls; no mmap; no blkio includes.
- Geometries are computed deterministically from inputs only.
- FILE backend: we record the path and intent; size/blocks remain 0
until a real backend attaches later.
- RAM backend: ram_mb -> size_bytes -> blocks (floor to FBS multiple).
Typical flow:
- Parse CLI flags (--disk-img=<path> | --ram-disk=<MB>).
- Call blkcfg_init_from_options().
- Use g_blkcfg elsewhere to make decisions (later you'll attach drivers).
License: See LICENSE file. No warranty.
*/
#ifndef STARFORTH_BLKCFG_H
#define STARFORTH_BLKCFG_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef BLKCFG_DEFAULT_FBS
#define BLKCFG_DEFAULT_FBS 1024u /* default Forth Block Size in bytes */
#endif
#ifndef BLKCFG_PATH_MAX
#define BLKCFG_PATH_MAX 4096
#endif
typedef enum {
BLKCFG_BACKEND_NONE = 0,
BLKCFG_BACKEND_FILE = 1,
BLKCFG_BACKEND_RAM = 2
} blkcfg_backend_t;
typedef struct {
blkcfg_backend_t backend; /* FILE or RAM (or NONE) */
uint32_t fbs; /* Forth Block Size (bytes) */
uint8_t read_only; /* 1 = intended RO, else 0 */
/* Geometry (no I/O performed to derive these) */
uint64_t size_bytes; /* For RAM: computed; FILE: 0 */
uint32_t blocks; /* For RAM: computed; FILE: 0 */
/* FILE plan (no I/O): path is recorded only */
char file_path[BLKCFG_PATH_MAX];
} blkcfg_t;
/* Global config (optional convenience) */
extern blkcfg_t g_blkcfg;
/**
* Initialize a block configuration from CLI-style options.
*
* Inputs:
* - disk_img_path: if non-NULL and non-empty => choose FILE backend
* - ram_mb: megabytes for RAM fallback (only used if no disk image)
* - fbs: forth block size (bytes). If 0 => BLKCFG_DEFAULT_FBS
* - read_only: intended read-only flag (advisory)
*
* Behavior:
* - FILE: record path, set blocks/size_bytes = 0 (unknown until attached)
* - RAM : compute size_bytes = ram_mb*MiB; blocks = size_bytes/fbs (floored)
*
* Returns:
* 0 on success; -1 on invalid args.
*/
int blkcfg_init_from_options(blkcfg_t *cfg,
const char *disk_img_path,
uint64_t ram_mb,
uint32_t fbs,
uint8_t read_only);
/**
* Format a single-line summary into 'dst' (capacity 'dst_cap' bytes).
* Example:
* "backend=FILE path=./disks/os.img fbs=1024 ro=0"
* "backend=RAM size=1024MB fbs=1024 blocks=1048576 ro=0"
*
* Returns number of bytes that would have been written (like snprintf).
*/
int blkcfg_format_summary(const blkcfg_t *cfg, char *dst, size_t dst_cap);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* STARFORTH_BLKCFG_H */
+178
View File
@@ -0,0 +1,178 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef STARFORTH_BLKIO_H
#define STARFORTH_BLKIO_H
/* -----------------------------------------------------------------------------
StarForth - Block I/O Core API
- C99, deterministic core (no hidden malloc)
- Public types / errors / thin inline wrapper
- Backends provide a vtable implementing: open, close, read, write, flush, info
License: See LICENSE file. No warranty.
----------------------------------------------------------------------------- */
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef BLKIO_FORTH_BLOCK_SIZE
#define BLKIO_FORTH_BLOCK_SIZE 1024u
#endif
/* Error codes (negative = error) */
enum {
BLKIO_OK = 0,
BLKIO_EINVAL = -1,
BLKIO_ENOSUP = -2,
BLKIO_EIO = -3,
BLKIO_ENOSPACE = -4,
BLKIO_ECLOSED = -5
};
/* Forward decl */
struct blkio_dev;
/* Open parameters (generic) */
typedef struct {
uint32_t forth_block_size; /* 0 => BLKIO_FORTH_BLOCK_SIZE */
uint32_t total_blocks; /* number of Forth blocks; FILE may derive when 0 */
void *opaque; /* backend-specific */
} blkio_params_t;
/* Device info */
typedef struct {
uint32_t forth_block_size;
uint32_t total_blocks;
uint32_t phys_sector_size; /* best-effort; 0 if N/A */
uint64_t phys_size_bytes; /* best-effort; 0 if N/A */
uint8_t read_only;
} blkio_info_t;
/* Backend vtable */
typedef struct {
int (*open)(struct blkio_dev *dev, const blkio_params_t *p);
int (*close)(struct blkio_dev *dev);
int (*read)(struct blkio_dev *dev, uint32_t fblock, void *dst);
int (*write)(struct blkio_dev *dev, uint32_t fblock, const void *src);
int (*flush)(struct blkio_dev *dev);
int (*info)(struct blkio_dev *dev, blkio_info_t *out);
} blkio_vtable_t;
/* Device (public) */
typedef struct blkio_dev {
const blkio_vtable_t *vt;
uint32_t forth_block_size;
uint32_t total_blocks;
void *state; /* backend private */
uint8_t is_open;
} blkio_dev_t;
/* Core wrappers */
static inline int blkio_open(blkio_dev_t *dev, const blkio_vtable_t *vt, const blkio_params_t *p) {
if (!dev || !vt || !vt->open || !vt->close || !vt->read || !vt->write || !vt->flush || !vt->info)
return BLKIO_EINVAL;
dev->vt = vt;
dev->is_open = 0u;
dev->state = 0;
dev->forth_block_size = (p && p->forth_block_size) ? p->forth_block_size : BLKIO_FORTH_BLOCK_SIZE;
if (dev->forth_block_size == 0) return BLKIO_EINVAL;
dev->total_blocks = p ? p->total_blocks : 0u;
const int rc = vt->open(dev, p);
if (rc == BLKIO_OK) dev->is_open = 1u;
return rc;
}
static inline int blkio_close(blkio_dev_t *dev) {
if (!dev || !dev->is_open) return BLKIO_ECLOSED;
const int rc = dev->vt->close(dev);
dev->is_open = 0u;
return rc;
}
static inline int blkio_read(blkio_dev_t *dev, uint32_t fblock, void *dst) {
if (!dev || !dev->is_open || !dst) return BLKIO_EINVAL;
if (fblock >= dev->total_blocks) return BLKIO_EINVAL;
return dev->vt->read(dev, fblock, dst);
}
static inline int blkio_write(blkio_dev_t *dev, uint32_t fblock, const void *src) {
if (!dev || !dev->is_open || !src) return BLKIO_EINVAL;
if (fblock >= dev->total_blocks) return BLKIO_EINVAL;
return dev->vt->write(dev, fblock, src);
}
static inline int blkio_flush(blkio_dev_t *dev) {
if (!dev || !dev->is_open) return BLKIO_ECLOSED;
return dev->vt->flush(dev);
}
static inline int blkio_info(blkio_dev_t *dev, blkio_info_t *out) {
if (!dev || !dev->is_open || !out) return BLKIO_EINVAL;
const int rc = dev->vt->info(dev, out);
if (rc == BLKIO_OK) {
if (!out->forth_block_size) out->forth_block_size = dev->forth_block_size;
if (!out->total_blocks) out->total_blocks = dev->total_blocks;
}
return rc;
}
/* Utilities */
static inline void blkio_bzero(void *p, uint32_t fbs) {
uint8_t *q = (uint8_t *) p;
for (uint32_t i = 0; i < fbs; i++) q[i] = 0;
}
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* STARFORTH_BLKIO_H */
+101
View File
@@ -0,0 +1,101 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef STARFORTH_BLKIO_FACTORY_H
#define STARFORTH_BLKIO_FACTORY_H
/* -----------------------------------------------------------------------------
StarForth - Block I/O Factory (Public API)
Chooses FILE backend when a disk image path is provided; otherwise RAM.
No dynamic allocation; caller supplies backend state memory.
NOTE: This header avoids referencing blkio typedefs to prevent "unknown type"
errors when include order or local headers differ. We forward-declare only the
device struct here. The implementation includes "blkio.h".
----------------------------------------------------------------------------- */
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Forward declaration; real definition is in blkio.h */
struct blkio_dev;
/**
* Open a block device using either a file-backed image (if disk_img_path is
* non-empty) or a RAM-backed buffer (fallback).
*
* Parameters:
* - dev : output device object (caller-allocated)
* - disk_img_path : path to raw image; if NULL/empty => RAM fallback
* - read_only : 1 = open read-only; 0 = read/write
* - total_blocks : FILE: 0 => derive from file size; >0 => size hint
* RAM : must match ram_blocks
* - fbs : forth block size in bytes (0 => default per blkio.h)
* - state_mem : caller-provided backend state buffer (opaque to caller)
* - state_len : size of state_mem in bytes
* - ram_base : RAM storage base pointer (RAM backend only)
* - ram_blocks : number of Forth blocks in ram_base (RAM backend only)
* - out_used_file : optional; set to 1 if FILE backend selected, else 0
*
* Returns:
* 0 on success; negative blkio error code on failure.
*/
int blkio_factory_open(struct blkio_dev *dev,
const char *disk_img_path,
uint8_t read_only,
uint32_t total_blocks,
uint32_t fbs,
void *state_mem,
size_t state_len,
uint8_t *ram_base,
uint32_t ram_blocks,
uint8_t *out_used_file);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* STARFORTH_BLKIO_FACTORY_H */
+597
View File
@@ -0,0 +1,597 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
*** StarForth ***
Block Subsystem - Layer 2: Mapping & Business Logic (v2)
---------------------------------------------------------
Architecture (unified block address space):
- LBN 0..2047: FAST RAM (volatile, g.ram_base)
- LBN 2048..x: RAMDRIVE (raw RAM buffer, volatile; first attached device)
- LBN x..y: DISK IMG (virtio-blk, persistent; second attached device)
- LBN y+: USB / future devices (chained)
Each non-RAM device is attached via blk_subsys_attach_device() (formatted disk)
or blk_subsys_add_raw_device() (volatile RAM buffer). Devices are appended to
a chain; each gets a contiguous slot in the unified logical BAM (g.bam[]).
Physical BAMs (on-disk) are synced to/from their logical BAM slot at attach/flush.
blkio NOTE:
- blkio backends operate on 1 KiB units.
- One 4 KiB “devblock” == 4 consecutive 1 KiB blkio blocks.
License: See LICENSE file. No warranty.
*/
#ifndef STARFORTH_BLOCK_SUBSYSTEM_H
#define STARFORTH_BLOCK_SUBSYSTEM_H
#include <stdint.h>
#include <stddef.h>
#include "vm.h"
#include "blkio.h" /* ensure struct blkio_dev is fully visible */
#ifdef __cplusplus
extern "C" {
#endif
/* Core configuration constants */
#define BLK_FORTH_SIZE 1024u /* Forth block size */
#define BLK_RAM_BLOCKS 2080u /* Physical RAM blocks (user-visible LBN 0..2047) */
#define BLK_DISK_START 2080u /* Device-backed Forth blocks start at LBN 2080 */
#define BLK_DEVICE_SECTOR 4096u /* Physical “devblock” size (4×1 KiB blkio units) */
#define BLK_PACK_RATIO 3u /* 3× 1 KiB data per 4 KiB devblock (plus 1 KiB metadata) */
#define BLK_META_TOTAL 1024u /* Last 1 KiB in a 4 KiB devblock is metadata */
#define BLK_META_PER_BLOCK 341u /* 341×3 ~= 1023, padded to 1024 */
/* Forth-friendly reserved ranges */
#ifndef BLK_FORTH_SYS_RESERVED
#define BLK_FORTH_SYS_RESERVED 32u /* RAM physical blocks 0..31 hidden; user LBN 0 = PBN 32 */
#endif
#ifndef BLK_DISK_SYS_RESERVED
#define BLK_DISK_SYS_RESERVED 32u /* First 32 blocks of each disk reserved (byte-aligned BAM offset) */
#endif
#ifndef BLK_META_FENCE_INIT
#define BLK_META_FENCE_INIT 128u /* Starting size (4 KiB devblocks -- 512 KiB) of the
* top-of-device system-metadata fence, grows downward
* from here. */
#endif
/* =========================
* On-disk volume header v2
* =========================
* Serialized into device devblock 0 (4 KiB = 4×1 KiB blkio blocks 0..3).
* BAM lives in external devblocks [bam_start .. bam_start+bam_devblocks-1] (each 4 KiB).
* All *_devblocks indices are in 4 KiB units.
*/
typedef struct {
/* Identification & versioning */
uint32_t magic; /* 0x53544652 "STFR" */
uint32_t version; /* 2 */
/* Administrative info */
uint32_t total_volumes;
uint32_t flags;
char label[64];
/* Physical device geometry (4 KiB devblocks) */
uint64_t total_devblocks; /* count of 4 KiB devblocks (blkio_info.total_blocks / 4) */
/* BAM placement (external 1-bit bitmap region, stored in 4 KiB pages) */
uint32_t bam_start; /* usually 1 */
uint32_t bam_devblocks; /* number of 4 KiB pages used by BAM */
uint32_t devblock_base; /* first payload devblock = reloc_start + reloc_devblocks */
/* Capacity modeling (Forth 1 KiB blocks tracked/usable) */
uint64_t tracked_blocks; /* 32768 * bam_devblocks (bits per 4 KiB page) */
uint64_t total_blocks; /* min(tracked, 3 * (total_devblocks - 1 - bam_devblocks)) */
uint64_t free_blocks;
/* Allocation hints */
uint64_t first_free; /* next free Forth block (>= 1024) */
uint64_t last_allocated;
/* Reserved low ranges */
uint32_t reserved_disk_lo; /* e.g., 32 blocks reserved at 1024.. */
uint32_t reserved_ram_lo; /* e.g., 33 blocks reserved at 0..32 */
/* Timestamps (optional) */
uint64_t created_time;
uint64_t mounted_time;
/* Optional integrity (unused yet) */
uint64_t hdr_crc;
/* Relocation-exception table placement (Milestone 2h+ single-block relocation).
* Appended after hdr_crc, carved out of what was previously _pad[] -- appending
* (not inserting) preserves every earlier field's byte offset, so a pre-existing
* formatted volume's zeroed padding reads back here as reloc_devblocks=0 ("no
* reloc capacity"), gracefully, not a format-breaking change. See
* block_subsystem.c's reloc_flush_to_disk()/reloc_load_from_disk(). */
uint32_t reloc_start; /* usually bam_start + bam_devblocks */
uint32_t reloc_devblocks; /* number of 4 KiB pages used by the reloc table (0 = none) */
/* System-metadata fence (Phase 8, 2026-08-26): a reserved zone of
* whole, RAW (unpacked -- same shape as the header/BAM/reloc-table
* devblocks, not the 3-Forth-blocks-per-devblock user payload
* packing) devblocks at the TOP of the device's payload region,
* opposite end from reserved_disk_lo's bottom BAM reservation,
* growing DOWNWARD as system metadata (starting with Zuse's cert)
* needs more room. Never RAM-backed -- this field only exists on
* real disk-backed slots. Units are 4 KiB devblocks, same as
* bam_devblocks/reloc_devblocks (NOT Forth 1 KiB blocks -- corrected
* 2026-08-26, before anything depended on the original wrong unit,
* so this reads as a clean field, not a migration). Folded directly
* into compute_totals_from_B()'s payload4k calculation, so
* total_blocks/user_blocks/free_blocks all shrink correctly with no
* separate subtraction needed elsewhere. Appended after
* reloc_devblocks, carved out of what was previously _pad[] -- same
* graceful-default technique as reloc_devblocks itself: a
* pre-existing formatted volume's zeroed padding reads back here as
* meta_fence_blocks=0 ("no fence yet"), not a format-breaking
* change. See FABRIC-2.md's Phase 8 §C writeup for the full design. */
uint32_t meta_fence_blocks; /* current fence size, in 4 KiB devblocks (0 = none yet) */
/* Padding to keep header ≤ 4096 bytes */
uint8_t _pad[4096 - (
4 + 4 + /* magic, version */
4 + 4 + 64 + /* total_volumes, flags, label */
4 + /* compiler alignment gap before total_devblocks'
* uint64_t sibling tracked_blocks -- verified via
* offsetof(), not hand-derived (2026-08-26: this
* exact formula was off by 4 bytes from trusting
* arithmetic alone before this fix) */
8 + /* total_devblocks */
4 + 4 + 4 + /* bam_start, bam_devblocks, devblock_base */
8 + 8 + 8 + /* tracked_blocks, total_blocks, free_blocks */
8 + 8 + /* first_free, last_allocated */
4 + 4 + /* reserved ranges */
8 + 8 + /* timestamps */
8 + /* hdr_crc */
4 + 4 + /* reloc_start, reloc_devblocks */
4 /* meta_fence_blocks */
)];
} blk_volume_meta_t;
/* Same discipline homeblocks_sig.h's own header-size check uses: the pad
* math above is meant to keep this struct at exactly one 4 KiB devblock,
* verified at compile time rather than trusted by inspection -- adding
* meta_fence_blocks is exactly the kind of edit that could silently
* throw this off by a few bytes. */
_Static_assert(sizeof(blk_volume_meta_t) == 4096,
"blk_volume_meta_t must be exactly one 4 KiB devblock");
/* blk_meta_t.flags bit values -- FABRIC-2.md §F.4/§H.6/§H.12 step 13,
* decided 2026-09-02/03. Orthogonal bits, not a mutually-exclusive enum:
* a block can be both CLAIMED and MIGRATING at once. Grounded in the only
* states §F.4 actually motivated by a real need (MIGSM/UNCLEAN, two
* then-currently-blocked graph nodes) plus CLAIMED/STALE, the names
* already used loosely in that pass's own prose. 61 bits remain reserved. */
#define BLK_FLAG_CLAIMED (1ull << 0) /* owned, per BMAPFMT's owner_fp */
#define BLK_FLAG_MIGRATING (1ull << 1) /* mid-migration; serves MIGSM */
#define BLK_FLAG_STALE (1ull << 2) /* interrupted flush; serves UNCLEAN */
/* Per-1 KiB block metadata (packed into top 1 KiB region of each 4 KiB sector). */
typedef struct {
/* Core integrity (16 bytes) */
uint64_t checksum; /* CRC64 of block data */
uint64_t magic; /* 0x424C4B5F5354524BULL "BLK_STRK" */
/* Timestamps (16 bytes) */
uint64_t created_time; /* Unix timestamp (creation) */
uint64_t modified_time; /* Unix timestamp (last write) */
/* Block status (16 bytes) */
uint64_t flags; /* Status flags -- BLK_FLAG_* bits above */
uint64_t write_count; /* Number of writes (wear leveling) */
/* Content identification (32 bytes) */
uint64_t content_type; /* 0=empty, 1=source, 2=data, ... */
uint64_t encoding; /* 0=ASCII, 1=UTF-8, 2=binary, ... */
uint64_t content_length; /* Actual data length (≤ 1024) */
uint64_t reserved1; /* Alignment/future use */
/* Cryptographic (64 bytes) */
uint64_t entropy[4]; /* 256-bit entropy/random seed */
uint64_t hash[4]; /* SHA-256 (optional) */
/* Security & ownership -- FABRIC-2.md §F.4/§H.6/§H.12 step 12, decided
* 2026-08-27/2026-09-03: BMAPFMT repurposes this slot rather than
* building a separate on-drive block-map table (distributed
* ownership/ACL, travels with the block itself). Replaces the old
* owner_id/permissions/acl_block/signature[2] fields, which predated
* and directly conflicted with both the anti-POSIX principle and
* VMIdentity's pubkey-based model. Not the same 40-byte budget the
* old fields occupied -- natural alignment padding (uint32_t acl_ttl
* and uint64_t reserved_future each force a few bytes of compiler-
* inserted padding after the preceding uint8_t fields) makes this
* section's real footprint smaller; verified below via
* _Static_assert on the whole struct's actual sizeof(), not trusted
* by hand (see the blk_volume_meta_t padding-bug lesson this project
* already learned once). */
uint8_t owner_fp[8]; /* truncated fingerprint of owner's VMIdentity
* pubkey -- cheap per-block; full pubkey
* resolves via the drive's own identity
* record. */
uint8_t acl_allow; /* cached fast-deny bit, checked first --
* vm.c:611-624's exact pattern, applied to a
* block instead of a word. */
uint32_t acl_ttl; /* countdown, same shape as DictEntry's
* acl_ttl -- blocks support temporary
* elevation too, same ACL-TTL-reuse
* mechanism and Zuse-eligibility-list gating
* as the word card (§H.5). */
uint8_t acl_reserved[3]; /* still genuinely undecided -- deliberate
* slack per "flexibility until we understand
* the recipe," not a placeholder to fill
* reflexively. */
uint64_t reserved_future; /* untouched budget, same reasoning. */
/* Link/chain support (32 bytes) */
uint64_t prev_block; /* Previous in chain (0=none) */
uint64_t next_block; /* Next in chain (0=none) */
uint64_t parent_block; /* Parent/index (0=none) */
uint64_t chain_length; /* Total blocks in chain */
/* Application-specific (120 bytes) */
uint64_t app_data[15]; /* 15×64-bit app-defined fields */
uint8_t padding[5]; /* trailing slack, unrelated to any exact size target --
* the old "341-byte slice" comment here was already
* inaccurate before FABRIC-2.md §H.12 step 12's edit
* (sizeof(blk_meta_t) was 344, not 341, due to
* ordinary trailing struct-alignment padding after
* this array -- harmless since BLK_META_PER_BLOCK,
* the only thing that constant would matter to, has
* zero callers anywhere in this codebase). Verify
* this struct's real size with the _Static_assert
* below, not by re-deriving it from this comment. */
} blk_meta_t;
/* Verified via offsetof()/sizeof(), not trusted by hand -- see the
* blk_volume_meta_t padding-bug lesson this project already learned once
* (a hand-summed struct padding formula hid a real 4-byte alignment gap).
* 336, not the BLK_META_PER_BLOCK/"341-byte slice" figure this struct's
* own comments have long claimed -- that mismatch predates this assert and
* is harmless today (see padding[5]'s own comment above), but this assert
* now makes any future drift in either direction fail the build instead of
* silently mismatching a constant nothing currently checks against it. */
_Static_assert(sizeof(blk_meta_t) == 336,
"blk_meta_t size changed -- update this assert and check BLK_META_PER_BLOCK");
/* Error codes */
enum {
BLK_OK = 0,
BLK_EINVAL = -1,
BLK_ERANGE = -2,
BLK_EIO = -3,
BLK_ENODEV = -4,
BLK_ERESERVED = -5,
BLK_EDIRTY = -6,
BLK_ENOMEM = -7,
BLK_ENOSPC = -8 /* not enough free devblocks -- blk_firsttouch_claim() */
};
/* Per-block BAM entry — one per user block in each device slot */
typedef struct {
uint8_t allocated; /* 0=free, 1=in use */
uint8_t dirty; /* 1=UPDATE called; content needs flush */
} blk_bam_entry_t;
/* Low-level disk container format state. This is distinct from — and known
* nothing about — any higher-level content classification (e.g. Artemis's
* own BLANK/LithosAnanke/Unrecognized marker check). A slot stays
* PROVISIONAL, and all writes to it are refused, until its owner explicitly
* calls blk_subsys_confirm_format() after deciding the disk is safe to
* touch. This is what makes "halt, disk preserved" actually true instead
* of being overwritten by this layer's own opportunistic reformat before
* the owner ever gets a look. */
enum { BLK_FMT_FORMATTED = 0, BLK_FMT_PROVISIONAL = 1 };
/* ===== Public API ===== */
int blk_subsys_init(VM *vm, uint8_t *ram_base, size_t ram_size);
int blk_subsys_attach_device(struct blkio_dev *dev);
/* Milestone 2h hot-detach. Refuses (BLK_EINVAL) unless dev's slot is the
* current chain tail -- see this function's own doc comment in
* block_subsystem.c for why. Discards any dirty cache/BAM/vol_meta state
* rather than attempting to flush it (the device is already physically
* gone by the time this is called). Returns BLK_ENODEV if dev isn't
* attached, BLK_EINVAL if dev is NULL or not the chain tail.
*/
int blk_subsys_detach_device(struct blkio_dev *dev);
/* Monotonic counter, bumped on every attach/detach (Milestone 2h). A raw
* pointer comparison against a blk_get_buffer() result cannot reliably
* detect a same-address device swap (this kernel's own first-fit kmalloc,
* src/starkernel/memory/kmalloc.c, can hand back the exact address just
* free()'d by a detach to the very next attach's calloc() -- confirmed
* live) -- callers that cache a blk_get_buffer() result across calls
* (block_words.c's VM block window) must instead compare this epoch
* against the value they last observed, invalidating their whole cache on
* any change rather than trusting a stored pointer's identity.
*/
uint64_t blk_subsys_epoch(void);
/* Single-block relocation. Copies home_lbn's current content to target_lbn
* (both must already be valid LBNs -- target_lbn is expected to be a block
* the caller's own identity already owns, on whichever device it's being
* relocated to; this function does not itself validate ownership, that's
* policy, left to the caller -- see this session's ACL-owns-policy
* direction in FABRIC-1.md), frees home_lbn's original BAM entry, records
* an LBN->LBN redirect so every future access to home_lbn transparently
* resolves to target_lbn instead, and bumps blk_subsys_epoch() so any VM's
* cached block window correctly invalidates. Persisted immediately to the
* relocation-owner device's on-disk table (see block_subsystem.c's
* reloc_flush_to_disk()) if one exists.
*
* Returns BLK_OK on success.
* Returns BLK_EINVAL if home_lbn == target_lbn, or home_lbn is already
* relocated (call again with a different target to re-relocate --
* not supported by simply calling this twice on the same home_lbn).
* Returns BLK_ERANGE if either LBN doesn't resolve to a valid device.
* Returns BLK_EIO if the content copy fails (e.g. target device refuses
* the write -- a read-only backend like blkio_usb.c today).
* Returns BLK_ENOMEM if the in-memory relocation table is full.
*/
int blk_subsys_relocate_block(uint32_t home_lbn, uint32_t target_lbn);
/* blk_get_device_range - the LBN range (start_lbn, count of 1 KiB FORTH
* blocks) a specific already-attached device occupies (FABRIC-2.md §I.2,
* 2026-09-04). Mirrors blk_subsys_detach_device()'s own dev-pointer slot
* lookup, exposed publicly for the first time -- needed so a caller can
* scope a scan/query to exactly one attached device rather than the
* global, multi-device LBN space blk_get_total_blocks()/blk_get_meta()
* otherwise address. Returns BLK_ENODEV if dev isn't attached.
*/
int blk_get_device_range(struct blkio_dev *dev, uint32_t *out_start_lbn, uint32_t *out_count);
/* blk_lbn_device_handle - the missing direction from blk_get_device_range()
* above (that goes device -> LBN range; this goes LBN -> device). Returns
* an opaque, stable handle -- directly comparable with == to test "do
* these two LBNs belong to the same physical device" -- or NULL if lbn
* doesn't resolve to any attached device. Never dereference the returned
* pointer; it exists only for equality comparison (Phase 8 v3, 2026-09-23:
* added so MOVE/CMOVE/CMOVE> can refuse a same-VM, cross-device raw block
* copy through the block-window mechanism -- see those words' own callers
* in memory_words.c/string_words.c).
*/
const void *blk_lbn_device_handle(uint32_t lbn);
/* blk_get_device_free_blocks - free vs. total 1 KiB FORTH-block count for
* a specific already-attached device (same slot lookup as
* blk_get_device_range()). RAM-backed slots (no on-disk vol_meta) report
* out_total == out_free == 0, matching blk_mark_allocated()/_free()'s own
* "!slot->raw_base" gate on touching free_blocks bookkeeping at all.
* Returns BLK_ENODEV if dev isn't attached.
*/
int blk_get_device_free_blocks(struct blkio_dev *dev, uint64_t *out_free, uint64_t *out_total);
/* blk_get_first_disk_range - the LBN range of first_disk_slot(), i.e.
* whichever disk-backed device blk_get_volume_meta()/blk_meta_zone_read()
* already implicitly target -- Artemis's own disk.img in practice, since
* it attaches at boot before any USB hotplug (the same boot-order
* convention those two functions already rely on, not a new assumption).
* FIRSTTOUCH (below) scans exactly this range, not the global LBN space.
* Returns BLK_ENODEV if no disk-backed device is attached at all.
*/
int blk_get_first_disk_range(uint32_t *out_start_lbn, uint32_t *out_count);
/* blk_firsttouch_claim - FABRIC-2.md §F.11, decisions 1-3, built exactly
* as scoped 2026-08-27 (re-verified against current blk_meta_t 2026-09-04
* before building -- the chain fields below are real and untouched by
* BMAPFMT's unrelated owner_fp/acl_allow/acl_ttl/flags field addition).
*
* One linear scan over blk_get_first_disk_range()'s devblocks (4 KiB
* granularity -- one blk_meta_t per devblock, not per 1 KiB FORTH block),
* via blk_get_meta(). Answers both questions the scan needs in the same
* pass: does owner_fp already own a claim (not reused by this call --
* every call makes a *new* claim of exactly `count` additional devblocks,
* per §F.11's own text; an existing claim is a separate, not-yet-decided
* question), and which devblocks are free (owner_fp all-zero, BLK_FLAG_
* CLAIMED clear). A claim is a scattered chain, not a required contiguous
* run -- links devblocks via blk_meta_t's prev_block/next_block/
* chain_length fields; owner_fp is stamped onto *every* member devblock,
* not just the head, so ownership reads locally from any member without
* walking the chain. Fails outright (BLK_ENOSPC), no partial claim, if
* fewer than `count` free devblocks exist.
*
* @param owner_fp 8-byte fingerprint (this session's convention: the
* first 8 bytes of the claiming identity's 32-byte
* VMIdentity.owner_pubkey -- owner_fp had no real
* producer anywhere in this codebase before this,
* only accessors, so this call site sets the
* convention rather than following one).
* @param count Devblocks to claim (whole devblocks, §F.4 decision
* 4, already inherited, not revisited here).
* @param out_chain_head The claim's identity to the caller: its
* chain-head devblock number (a 1 KiB FORTH-block
* LBN, the representative/first sub-block of that
* devblock -- not a separate devblock-index
* namespace; blk_get_meta()/blk_set_meta() already
* resolve any LBN within a devblock to the same
* shared blk_meta_t, so this is a valid handle for
* blk_meta_relocate_devblock() below).
* @return BLK_OK, BLK_ENODEV (no disk-backed device), BLK_EINVAL (NULL
* arg or count == 0), or BLK_ENOSPC (not enough free devblocks).
*/
int blk_firsttouch_claim(const uint8_t owner_fp[8], uint32_t count, uint32_t *out_chain_head);
/* blk_owner_has_claim - FABRIC-2.md §I.2's own "an existing claim is a
* separate, not-yet-decided question" (see blk_firsttouch_claim()'s doc
* comment above), decided 2026-09-05 for the overflow-trigger call site
* (capsule_wirebind.c): answered by a disk scan, not a RAM flag, matching
* BMAPFMT's own "ownership travels with the block, no centralized table"
* model -- the answer survives a reboot/reattach for free, the same way
* blk_firsttouch_claim()'s own free/claimed scan already does.
*
* One linear scan over blk_get_first_disk_range()'s representative LBNs
* (same shape as blk_firsttouch_claim()'s own scan); returns as soon as
* any devblock's blk_meta_t has BLK_FLAG_CLAIMED set and owner_fp equal
* to the argument.
*
* @param owner_fp 8-byte fingerprint, same convention as
* blk_firsttouch_claim()'s own owner_fp argument.
* @return 1 if owner_fp already owns at least one claimed devblock, 0 if
* not (including "no disk-backed device attached" -- nothing to
* own in that case).
*/
int blk_owner_has_claim(const uint8_t owner_fp[8]);
/* blk_meta_relocate_devblock - the real migration primitive (FABRIC-2.md
* §I.2, migration state machine, 2026-09-04): moves one devblock's full
* content and blk_meta_t ownership from home_devblock to target_devblock.
* BLK_FLAG_MIGRATING is set on home_devblock for the duration -- the
* state machine genuinely is just this one flag's two states (clear =
* STABLE, set = MIGRATING), matching what BLK_FLAG_MIGRATING was already
* reserved for.
*
* Devblock granularity (4 KiB) holds BLK_PACK_RATIO (3, not 4 -- the 4th
* 1 KiB slice is the packed blk_meta_t metadata region) FORTH blocks
* (1 KiB each); the actual content copy runs blk_subsys_relocate_block()
* BLK_PACK_RATIO times, once per FORTH-block offset within the devblock
* -- that function is the only existing content-relocation primitive,
* and it operates at FORTH-block granularity, not devblock granularity,
* so this function is the bridge between the two. On success, transfers
* owner_fp/acl_allow/acl_ttl from home's blk_meta_t to target's, clears
* home's (freed, BLK_FLAG_CLAIMED unset), and sets BLK_FLAG_CLAIMED on
* target.
*
* NOT atomic across all BLK_PACK_RATIO FORTH-block copies -- a mid-loop
* failure (e.g. blk_subsys_relocate_block()'s own reloc-table
* exhaustion) leaves however many already succeeded in place, redirected, with
* BLK_FLAG_MIGRATING still set on home_devblock and blk_meta_t ownership
* NOT yet transferred. This is a known, documented limitation, not
* silently accepted: true all-or-nothing atomicity across BLK_PACK_RATIO
* separate relocation-table entries would need its own transaction log, out of
* scope for this pass. A caller that hits a non-BLK_OK return should
* treat home_devblock as still MIGRATING and not yet safe to reclaim.
*
* @return BLK_OK, BLK_EINVAL (home == target, or invalid devblock), or
* whichever blk_subsys_relocate_block() error stopped the loop.
*/
int blk_meta_relocate_devblock(uint32_t home_devblock, uint32_t target_devblock);
/* blk_migration_idle_check - heat/wear-leveling migration trigger, meant
* to be called once per idle tick (FABRIC-2.md §I.2, 2026-09-04). See
* block_subsystem.c's own doc comment for the full scan/threshold
* description and what's deliberately left open (overflow-triggered
* migration). */
void blk_migration_idle_check(void);
int blk_subsys_shutdown(void);
uint8_t *blk_get_buffer(uint32_t block_num, int writable);
uint8_t *blk_get_empty_buffer(uint32_t block_num);
int blk_flush(uint32_t block_num);
int blk_update(uint32_t block_num);
int blk_get_volume_meta(blk_volume_meta_t *meta);
int blk_set_volume_meta(const blk_volume_meta_t *meta);
/* Top-of-device system-metadata fence I/O (Phase 8, 2026-08-26) -- raw,
* unpacked 4 KiB devblocks, no FORTH word wraps either. See
* block_subsystem.c's own doc comment on these two functions for the
* full addressing/refusal rules. buf must point to exactly 4096 bytes.
* Returns BLK_OK, BLK_ENODEV (no disk-backed device attached),
* BLK_EINVAL (devblock_from_top >= the on-disk fence size, or NULL buf),
* or BLK_EIO (underlying blkio read/write failed). */
int blk_meta_zone_read(uint32_t devblock_from_top, uint8_t buf[4096]);
int blk_meta_zone_write(uint32_t devblock_from_top, const uint8_t buf[4096]);
/* CRC-64/ISO (poly 0x42F0E1EBA9EA3693), reflected, init/final all-ones --
* exposed for homeblocks_sig.c's drive-signature integrity check, which
* needs the exact same algorithm this file already uses for per-block
* checksums rather than a second, duplicate CRC implementation. */
uint64_t compute_crc64(const uint8_t *data, size_t len);
int blk_is_valid(uint32_t block_num);
uint32_t blk_get_total_blocks(void);
int blk_get_meta(uint32_t block_num, blk_meta_t *meta);
int blk_set_meta(uint32_t block_num, const blk_meta_t *meta);
/* BMAPFMT field accessors -- FABRIC-2.md §F.4/§H.6/§H.12 step 14. Thin
* read-modify-write wrappers over blk_get_meta()/blk_set_meta() (which
* already own the caching/dirty-tracking), one per new blk_meta_t field.
* FORTH wrappers (BLK-ACL-ALLOW@/! etc., §H.12 step 15) call these, not
* blk_get_meta()/blk_set_meta() directly -- same C-primitive/FORTH-policy
* split as the existing word-level ACL system. */
int blk_owner_fp_get(uint32_t block_num, uint8_t out_fp[8]);
int blk_owner_fp_set(uint32_t block_num, const uint8_t fp[8]);
int blk_acl_allow_get(uint32_t block_num, uint8_t *out_allow);
int blk_acl_allow_set(uint32_t block_num, uint8_t allow);
int blk_acl_ttl_get(uint32_t block_num, uint32_t *out_ttl);
int blk_acl_ttl_set(uint32_t block_num, uint32_t ttl);
int blk_flags_get(uint32_t block_num, uint64_t *out_flags);
int blk_flags_set(uint32_t block_num, uint64_t flags);
int blk_is_allocated(uint32_t block_num);
int blk_mark_allocated(uint32_t block_num);
int blk_mark_free(uint32_t block_num);
int blk_allocate(uint32_t * block_num);
int blk_subsys_add_raw_device(uint8_t *buf, uint32_t nblocks);
/* Commit the low-level format (write header + BAM) for the disk slot that
* owns lbn. No-op (returns BLK_OK) if already FORMATTED. Must be called
* by the disk's owner before any write to that slot will succeed. */
int blk_subsys_confirm_format(uint32_t lbn);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* STARFORTH_BLOCK_SUBSYSTEM_H */
+107
View File
@@ -0,0 +1,107 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef STARFORTH_CLI_H
#define STARFORTH_CLI_H
#include <stdint.h>
#include "log.h"
/**
* @brief Heartbeat logging modes for DoE experiments
*/
typedef enum {
HEARTBEAT_LOG_OFF = 0, /* No heartbeat output, only final summary */
HEARTBEAT_LOG_SUMMARY = 1, /* Bucket aggregates every N ticks */
HEARTBEAT_LOG_FULL = 2 /* Per-tick CSV output to hb/run-<id>.csv */
} HeartbeatLogMode;
/**
* @brief Configuration structure from command-line arguments
* Consolidates all parsed CLI options into a single, cleanly organized struct
*/
typedef struct
{
/* Test modes */
int run_tests;
int break_me;
int fail_fast;
/* Benchmark mode */
int benchmark;
int benchmark_iterations;
/* Design of Experiments mode */
int doe_experiment; /* 1 if --doe-experiment flag set, run harness once and collect metrics */
HeartbeatLogMode heartbeat_log_mode; /* off|summary|full for DoE time-series data */
/* REPL mode */
int script_mode; /* -s: silent/script mode (no prompts, no "ok" output) */
/* Logging */
LogLevel log_level;
int log_level_explicitly_set;
/* Block I/O */
const char* disk_img_path;
uint32_t ram_disk_mb;
/* Legacy flags (internal use) */
int run_tests_flag_observed;
} CLIConfig;
/**
* @brief Parse command-line arguments into CLIConfig
*
* @param argc Argument count
* @param argv Argument vector
* @return CLIConfig struct with parsed options, or exits on --version/--help
*/
CLIConfig cli_parse(int argc, char* argv[]);
/**
* @brief Print usage/help information
* @param program_name Name of the executable
*/
void cli_print_usage(const char* program_name);
#endif /* STARFORTH_CLI_H */
+240
View File
@@ -0,0 +1,240 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef COMPUDYNAMICS_H
#define COMPUDYNAMICS_H
#include <stdint.h>
/* ============================================================================
* Compudynamics: Generic Entity-Agnostic Physics Engine
* ============================================================================
*
* Four operational levels observe and adapt to their own execution history:
* - words (dictionary entries, driven by ssm_jacquard.c) -- LIVE
* - VMs (Tripod fleet, driven by capsule_vm_physics.c) -- reserved
* - blocks (Artemis block storage) -- reserved
* - messages (Hermes event routing) -- reserved
*
* This module provides the entity-agnostic machinery shared by all four:
* a deterministic regime classifier over an entity-ID touch history, and a
* generic UCB1 bandit over a caller-sized config space. "Entity-ID" means
* whatever the level touches -- word_id for words, vm_id for VMs, block
* LBN for blocks, message type for messages.
*
* Determinism is the entire point of this module. Every input is an
* execution count or an entity-ID sequence -- never a wall-clock timestamp,
* never a duration. Two architectures executing the same instruction
* sequence at different real speeds (e.g. QEMU/TCG amd64 vs aarch64) must
* produce byte-identical classifier output. This was violated once
* (ssm_jacquard.c's temporal_decay input, derived from a wall-clock-tainted
* decay slope) and broke cross-architecture dict_hash parity; this module
* exists so that mistake has nowhere to hide the second time.
*
* Word-level and VM-level are the only two levels with real, instantiated
* CDTuning constants today (CD_TUNING_WORD, CD_TUNING_VM). Only word-level
* is actually wired through this module (ssm_jacquard.c); VM-level's own
* fleet mechanism (capsule_vm_physics.c, VMFleetWindow) is a separate,
* already-validated, already-accepted design that legitimately depends on
* real elapsed time for its rate-recovery math (fleet_transfer_slope_q48)
* and is out of scope here. CD_TUNING_VM documents what this module's
* classifier-side parameters would be if VM-level regime classification
* were ever migrated onto it; its bandit-side fields are zeroed and
* unused because VM level has no config-space bandit today. Block and
* message levels have no Artemis/Hermes compudynamics implementation yet
* -- CD_TUNING_BLOCK and CD_TUNING_MESSAGE are deliberately not defined
* here rather than filled with invented numbers.
* ============================================================================
*/
/* Hard cap on how many recent entity-ID touches cd_classify_ids() will look
* at. Callers pass a shorter slice via count; this cap bounds the classifier's
* O(depth^2) worst case and the stack scratch space it uses internally. */
#define CD_MAX_CLASSIFY_DEPTH 64u
/* ============================================================================
* Deterministic Regime Classification
* ============================================================================
*/
typedef struct {
uint32_t diversity_q16; /* distinct IDs in window / window size, Q16 [0,65536] */
uint32_t volatility_q16; /* dispersion (coefficient of variation) of per-ID
* touch counts within the window, Q16, unclamped
* above 65536 is possible and meaningful (high CV) */
uint32_t locality_q16; /* fraction of touches that repeat an ID seen within
* the preceding locality_lookback touches, Q16 [0,65536] */
} CDRegimeMetrics;
/**
* @brief Classify a recent entity-ID touch sequence into diversity/volatility/
* locality, purely from execution counts -- no wall-clock input anywhere.
*
* @param ids Recent entity IDs, oldest first, chronological order
* @param count Number of valid entries in ids (clamped internally
* to CD_MAX_CLASSIFY_DEPTH)
* @param locality_lookback How many preceding touches count as "recent" for
* the locality measure (clamped to count)
* @return Classified regime metrics; all-zero if count == 0
*/
CDRegimeMetrics cd_classify_ids(const uint32_t *ids, uint32_t count,
uint32_t locality_lookback);
/* ============================================================================
* Per-Level Tuning Knobs
* ============================================================================
*/
typedef struct {
/* --- Classifier knobs (used by cd_classify_ids callers) --- */
uint32_t id_window_depth; /* touches to classify over, <= CD_MAX_CLASSIFY_DEPTH */
uint32_t locality_lookback; /* recurrence lookback within id_window_depth */
uint32_t diversity_high_threshold_q16;
uint32_t volatility_high_threshold_q16;
uint32_t locality_high_threshold_q16;
/* --- Bandit knobs (used by cd_config_table_*); 0 where the level has
* no config-space bandit (e.g. VM level today -- see file header) --- */
uint32_t num_configs; /* size of the config space, e.g. 128 */
uint32_t num_regimes; /* size of the regime space, e.g. 8 */
uint32_t score_max;
uint32_t score_min;
uint32_t score_decay_factor_q16; /* per-tick exponential decay of benefit_score */
uint32_t ucb_k; /* UCB1 exploration coefficient */
uint32_t reward_gain; /* max per-tick score delta magnitude */
uint32_t joint_weight_q16; /* weight of window/locality joint-convergence
* signal in the combined reward, Q16 */
uint32_t anova_weight_q16; /* weight of the caller-supplied stability
* signal (e.g. ANOVA early-exit) in the
* combined reward, Q16 */
} CDTuning;
/* Tuning knobs are returned by value, not exposed as addressable globals.
* This freestanding kernel's boot-time loader does not reliably relocate
* the address of an extern const aggregate referenced from a different
* translation unit (confirmed by direct QEMU/gdb-style fault tracing:
* &CD_TUNING_WORD-style access faulted on an unrelocated pointer, while
* cross-TU function calls -- including struct-return-by-value, which
* uses a caller-local hidden pointer, never a global's address --
* worked correctly). Every existing scalar tunable elsewhere in this
* codebase already avoids this by being a #define immediate rather than
* a referenced global; these accessors are the aggregate-typed
* equivalent of that same constraint. */
/* Word level (ssm_jacquard.c): 128 configs (7-bit L1-L7), 8 regimes
* (3-bit diversity/volatility/locality). Values match the constants
* ssm_jacquard.c/.h used before this module existed -- this is a lift,
* not a retune. */
CDTuning cd_tuning_word(void);
/* VM level (capsule_vm_physics.c): classifier-side parameters only, for a
* future migration off Hera's word-level regime proxy. Not consumed by
* any call site today -- see file header. Bandit fields are zero: VM
* level modulates a continuous multiplier (fleet_transfer_slope_q48)
* rather than selecting from a discrete config space. */
CDTuning cd_tuning_vm(void);
/* ============================================================================
* Generic UCB1 Config-Space Bandit
* ============================================================================
*
* Stratified by regime: each (regime, config) pair has its own benefit
* score and tick count, so the bandit learns "best config per regime"
* rather than one global best. One clock only: cd_config_table_tick()
* scores the currently-active config and reselects on every call -- no
* derived multi-tick batching unit exists anywhere in this mechanism.
* ============================================================================
*/
typedef struct {
uint8_t config_bits;
uint8_t has_prev; /* 1 once prev_window/prev_locality_q16 hold real data */
uint8_t _pad[2];
uint32_t benefit_score;
uint32_t tick_count;
uint32_t prev_window;
uint32_t prev_locality_q16;
} CDConfigEntry;
typedef struct {
CDConfigEntry *entries; /* num_configs entries, malloc'd */
uint32_t *regime_scores; /* num_regimes * num_configs, row-major [regime][config] */
uint32_t *regime_ticks; /* same shape as regime_scores */
uint32_t *total_regime_ticks; /* num_regimes entries */
uint32_t num_configs;
uint32_t num_regimes;
uint8_t current_config;
uint8_t current_regime;
} CDConfigTable;
/**
* @brief Allocate a config table sized for tuning->num_configs / num_regimes.
* @return 0 on success, -1 on malloc failure (table left zeroed, unusable)
*/
int cd_config_table_init(CDConfigTable *table, const CDTuning *tuning);
/**
* @brief Free all memory owned by a config table. Safe on a zeroed table.
*/
void cd_config_table_free(CDConfigTable *table);
/**
* @brief Seed every (regime, config) score with initial_scores[config], and
* set the starting active config to initial_config.
* @param initial_scores Array of tuning->num_configs initial benefit scores
*/
void cd_config_table_seed(CDConfigTable *table, const CDTuning *tuning,
const uint32_t *initial_scores, uint8_t initial_config);
/**
* @brief Score the currently-active config from this tick's outcome and
* UCB1-reselect this tick's config, for the given regime.
*
* Combined reward = joint_weight * window/locality-convergence-stability
* + anova_weight * anova_stability_q16
* Joint-convergence is skipped (falls back to ANOVA-only) on a config's
* first tick, or when use_joint_convergence is false (the caller's config
* bits don't include loops whose convergence this signal is meant to
* track -- word level's L5/L6, see ssm_jacquard.c).
*
* @param table Config table (must be initialised)
* @param tuning Tuning knobs matching this table's sizing
* @param regime Current regime index, < tuning->num_regimes
* @param anova_stability_q16 Caller-supplied stability signal, Q16 [0,65536]
* @param use_joint_convergence Whether to include the window/locality signal
* @param current_window Caller's current window-width metric
* @param current_locality_q16 Caller's current locality_q16 from cd_classify_ids
* @return The newly-selected config's config_bits
*/
uint8_t cd_config_table_tick(CDConfigTable *table, const CDTuning *tuning,
uint8_t regime, uint32_t anova_stability_q16,
int use_joint_convergence,
uint32_t current_window, uint32_t current_locality_q16);
/**
* @brief Force the table onto an externally-chosen config (as if UCB1 had
* picked it). One-tick nudge, not a hold -- see ssm_l8_force_config().
*/
void cd_config_table_force(CDConfigTable *table, uint8_t config_idx);
#endif /* COMPUDYNAMICS_H */
+106
View File
@@ -0,0 +1,106 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
*** StarForth ***
dictionary_heat_optimization.h - Phase 2: Heat-Aware Dictionary Lookup
Inference-driven optimizations for dictionary lookup based on execution heat
patterns from the Rolling Window of Truth:
1. Heat percentile thresholds (25th, 50th, 75th)
2. Bucket reorganization by co-execution patterns
3. Heat-aware lookup strategy (search hot words first)
*/
#ifndef DICTIONARY_HEAT_OPTIMIZATION_H
#define DICTIONARY_HEAT_OPTIMIZATION_H
#include "vm.h"
/**
* @brief Calculate heat percentile thresholds for all words in dictionary
*
* Computes 25th, 50th, 75th percentiles of execution_heat across all
* dictionary entries. Used to partition lookup search order.
*/
void dict_update_heat_percentiles(VM *vm);
/**
* @brief Reorganize dictionary buckets by inference-driven priorities
*
* Uses rolling window co-execution patterns to reorder words within buckets.
* Hot/frequently co-executing words are placed first for faster lookup.
*/
void dict_reorganize_buckets_by_heat(VM *vm);
/**
* @brief Find word with heat-aware search strategy
*
* Instead of newest-first, searches buckets in order:
* 1. Top 25% by heat (likely hits)
* 2. Middle 50% by heat
* 3. Bottom 25% (rarely executed)
*/
DictEntry *dict_find_word_heat_aware(VM *vm, const char *name, size_t len);
/**
* @brief Periodically reoptimize dictionary based on latest heat patterns
*
* Called every ~1 second or every N executions to adapt lookup strategy
* to current workload patterns from rolling window.
*/
void dict_adaptive_optimization_pass(VM *vm);
/* Frequency-class (fc) buckets — shared between dictionary_management.c and
dictionary_heat_optimization.c. Declared here once to give LTO a single
canonical type definition and avoid -Werror=lto-type-mismatch. */
#ifndef SF_FC_BUCKETS
#define SF_FC_BUCKETS 256
#endif
extern DictEntry **sf_fc_list[SF_FC_BUCKETS];
extern size_t sf_fc_count[SF_FC_BUCKETS];
extern size_t sf_fc_cap[SF_FC_BUCKETS];
#endif /* DICTIONARY_HEAT_OPTIMIZATION_H */
+88
View File
@@ -0,0 +1,88 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef DICTIONARY_MANAGEMENT_H
#define DICTIONARY_MANAGEMENT_H
#include "vm.h" /* For VM type and word_func_t */
/**
* @brief Initialize the Forth dictionary
*
* @param vm Pointer to the virtual machine instance
*
* Initializes all dictionary structures and prepares them for use.
* Must be called before any other dictionary operations.
*/
void dictionary_init(VM * vm);
/**
* @brief Register a new word in the dictionary
*
* @param vm Pointer to the virtual machine instance
* @param name Name of the word to register
* @param func Function pointer to the word's implementation
*
* Adds a new word to the dictionary with the specified name and implementation.
*/
void dictionary_register(VM *vm, const char *name, word_func_t func);
/**
* @brief Look up a word in the dictionary by name
*
* @param vm Pointer to the virtual machine instance
* @param name Name of the word to look up
* @return word_func_t Function pointer to the word's implementation or NULL if not found
*/
word_func_t dictionary_lookup(VM *vm, const char *name);
/**
* @brief Clear the entire dictionary
*
* @param vm Pointer to the virtual machine instance
*
* Removes all words from the dictionary and frees associated memory.
* This is an optional operation that may be used during cleanup.
*/
void dictionary_clear(VM * vm);
#endif
+254
View File
@@ -0,0 +1,254 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @file doe_metrics.h
* @brief Design of Experiments (DoE) metrics collection API
*
* Provides C-level programmatic access to VM metrics for physics-based experiments.
* Enables efficient in-process data collection without string parsing overhead.
*
* Usage:
* struct DoeMetrics metrics = metrics_from_vm(vm);
* metrics_write_csv_row(stdout, &metrics, run_number);
*/
#ifndef DOE_METRICS_H
#define DOE_METRICS_H
#include <stdio.h>
#include <stdint.h>
#include <time.h>
#include "vm.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Complete metrics snapshot for a single DoE run
*
* Represents all 35 metrics collected per test iteration, mirroring the CSV schema.
* All values extracted directly from VM structures (no string parsing).
*/
typedef struct {
/* === Metrics === */
uint32_t total_lookups; /* Total dictionary lookups during run */
/* === Cache Statistics === */
uint64_t cache_hits; /* Cache hits (if enabled) */
double cache_hit_percent; /* Hit percentage (0-100) */
uint64_t bucket_hits; /* Bucket hits after cache miss */
double bucket_hit_percent; /* Hit percentage (0-100) */
/* === Latency Metrics === */
int64_t cache_hit_latency_ns; /* Average cache hit latency (ns) */
int64_t cache_hit_stddev_ns; /* StdDev of cache hit latency */
int64_t bucket_search_latency_ns; /* Average bucket search latency (ns) */
int64_t bucket_search_stddev_ns; /* StdDev of bucket search latency */
/* === Pipelining Metrics (Loop #4) === */
uint64_t context_predictions_total; /* Total prefetch predictions */
uint64_t context_correct; /* Successful predictions */
double context_accuracy_percent; /* Accuracy percentage (0-100) */
uint64_t cache_promotions; /* Hotwords cache promotions */
uint64_t cache_demotions; /* Hotwords cache demotions */
/* === Rolling Window Metrics (Loop #2) === */
double window_diversity_percent; /* Window pattern diversity (0-100) */
uint32_t window_final_size_bytes; /* Final rolling window size */
uint32_t rolling_window_width; /* Width of rolling window (effective/adaptive) */
uint32_t actual_window_size; /* Ring buffer fill level: min(total_executions, ROLLING_WINDOW_SIZE) */
uint64_t total_executions; /* Words recorded in window */
uint64_t window_variance_q48; /* Pattern variance (Q48.16) */
/* === Heat Dynamics (Loop #1 & #3) === */
double decay_slope; /* Decay rate slope */
uint64_t total_heat; /* Aggregate execution heat */
uint64_t hot_word_count; /* Words above heat threshold */
uint64_t stale_word_count; /* Words with decaying heat */
double stale_word_ratio; /* Stale words / total words */
double avg_word_heat; /* Average execution heat */
/* === Heartbeat & Timing (Loop #7) === */
uint64_t tick_count; /* Total heartbeat ticks elapsed */
uint64_t tick_target_ns; /* Current tick interval (adaptive) */
uint64_t inference_run_count; /* Times inference engine invoked */
uint64_t early_exit_count; /* ANOVA early-exits (variance stable) */
/* === Window & Decay Inference (Loop #5 & #6) === */
double prefetch_accuracy_percent; /* Speculative prefetch success rate (0-100) */
uint64_t prefetch_attempts; /* Total speculative prefetch attempts */
uint64_t prefetch_hits; /* Successful prefetch hits */
uint64_t window_tuning_checks; /* Number of times window was tuned */
uint32_t final_effective_window_size; /* Final window size after tuning */
/* === Performance === */
int64_t vm_workload_duration_ns_q48; /* VM workload duration (Q48.16, nanoseconds) */
uint64_t total_runtime_ms; /* Total execution time (ms) */
uint64_t words_executed; /* Total word executions */
uint64_t dictionary_lookups; /* Dictionary search operations */
uint64_t memory_allocated_bytes; /* Total memory allocated */
double speedup_vs_baseline; /* Speedup ratio vs baseline */
/* === Statistical === */
double ci_lower_95; /* 95% confidence interval lower */
double ci_upper_95; /* 95% confidence interval upper */
/* === System State Deltas === */
int64_t cpu_temp_delta_c_q48; /* CPU temp change during run (Q48.16, °C) */
int64_t cpu_freq_delta_mhz_q48; /* CPU freq change during run (Q48.16, MHz) */
/* === Tuning Knobs === */
uint32_t decay_rate_q16; /* Decay rate (Q16 fixed-point) */
uint32_t decay_min_interval_ns; /* Min decay interval (ns) */
uint32_t rolling_window_size; /* Rolling window size */
uint32_t adaptive_shrink_rate; /* Shrink rate percentage */
uint32_t heat_cache_demotion_threshold; /* Demotion threshold */
/* === Loop Enable Flags (2^7 factorial) === */
int enable_loop_1_heat_tracking; /* Loop #1: Execution heat tracking */
int enable_loop_2_rolling_window; /* Loop #2: Rolling window history */
int enable_loop_3_linear_decay; /* Loop #3: Linear decay */
int enable_loop_4_pipelining; /* Loop #4: Pipelining metrics */
int enable_loop_5_window_inference; /* Loop #5: Window width inference */
int enable_loop_6_decay_inference; /* Loop #6: Decay slope inference */
int enable_loop_7_adaptive_heartrate; /* Loop #7: Adaptive heartrate */
/* === Legacy Configuration === */
int enable_hotwords_cache; /* 1 if cache enabled, 0 otherwise */
int enable_pipelining; /* 1 if pipelining enabled, 0 otherwise */
} DoeMetrics;
/**
* @brief Extract complete metrics snapshot from VM
*
* Collects all metrics from current VM state after a test harness run.
* Workload duration and CPU deltas must be supplied by caller.
*
* @param vm Pointer to Forth VM instance
* @param workload_duration_ns Actual VM workload execution time (nanoseconds)
* @param cpu_temp_delta_c CPU temperature change during run (°C)
* @param cpu_freq_delta_mhz CPU frequency change during run (MHz)
* @return Populated metrics structure (timestamp filled in)
*/
DoeMetrics metrics_from_vm(VM *vm, uint64_t workload_duration_ns,
int32_t cpu_temp_delta_c, int32_t cpu_freq_delta_mhz);
/**
* @brief Write CSV header row
*
* Writes the 35-column CSV header to output stream.
*
* @param out Output file stream
*/
void metrics_write_csv_header(FILE *out);
/**
* @brief Write metrics as CSV row
*
* Writes a single run's metrics as comma-separated values.
*
* @param out Output file stream
* @param metrics Metrics structure to write
*/
void metrics_write_csv_row(FILE *out, const DoeMetrics *metrics);
/**
* @brief Write reduced James Law CSV row (20 columns)
*
* Outputs only the subset of metrics needed for the James Law validation experiment.
* This matches the header format in run_window_sweep.sh (minus the 5 script-prepended columns).
*
* @param out Output file stream
* @param metrics Metrics structure to write
* @param vm VM instance (for heartbeat bucket aggregates and L8 mode)
*/
void metrics_write_james_law_csv_row(FILE *out, const DoeMetrics *metrics, VM *vm);
/**
* @brief Print metrics as human-readable text
*
* Outputs metrics in formatted text for debugging/verification.
*
* @param out Output file stream
* @param metrics Metrics structure to print
*/
void metrics_print_text(FILE *out, const DoeMetrics *metrics);
/**
* @brief Get current CPU temperature (Celsius)
*
* Reads /sys/class/thermal/thermal_zone0/temp on Linux,
* returns 0 if unavailable.
*
* @return Temperature in °C, or 0 if unable to read
*/
int32_t metrics_get_cpu_temp_c(void);
/**
* @brief Get current CPU frequency (MHz)
*
* Tries multiple sources:
* 1. /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
* 2. /proc/cpuinfo
* Returns 0 if unavailable.
*
* @return Frequency in MHz, or 0 if unable to read
*/
int32_t metrics_get_cpu_freq_mhz(void);
/**
* @brief Get current timestamp as ISO 8601 string
*
* Format: "YYYY-MM-DDTHH:MM:SS"
*
* @param buf Buffer to write timestamp (minimum 32 bytes)
* @param bufsize Size of buffer
*/
void metrics_get_timestamp(char *buf, size_t bufsize);
#ifdef __cplusplus
}
#endif
#endif /* DOE_METRICS_H */
+263
View File
@@ -0,0 +1,263 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef INFERENCE_ENGINE_H
#define INFERENCE_ENGINE_H
#include <stdint.h>
#include <stddef.h>
#include "vm.h" /* For RollingWindowOfTruth, VM structure */
#include "q48_16.h" /* For Q48.16 fixed-point math */
/* ============================================================================
* OpAmp Model: Inference Engine Architecture
* ============================================================================
*
* The inference engine unifies all VM adaptive tuning into a single loop:
*
* Rolling Window of Truth (execution_heat trajectory, metrics)
* ↓
* [Inference Engine]
* ├─ ANOVA Early-Exit: Check if variance stable
* ├─ Window Width Inference: Variance inflection detection
* ├─ Decay Slope Inference: Closed-form linear regression
* └─ Diagnostics: Fit quality, variance metrics
* ↓
* Tuning Outputs (adaptive_window_width, adaptive_decay_slope)
* ↓
* [Apply to VM]
* ↓
* [System Adapts]
* ↓
* [Feedback Loop Repeats]
*
* Key Design Principles:
* - Q48.16 math only (no floating-point)
* - ANOVA early-exit for efficiency (5% variance threshold)
* - Closed-form algorithms (deterministic, fast)
* - Observable diagnostics (RStudio dashboard feed)
*/
/* ============================================================================
* Input Data Structure: Metrics snapshot + window context
* ============================================================================
*/
typedef struct {
VM *vm; /* Owning VM (for dictionary heat snapshots) */
/* === Window Context === */
RollingWindowOfTruth *window; /* Pointer to window of truth */
uint64_t trajectory_length; /* Number of heat entries to analyze */
/* === Current Metrics Snapshot === */
uint64_t prefetch_hits; /* Successful prefetch predictions */
uint64_t prefetch_attempts; /* Total prefetch attempts */
uint64_t hot_word_count; /* Words above execution_heat threshold */
uint64_t stale_word_count; /* Words with execution_heat in low range */
uint64_t total_heat; /* Sum of all execution_heat values */
uint32_t word_count; /* Total dictionary entries */
/* === Baseline (For Trending) === */
uint64_t last_total_heat; /* Total heat from previous check */
uint64_t last_stale_count; /* Stale count from previous check */
} InferenceInputs;
/* ============================================================================
* Output Data Structure: Tuning parameters + diagnostics
* ============================================================================
*/
struct InferenceOutputs {
/* === Tuning Outputs (Apply to VM) === */
uint32_t adaptive_window_width; /* Inferred optimal window size (from variance inflection) */
uint64_t adaptive_decay_slope; /* Inferred decay slope in Q48.16 (from regression) */
/* === Diagnostics (RStudio Dashboard Feed) === */
uint64_t window_variance_q48; /* Variance of heat across window in Q48.16 */
uint64_t slope_fit_quality_q48; /* R² or residual metric in Q48.16 (0.0 to 1.0) */
/* === Status Tracking === */
uint32_t early_exited; /* 1 if ANOVA check skipped full inference, 0 if full run */
uint64_t last_check_tick; /* Timestamp of last full inference (for monitoring) */
};
/* ============================================================================
* Main API: Run Inference Engine
* ============================================================================
*
* Single entry point that handles:
* 1. ANOVA early-exit (check variance stability)
* 2. Full inference (if variance changed significantly)
* 3. Update tuning outputs
*
* Thread-Safety: Safe for synchronous calls from vm_tick()
* (No shared state written; outputs struct is caller-allocated)
*/
/**
* @brief Run adaptive inference engine on window data
*
* High-level flow:
* 1. ANOVA Check: Is variance delta < 5%?
* - YES: Return cached outputs immediately (cost: ~100 cycles)
* - NO: Continue to full inference
*
* 2. Extract trajectory: Fresh execution_heat snapshot from dictionary
*
* 3. Window Width Inference:
* - Compute variance across entire window
* - Detect inflection point (where d(variance)/d(size) → 0)
* - Return optimal window size
*
* 4. Decay Slope Inference:
* - Transform trajectory to log space (ln(heat[t]))
* - Fit linear regression: ln(heat) = a - slope*t
* - Extract slope coefficient in Q48.16
*
* 5. Diagnostics:
* - Compute fit quality (R² or residual)
* - Store variance metric for next early-exit check
*
* @param inputs Snapshot of window + metrics (caller-provided)
* @param outputs Tuning parameters + diagnostics (caller-provided, will be updated)
*
* @return void (results_run_01_2025_12_08 in outputs struct)
*
* Preconditions:
* - inputs != NULL
* - outputs != NULL
* - inputs->window != NULL
* - inputs->window->is_warm == 1 (window has enough data)
* - inputs->trajectory_length > 1
*
* Side Effects:
* - Modifies outputs struct (caller must preserve if caching)
* - Allocates temporary trajectory buffer (freed on return)
*/
void inference_engine_run(InferenceInputs *inputs, InferenceOutputs *outputs);
/* ============================================================================
* Configuration Constants (In vm.h, but referenced here)
* ============================================================================
*
* User-tunable via Makefile:
*
* #define HEARTBEAT_INFERENCE_FREQUENCY 5000
* - Ticks between full inference runs
* - Decreasing = faster response, more CPU
* - Increasing = slower response, less CPU
*
* #define VARIANCE_SIGNIFICANCE_THRESHOLD 5
* - Percentage (0-100) at which variance change triggers re-inference
* - Default: 5% (skip inference if variance changed < 5%)
* - Decreasing = more frequent re-inference (conservative)
* - Increasing = less frequent re-inference (aggressive)
*
* #define ADAPTIVE_MIN_WINDOW_SIZE 256
* - Minimum effective_window_size (never shrink below this)
* - Ensures minimum statistical validity
* - Increasing = higher overhead, more accuracy
*/
/* ============================================================================
* Helper Functions (For Testing / Diagnostics)
* ============================================================================
*/
/*
* @brief Pretty-print inference outputs for logging
*
* Format: "window_width=XXX variance_q48=YYYY slope_q48=ZZZZ fit_quality=WWWW"
*
* @param outputs Inference outputs struct
* @return Pointer to static string (valid until next call)
*/
const char* inference_outputs_to_string(const InferenceOutputs *outputs);
/*
* @brief Validate inference outputs for sanity
*
* Checks:
* - window_width is within [ADAPTIVE_MIN_WINDOW_SIZE, ROLLING_WINDOW_SIZE]
* - slope_q48 is reasonable (between 0.001 and 100 in Q48.16)
* - variance and fit_quality are between 0.0 and 1.0 in Q48.16
*
* @param outputs Inference outputs to validate
* @return 1 if valid, 0 if suspicious
*/
int inference_outputs_validate(const InferenceOutputs *outputs);
/* ============================================================================
* Internal Functions (Exposed for Unit Testing)
* ============================================================================
*/
/*
* @brief Compute variance of heat data in Q48.16 format
*
* @param heat_data Array of heat values
* @param length Number of elements in array
* @return Variance in Q48.16 format
*/
q48_16_t compute_variance_q48(const uint64_t *heat_data, uint64_t length);
/*
* @brief Find variance inflection point for optimal window width
*
* @param heat_data Array of heat values
* @param trajectory_length Total trajectory length
* @param full_variance Variance of full trajectory
* @return Optimal window width
*/
uint32_t find_variance_inflection(const uint64_t *heat_data, uint64_t trajectory_length, q48_16_t full_variance);
/*
* @brief Infer decay slope from heat trajectory using linear regression
*
* @param heat_data Array of heat values
* @param length Number of elements
* @return Decay slope in Q48.16 format
*/
uint64_t infer_decay_slope_q48(const uint64_t *heat_data, uint64_t length);
#endif /* INFERENCE_ENGINE_H */
+80
View File
@@ -0,0 +1,80 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef IO_H
#define IO_H
#include <stdint.h>
#include "vm.h"
/* BLOCK_SIZE is defined in vm.h */
/** @brief Total number of blocks available */
#define BLOCK_COUNT 1024
/** @brief Total memory size in bytes */
#define MEMORY_SIZE (BLOCK_SIZE * BLOCK_COUNT)
/*
* @brief Initialize the I/O subsystem
* @param vm Pointer to the virtual machine instance
*/
void io_init(VM * vm);
/**
* @brief Read a block from storage
* @param vm Pointer to the virtual machine instance
* @param block_num Block number to read
* @param buffer Buffer to store the read data
* @return 0 on success, non-zero on failure
*/
int io_read_block(VM *vm, int block_num, unsigned char *buffer);
/*
* @brief Write a block to storage
* @param vm Pointer to the virtual machine instance
* @param block_num Block number to write
* @param buffer Buffer containing data to write
* @return 0 on success, non-zero on failure
*/
int io_write_block(VM *vm, int block_num, const unsigned char *buffer);
#endif /* IO_H */
+120
View File
@@ -0,0 +1,120 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef LOG_H
#define LOG_H
/* Maximum log line size for persistent logging */
/* Renamed from LOG_LINE_MAX: vm.h owns that name for the persistent
* block-log line width (64, unrelated concept). This is the in-memory log
* message-formatting line length; keeping a distinct name removes the
* include-order collision that forced a fragile "vm.h before log.h"
* convention across the kernel (kernel_main.c/shim.c/capsule_birth.c). */
#ifndef LOG_MSG_LINE_MAX
#define LOG_MSG_LINE_MAX 256
#endif
/* Forward declaration */
struct VM;
/**
* @brief Logging level enumeration
*
* Defines the available logging levels in order of increasing verbosity.
*/
typedef enum {
LOG_NONE = -1, /**< Completely disable all logging */
LOG_ERROR = 0, /**< Error messages only */
LOG_WARN, /**< Warning and error messages */
LOG_INFO, /**< Informational, warning and error messages */
LOG_TEST, /**< Test results_run_01_2025_12_08 and all previous levels */
LOG_DEBUG /**< Debug messages and all previous levels */
} LogLevel;
/**
* @brief Test result type enumeration
*
* Defines the possible outcomes of test execution.
*/
typedef enum {
TEST_PASS = 0, /**< Test completed successfully */
TEST_FAIL, /**< Test failed */
TEST_SKIP, /**< Test was skipped */
TEST_ERROR /**< Test encountered an error during execution */
} TestResult;
/**
* @brief Set the global logging level
* @param level The new logging level to set
* @note Default level is LOG_INFO
*/
void log_set_level(LogLevel level);
/**
* @brief Get the current logging level
* @return The current logging level
*/
LogLevel log_get_level(void);
/**
* @brief Log a formatted message at the specified level
* @param level The logging level for this message
* @param fmt Printf-style format string
* @param ... Variable arguments for format string
*/
void log_message(LogLevel level, const char *fmt, ...);
/**
* @brief Log a test result with colored output
* @param word_name Name of the word being tested
* @param result Result of the test execution
*/
void log_test_result(const char *word_name, TestResult result);
/**
* @brief Set the VM instance for persistent logging
* @param vm VM instance to use for persistent logging (NULL to disable)
*/
void log_set_vm(struct VM *vm);
#endif /* LOG_H */
+91
View File
@@ -0,0 +1,91 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* math_portable.h
*
* Portable C99 math functions in Q48.16 64-bit fixed-point format.
* Suitable for embedded environments with no libm
* (HISTORICAL: also L4Re, through mid-2026; removed as an active target).
* All arithmetic is integer-only for maximum performance.
*
* Q48.16 Format:
* - 48-bit signed integer + 16-bit fractional part
* - Precision: 2^-16 ≈ 0.0000153 (nanoseconds)
* - Range: ±140 trillion ns (≈ 4.4 years)
*
* Functions:
* sqrt_q48(x) - Square root of Q48.16 value via Newton-Raphson
* erf_q48(x) - Error function of Q48.16 value (dimensionless)
*/
#ifndef MATH_PORTABLE_H
#define MATH_PORTABLE_H
#include <stdint.h>
#define Q48_SCALE (1LL << 16) /* 65536 */
/**
* Square root in Q48.16 fixed-point
* Input: x in Q48.16 format
* Output: sqrt(x) in Q48.16 format
* Uses Newton-Raphson: x_{n+1} = (x_n + a/x_n) / 2
* Converges in ~10-15 iterations
* Returns 0 for negative inputs (guard against domain error)
*/
int64_t sqrt_q48(int64_t x);
/**
* Error function in Q48.16 fixed-point
* Input: x in Q48.16 format (dimensionless Z-score)
* Output: erf(x) in Q48.16 format (range: -Q48_SCALE to +Q48_SCALE)
* Uses Abramowitz & Stegun approximation (7.1.26)
* Maximum error: 1.5e-7 relative to floating-point erf
* Properties:
* - erf(0) = 0
* - erf(-x) = -erf(x) (odd function)
* - erf(x) approaches ±Q48_SCALE for large |x|
*/
int64_t erf_q48(int64_t x);
#endif /* MATH_PORTABLE_H */
+68
View File
@@ -0,0 +1,68 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @file memory_management.h
* @brief Memory management functions for the StarForth virtual machine
*
* This file contains functions for managing memory allocation and alignment
* in the StarForth virtual machine implementation.
*/
#ifndef VM_MEMORY_H
#define VM_MEMORY_H
#include "vm.h"
/*
* @brief Allocates memory in the virtual machine's memory space
* @param vm Pointer to the virtual machine instance
* @param bytes Number of bytes to allocate
* @return Pointer to the allocated memory or NULL if allocation fails
*/
void *vm_allot(VM *vm, size_t bytes);
/*
* @brief Aligns the virtual machine's memory pointer to the next word boundary
* @param vm Pointer to the virtual machine instance
*/
void vm_align(VM * vm);
#endif
+92
View File
@@ -0,0 +1,92 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef STARFORTH_PHYSICS_EXECUTION_HOOKS_H
#define STARFORTH_PHYSICS_EXECUTION_HOOKS_H
/*
* physics_execution_hooks.h - Clean abstraction for physics-driven execution hooks
*
* Purpose: Hide ALL physics machinery behind two simple function calls.
* The VM execution loop calls these; it doesn't know or care about:
* - Heat tracking
* - Decay models
* - Rolling window
* - Pipelining metrics
* - Hotwords cache
* - Profiling
*
* This keeps vm.c clean and focused on execution semantics.
*/
#include "vm.h"
/*
* Call BEFORE executing a word.
* Handles: decay, heat increment, rolling window, pipelining speculation check.
*
* @param vm The VM instance
* @param word The word about to execute
* @param prev The previously executed word (NULL if first in sequence)
*/
void physics_pre_execute(VM* vm, DictEntry* word, DictEntry* prev);
/*
* Call AFTER executing a word.
* Handles: physics touch, profiler exit, DoE counter, heartbeat check.
*
* @param vm The VM instance
* @param word The word that just executed
*/
void physics_post_execute(VM* vm, DictEntry* word);
/*
* Call when looking up a word (outer interpreter).
* Handles: decay + heat for lookup path (not inner loop).
*
* @param vm The VM instance
* @param entry The word found
* @param canon Canonical dictionary entry (may differ from entry)
*/
void physics_on_lookup(VM* vm, DictEntry* entry, DictEntry* canon);
#endif /* STARFORTH_PHYSICS_EXECUTION_HOOKS_H */
+314
View File
@@ -0,0 +1,314 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* physics_hotwords_cache.h
*
* Hot-words cache optimization: frequency-driven dictionary acceleration
*
* Compile-time switch to enable/disable the hot-words cache variant.
* Allows measuring impact before/after on different build profiles.
*
* Usage:
* make clean && make ENABLE_HOTWORDS_CACHE=1 # With cache
* make clean && make ENABLE_HOTWORDS_CACHE=0 # Without cache
*
* Measure performance:
* BENCH-DICT-LOOKUP (show baseline + stats)
* PHYSICS-SHOW-CACHE-STATS (show hit rate, evictions, etc)
*/
#ifndef PHYSICS_HOTWORDS_CACHE_H
#define PHYSICS_HOTWORDS_CACHE_H
#include <stdint.h>
#include "vm.h"
/* ============================================================================
* Feature Switch
* ============================================================================
*
* Set via -DENABLE_HOTWORDS_CACHE=1 during compilation
* Default: enabled (can be disabled for baseline comparison)
*/
#ifndef ENABLE_HOTWORDS_CACHE
#define ENABLE_HOTWORDS_CACHE 1
#endif
/* ============================================================================
* Hot Words Cache Configuration
* ============================================================================
*/
/** Maximum number of hot words to cache (typically 16-32) */
#define HOTWORDS_CACHE_SIZE 32
/** Execution heat threshold to consider a word "hot" (execution count) */
#define HOTWORDS_EXECUTION_HEAT_THRESHOLD 50
/** Reorder bucket when execution heat delta exceeds this (avoid thrashing) */
#define HOTWORDS_EXECUTION_HEAT_DELTA_THRESHOLD 100
/* ============================================================================
* Statistics Structure
* ============================================================================
*
* Tracks cache performance for measurement and diagnostics
*/
typedef struct {
/** Total lookups performed */
uint64_t total_lookups;
/** Lookups that hit the hot-words cache */
uint64_t cache_hits;
/** Lookups that missed cache but found in bucket */
uint64_t bucket_hits;
/** Lookups that failed (word not found) */
uint64_t misses;
/** Words evicted from cache (LRU) */
uint64_t evictions;
/** Times a word was promoted to hot-words */
uint64_t promotions;
/** Times bucket was reordered */
uint64_t bucket_reorders;
/** === 64-bit Fixed-Point Statistics (Q48.16 format) ===
* Q48.16 = 48-bit integer + 16-bit fractional part
* Precision: 2^-16 ns ≈ 0.0000153 ns
* Range: ±140 trillion ns (≈ 4.4 years)
* Allows: Bayesian inference, precise averaging, statistical computation
*/
/** Sum of cache hit latencies (64-bit fixed-point, Q48.16) */
int64_t cache_hit_total_ns_q48;
/** Sum of bucket search latencies (64-bit fixed-point, Q48.16) */
int64_t bucket_search_total_ns_q48;
/** Number of cache hit samples (for averaging) */
uint64_t cache_hit_samples;
/** Number of bucket search samples (for averaging) */
uint64_t bucket_search_samples;
/** Minimum cache hit latency (nanoseconds) */
int64_t min_cache_hit_ns;
/** Maximum cache hit latency (nanoseconds) */
int64_t max_cache_hit_ns;
/** Minimum bucket search latency (nanoseconds) */
int64_t min_bucket_search_ns;
/** Maximum bucket search latency (nanoseconds) */
int64_t max_bucket_search_ns;
/** Sum of squared cache hit latencies (for variance/stddev) */
int64_t cache_hit_variance_sum_q48;
/** Sum of squared bucket search latencies (for variance/stddev) */
int64_t bucket_search_variance_sum_q48;
} HotwordsStats;
/* ============================================================================
* Hot Words Cache State
* ============================================================================
*
* Embedded in VM for global state
*/
typedef struct HotwordsCache_s {
/** Cache entries (pointers to DictEntry) */
DictEntry *cache[HOTWORDS_CACHE_SIZE];
/** Number of valid entries in cache */
uint32_t cache_count;
/** LRU: last-used index for eviction */
uint32_t lru_index;
/** Performance statistics */
HotwordsStats stats;
/** Enabled/disabled flag (runtime toggle) */
int enabled;
} HotwordsCache;
/* ============================================================================
* Public API
* ============================================================================
*/
/**
* Initialize hot-words cache
* Should be called during VM initialization
*/
void hotwords_cache_init(HotwordsCache *cache);
/**
* Cleanup hot-words cache
*/
void hotwords_cache_cleanup(HotwordsCache *cache);
/**
* Lookup word in cache first, then fall back to bucket search
* Returns the found word, or NULL if not found
*
* This is the main function that replaces vm_find_word() logic.
* Takes the VM so the bucket fallback can resolve same-named entries by
* definition age (vm_dict_resolve_in_bucket) — bucket order alone is not
* authoritative once dict_reorganize_buckets_by_heat() has run.
*/
DictEntry *hotwords_cache_lookup(VM *vm,
HotwordsCache *cache,
DictEntry **bucket,
size_t bucket_count,
const char *name,
size_t len);
/**
* Promote a word to the hot-words cache
* Called when a word's execution heat exceeds threshold
*/
void hotwords_cache_promote(HotwordsCache *cache, DictEntry *word);
/**
* Evict every cache slot holding this exact entry.
* Must be called before an entry is freed (FORGET) — a stale pointer in the
* ring is a use-after-free.
*/
void hotwords_cache_evict_entry(HotwordsCache *cache, const DictEntry *word);
/**
* Evict every cache slot whose entry matches this name.
* Called whenever name resolution may change: word creation (shadowing),
* smudge/unsmudge, hide. The next lookup re-resolves through the bucket.
*/
void hotwords_cache_evict_name(HotwordsCache *cache, const char *name, size_t len);
/**
* Reorder a bucket by execution heat (move hot words forward)
* Called when execution heat delta exceeds threshold
*/
void hotwords_bucket_reorder(DictEntry **bucket, size_t count);
/**
* Reset statistics (for before/after comparison)
*/
void hotwords_stats_reset(HotwordsStats *stats);
/**
* Print statistics to stdout
*/
void hotwords_stats_print(const HotwordsStats *stats);
/**
* Enable/disable cache at runtime (for A/B testing)
*/
void hotwords_cache_set_enabled(HotwordsCache *cache, int enabled);
/* ============================================================================
* Bayesian Inference Statistics
* ============================================================================
*/
/**
* Bayesian statistics structure for inference and credible intervals
* All values stored in Q48.16 fixed-point (64-bit signed integer)
* representing nanoseconds with 2^-16 ≈ 0.0000153 ns precision
*/
typedef struct {
int64_t mean_ns_q48; /* Mean latency (Q48.16) */
int64_t stddev_ns_q48; /* Standard deviation (Q48.16) */
int64_t median_ns_q48; /* Median (p50) (Q48.16) */
int64_t credible_lower_95; /* Lower bound of 95% credible interval (Q48.16) */
int64_t credible_upper_95; /* Upper bound of 95% credible interval (Q48.16) */
int64_t credible_lower_99; /* Lower bound of 99% credible interval (Q48.16) */
int64_t credible_upper_99; /* Upper bound of 99% credible interval (Q48.16) */
uint64_t sample_count; /* Number of samples in posterior */
} BayesianLatencyPosterior;
/**
* Calculate posterior distribution for cache hits
* Uses accumulated samples to compute credible intervals
*/
BayesianLatencyPosterior hotwords_posterior_cache_hits(const HotwordsStats *stats);
/**
* Calculate posterior distribution for bucket searches
* Uses accumulated samples to compute credible intervals
*/
BayesianLatencyPosterior hotwords_posterior_bucket_searches(const HotwordsStats *stats);
/**
* Calculate speedup estimate with credible interval
* All values in Q48.16 fixed-point format (64-bit signed integer)
* Probabilities are in range [0, Q48_SCALE] representing [0%, 100%]
*/
struct SpeedupEstimate {
int64_t speedup_factor_q48; /* Bucket latency / Cache latency (Q48.16) */
int64_t credible_lower_95_q48; /* Lower bound (95% credible) (Q48.16) */
int64_t credible_upper_95_q48; /* Upper bound (95% credible) (Q48.16) */
int64_t probability_gt_10pct_q48; /* P(speedup > 1.1) in Q48.16, [0, 65536] = [0%, 100%] */
int64_t probability_gt_double_q48;/* P(speedup > 2.0) in Q48.16, [0, 65536] = [0%, 100%] */
};
/**
* Calculate speedup estimate with uncertainty
*/
struct SpeedupEstimate hotwords_speedup_estimate(const HotwordsStats *stats);
/**
* Generate detailed Bayesian inference report
* Compares cache vs bucket performance with confidence measures
*/
void hotwords_bayesian_report(const HotwordsStats *stats_with_cache,
const HotwordsStats *stats_without_cache);
#endif /* PHYSICS_HOTWORDS_CACHE_H */
+111
View File
@@ -0,0 +1,111 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
*** StarForth ***
physics_metadata.h - Physics metadata helpers for dictionary entries
Phase 1 implements lightweight thermodynamic signals that inform scheduling
and storage placement while keeping the runtime overhead minimal.
*/
#ifndef STARFORTH_PHYSICS_METADATA_H
#define STARFORTH_PHYSICS_METADATA_H
#include <stdint.h>
#include "vm.h"
/* L8 FINAL INTEGRATION: L1 heat tracking always-on */
/* ============================================================================
* INTENT: Atomic heat increment for FL1 feedback loop
* FL1: Heat accumulation - called on every word execution by inner interpreter
* WHY: Atomic operations prevent race conditions when multiple execution paths
* (e.g., heartbeat thread + main interpreter) touch execution_heat
* RELAXED memory order is sufficient (no ordering dependencies)
* ============================================================================ */
static inline void physics_execution_heat_increment(DictEntry *entry)
{
if (!entry) return;
#if defined(__GNUC__)
__atomic_fetch_add(&entry->execution_heat, 1, __ATOMIC_RELAXED);
#else
entry->execution_heat++;
#endif
}
static inline cell_t physics_execution_heat_load(const DictEntry *entry)
{
if (!entry) return 0;
#if defined(__GNUC__)
return __atomic_load_n(&entry->execution_heat, __ATOMIC_RELAXED);
#else
return entry->execution_heat;
#endif
}
static inline uint64_t physics_decay_slope_load(const VM *vm)
{
#if defined(__GNUC__)
return __atomic_load_n(&vm->decay_slope_q48, __ATOMIC_RELAXED);
#else
return vm->decay_slope_q48;
#endif
}
void physics_metadata_init(DictEntry *entry, uint32_t header_bytes);
void physics_metadata_set_mass(DictEntry *entry, uint32_t total_bytes);
void physics_metadata_touch(DictEntry *entry, cell_t execution_heat, uint64_t now_ns);
void physics_metadata_refresh_state(DictEntry * entry);
void physics_metadata_record_latency(DictEntry *entry, uint64_t sample_ns);
void physics_metadata_apply_seed(DictEntry * entry);
/* L8 FINAL INTEGRATION: L3 linear decay always-on */
void physics_metadata_apply_linear_decay(DictEntry *entry, uint64_t elapsed_ticks, VM *vm);
#endif /* STARFORTH_PHYSICS_METADATA_H */
+430
View File
@@ -0,0 +1,430 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
*** StarForth ***
physics_pipelining_metrics.h - Word transition tracking for speculative execution
Phase 1: Instrumentation for pipelining/speculative execution
This module tracks word-to-word transitions (frequency of which words follow
which other words) to support speculative prefetching and pipelining decisions.
Terminology:
- transition_heat[i]: How many times word[i] has been executed immediately
after this word (forms prediction basis)
- total_transitions: Sum of all transition_heat entries
- prefetch_*: Metrics for speculative execution success/failure
*/
#ifndef PHYSICS_PIPELINING_METRICS_H
#define PHYSICS_PIPELINING_METRICS_H
#include <stdint.h>
#include "starforth_config.h"
/* ============================================================================
* Pipelining Feature Switch (disabled by default during Phase 1)
* ============================================================================
* Default lives in starforth_config.h, the single source of truth for VM
* build-time defaults; overridable via -D exactly as before.
*/
/* ============================================================================
* Tuning Knobs (adjustable at compile-time, future: runtime)
* ============================================================================
*/
/**
* SPECULATION_THRESHOLD_Q48: Minimum probability (Q48.16) to speculate on
* a word transition.
* Range: 0.10 (very aggressive) to 0.95 (very conservative)
* Default: see starforth_config.h (STARFORTH_CONFIG_SPECULATION_THRESHOLD_Q48_DEFAULT)
* Q48.16 encoding: 0.50 = 0x8000LL (1LL << 15)
*/
/**
* SPECULATION_DEPTH: How many words ahead to prefetch speculatively.
* Range: 1 (next word only) to 4 (very aggressive)
* Default: see starforth_config.h (STARFORTH_CONFIG_SPECULATION_DEPTH_DEFAULT)
*/
/**
* MIN_SAMPLES_FOR_SPECULATION: Minimum number of transitions observed
* before making speculation decisions.
* Range: 1 (immediate) to 100 (very conservative)
* Default: see starforth_config.h (STARFORTH_CONFIG_MIN_SAMPLES_FOR_SPECULATION_DEFAULT)
*/
/**
* MISPREDICTION_COST_Q48: Estimated cost (in nanoseconds, Q48.16 format)
* of recovering from wrong speculation.
* This is calibrated to the actual recovery time on target hardware.
* Range: 0 to 100 ns
* Default: see starforth_config.h (STARFORTH_CONFIG_MISPREDICTION_COST_Q48_DEFAULT)
* Q48.16 encoding: 25 ns = 25 << 16 = 0x190000LL
*/
/**
* MINIMUM_PREFETCH_ROI: Minimum expected Return-On-Investment ratio
* for speculation to be worthwhile.
* Speculation succeeds only if:
* (prefetch_latency_saved / total_attempts) > MINIMUM_PREFETCH_ROI
* Range: 1.0 (always speculate) to 2.0 (very selective)
* Default: see starforth_config.h (STARFORTH_CONFIG_MINIMUM_PREFETCH_ROI_DEFAULT)
* Q48.16 encoding: 1.10 = 1.10 * (1 << 16) = 0x1199AL
*/
/**
* TRANSITION_WINDOW_SIZE: Knob #6 - Execution context depth for prediction
*
* How many previous words to remember when predicting next word.
* Larger window captures deeper patterns but uses more memory.
*
* Window=1: Only immediate predecessor (A→B predictions)
* Window=2: Previous 2 words (A,B→C predictions) - good balance
* Window=4: Previous 4 words (captures multi-level patterns)
* Window=8: Very deep, expensive -- see starforth_config.h for the actual
* shipped default
*
* Binary Chop Search Space (Phase 2):
* Iteration 1: Try window=2 (baseline)
* Iteration 2: Try window=1 and window=4
* Iteration 3: Narrow to optimal (1, 2, or 4)
* Converge: Pick size with highest prediction accuracy
*
* Range: 1 to 8
* Default: see starforth_config.h (STARFORTH_CONFIG_TRANSITION_WINDOW_SIZE_DEFAULT)
* Trade-off: Pattern depth vs. memory usage & complexity
*
* Tuning: make TRANSITION_WINDOW_SIZE=1 (or 2, 4, 8)
*/
/* ============================================================================
* Word Transition Metrics
* ============================================================================
*
* Allocated per dictionary entry to track which words follow this word.
* This is the basis for speculative prefetching decisions.
*/
typedef struct WordTransitionMetrics {
/**
* transition_heat[i]: Number of times word with index i has been
* executed immediately after this word.
*
* Dynamically allocated array of size DICTIONARY_SIZE.
* Initialized to NULL; allocated on first use to save memory
* for words that are never executed.
*/
uint64_t *transition_heat;
/** Total number of transitions observed from this word */
uint64_t total_transitions;
/** Number of speculative prefetch attempts made for this word */
uint64_t prefetch_attempts;
/** Number of successful prefetch predictions (hits) */
uint64_t prefetch_hits;
/** Number of failed prefetch predictions (misses) */
uint64_t prefetch_misses;
/**
* Total latency saved through successful prefetching (Q48.16 nanoseconds).
* Positive value indicates net benefit from speculation.
*/
int64_t prefetch_latency_saved_q48;
/**
* Total latency cost from failed prefetch predictions (Q48.16 nanoseconds).
* Negative value (cost) to be subtracted from savings.
*/
int64_t misprediction_cost_q48;
/**
* Cached probability of most likely next word (for quick access).
* Updated periodically to avoid recomputation.
* Q48.16 format (0.0 to 1.0 == 0LL to 1LL<<16)
*/
int64_t max_transition_probability_q48;
/**
* Index (word id) of most likely next word.
* Used for fast prefetch without scanning transition_heat array.
*/
uint32_t most_likely_next_word_id;
/* ========== Context-Aware Transitions (Phase 1 Extension) ========== */
/**
* Circular buffer of previous word IDs for context tracking.
* Size = TRANSITION_WINDOW_SIZE (default 2)
* Used to build multi-word context patterns (e.g., A,B→C)
*
* Example with WINDOW=2:
* context_window[0] = older word (2 steps back)
* context_window[1] = recent word (1 step back)
* Next word completes the pattern: (context_window[0], context_window[1]) → next
*/
uint32_t *context_window;
/**
* Current position in context_window circular buffer.
* Points to slot where next word ID will be stored.
*/
uint32_t context_window_pos;
/**
* Sparse hash table for context-based transitions.
* Maps: hash(context_window) → counts_array[DICTIONARY_SIZE]
*
* Phase 1: Collect only (simple reference implementation)
* Phase 2: Analyze prediction accuracy by context depth
* Phase 3: Use for adaptive prefetching decisions
*
* Opaque pointer - implementation is in transition_metrics.c
*/
void *context_transitions;
/**
* Total context-based transitions observed.
* Counter for Phase 2 analysis (accuracy measurement)
*/
uint64_t total_context_transitions;
/**
* Actual window size for this word's metrics.
* May differ from TRANSITION_WINDOW_SIZE if memory allocation failed.
* Phase 2 uses this to validate context data reliability.
*/
uint32_t actual_window_size;
} WordTransitionMetrics;
/* ============================================================================
* Transition Metrics API
* ============================================================================
*/
/*
* Initialize word transition metrics for a dictionary entry.
* Called once per word during dictionary entry creation.
*
* @param metrics Pointer to uninitialized WordTransitionMetrics
* @return 0 on success, -1 on memory allocation failure
*/
int transition_metrics_init(WordTransitionMetrics *metrics);
/*
* Record a word transition (word_from -> word_to).
* Called in the inner interpreter after each word execution.
*
* @param metrics Metrics for the current word
* @param next_word_id Index of word that executed next
* @param dict_size Total number of words in dictionary (for array bounds)
* @return 0 on success, -1 on invalid word_id
*/
int transition_metrics_record(WordTransitionMetrics *metrics, uint32_t next_word_id, uint32_t dict_size);
/*
* Get the probability (Q48.16 format) of a specific word following this word.
*
* Returns: (transition_heat[target] / total_transitions) in Q48.16 format
* or 0 if total_transitions == 0
*
* @param metrics Metrics for the current word
* @param target_word_id Index of potential next word
* @return Probability in Q48.16 format (0.0 to 1.0)
*/
int64_t transition_metrics_get_probability_q48(const WordTransitionMetrics *metrics, uint32_t target_word_id);
/*
* Update cached most-likely-next-word information.
* Should be called periodically (e.g., after every 100 transitions)
* to avoid stale cache.
*
* @param metrics Metrics for the current word
* @param dict_size Total number of words in dictionary
*/
void transition_metrics_update_cache(WordTransitionMetrics *metrics, uint32_t dict_size);
/*
* Record a successful prefetch prediction.
* Called when speculation correctly predicted the next word.
*
* @param metrics Metrics for the current word
* @param latency_saved_ns Nanoseconds saved (will be Q48.16 encoded)
*/
void transition_metrics_record_prefetch_hit(WordTransitionMetrics *metrics, int64_t latency_saved_ns);
/*
* Record a failed prefetch prediction.
* Called when speculation incorrectly predicted the next word.
*
* @param metrics Metrics for the current word
* @param recovery_cost_ns Nanoseconds lost to misprediction recovery
*/
void transition_metrics_record_prefetch_miss(WordTransitionMetrics *metrics, int64_t recovery_cost_ns);
/*
* Check if speculation should be attempted for a given target word.
* Applies threshold logic and ROI analysis.
*
* Returns: 1 if speculation should be attempted, 0 otherwise
*
* @param metrics Metrics for the current word
* @param target_word_id Index of potential next word
* @return 1 if probability > SPECULATION_THRESHOLD and enough samples, 0 otherwise
*/
int transition_metrics_should_speculate(const WordTransitionMetrics *metrics, uint32_t target_word_id);
/*
* Get detailed statistics string for diagnostics.
* Caller must free the returned string.
*
* @param metrics Metrics to report on
* @return Formatted statistics string (must be freed by caller)
*/
char *transition_metrics_stats_string(const WordTransitionMetrics *metrics);
/*
* Reset all transition metrics (clear counts, free transition_heat array).
* Called on VM reset or explicit metrics reset.
*
* @param metrics Metrics to reset
*/
void transition_metrics_reset(WordTransitionMetrics *metrics);
/*
* Free all allocated memory for transition metrics.
* Called during dictionary cleanup.
*
* @param metrics Metrics to clean up
*/
void transition_metrics_cleanup(WordTransitionMetrics *metrics);
/* ============================================================================
* Context-Aware Transition API (Phase 1 Extension)
* ============================================================================
*
* These functions handle multi-word pattern tracking for Phase 2 analysis.
* Phase 1: Collect context window data (this session)
* Phase 2: Analyze accuracy by context depth (next session)
* Phase 3: Use for adaptive prediction (future)
*/
/*
* Record a context-based transition (multi-word pattern).
* Called after each word execution with the execution window.
*
* Example with WINDOW=2:
* context[0] = word_A, context[1] = word_B, next_word = word_C
* Records: (A,B) → C transition in sparse hash table
*
* Phase 1: Just collects data, no analysis
* Phase 2: Will measure: "Do patterns with window=2 predict better than window=1?"
*
* @param metrics Metrics for the current word
* @param context Array of word IDs forming the context (size = window_size)
* @param window_size Length of context array
* @param next_word_id Word that follows the context
* @param dict_size Total number of words in dictionary
* @return 0 on success, -1 on error
*/
int transition_metrics_record_context(WordTransitionMetrics *metrics,
const uint32_t *context,
uint32_t window_size,
uint32_t next_word_id,
uint32_t dict_size);
/*
* Update the execution context window with a new word.
* Called after each word execution to slide the window forward.
*
* Example with WINDOW=2:
* Before: [A, B] (two previous words)
* After calling with C: [B, C] (shift left, add C on right)
*
* @param metrics Metrics to update
* @param word_id New word ID to add to window
* @return 0 on success, -1 if context tracking not initialized
*/
int transition_metrics_update_context_window(WordTransitionMetrics *metrics, uint32_t word_id);
/*
* Get context-based prediction accuracy statistics (Phase 2).
* Compares prediction success rates across different window sizes.
*
* Phase 2 will use this to run binary chop tuning:
* Measure accuracy(window=1), accuracy(window=2), accuracy(window=4)
* Binary search converges to optimal window size
*
* @param metrics Metrics to analyze
* @return Formatted string with accuracy statistics (caller must free)
*/
char *transition_metrics_context_accuracy_string(const WordTransitionMetrics *metrics);
/*
* Get recommended window size based on collected data.
* Phase 2 will call this as part of binary chop tuning algorithm.
*
* Algorithm:
* 1. Collect data with current window size
* 2. Measure prediction accuracy
* 3. If accuracy improves, try larger window
* 4. If accuracy declines, try smaller window
* 5. Converge to optimal size
*
* @param metrics Metrics to analyze
* @param current_window Current window size being tested
* @param accuracy_at_current Measured accuracy with current window
* @return Suggested next window size to try (1, 2, 4, or 8)
*/
uint32_t transition_metrics_binary_chop_suggest_window(const WordTransitionMetrics *metrics,
uint32_t current_window,
double accuracy_at_current);
#endif /* PHYSICS_PIPELINING_METRICS_H */
+215
View File
@@ -0,0 +1,215 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
*** StarForth ***
physics_runtime.h - Phase 1 physics runtime services
Provides host snapshot shims (POSIX + L4Re) and analytics heap scaffolding
for physics-aware scheduling. The interface is intentionally lightweight so
future governance-approved modules can extend it without ABI churn.
============================================================================
IMPORTANT SYNC NOTE (projects using git hooks / governance mirror):
• If you change HOLA protocol fields, magic, or version numbers below, also
update the governance export docs so the L4Re mirror stays aligned.
• Daemons touching this header can throw a warning when HOLA_PROTOCOL_* or
HOLA_SHARED_MAGIC change to catch accidental ABI drifts early.
• Reference: docs/src/internal/GOVERNANCE_EXPORT_NOTES.adoc
============================================================================
*/
#ifndef STARFORTH_PHYSICS_RUNTIME_H
#define STARFORTH_PHYSICS_RUNTIME_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Default analytics heap size (10 MiB) */
#define PHYSICS_ANALYTICS_DEFAULT_HEAP_BYTES (10u * 1024u * 1024u)
/** Magic tag for HOLA analytics heap headers ('HOLA') */
#define HOLA_SHARED_MAGIC 0x484f4c41u
/** HOLA protocol version (major.minor) */
#define HOLA_PROTOCOL_MAJOR 0x0001u
#define HOLA_PROTOCOL_MINOR 0x0000u
/** Host backend identifier */
typedef enum physics_host_backend {
PHYSICS_HOST_BACKEND_POSIX = 0,
PHYSICS_HOST_BACKEND_L4RE = 1, /* HISTORICAL: no backend sets this anymore; L4Re removed as an active target. Value kept stable for the HOLA analytics data format. */
PHYSICS_HOST_BACKEND_FAKE = 2
} physics_host_backend_t;
/** Analytics channel ids */
#define PHYSICS_ANALYTICS_CHANNEL_HOST_SNAPSHOT 0x00000002u
/** Host snapshot flag bits */
#define PHYSICS_HOST_FLAG_PSI 0x0001u
#define PHYSICS_HOST_FLAG_CGROUP 0x0002u
#define PHYSICS_HOST_FLAG_CPU_STAT 0x0004u
/**
* @brief Snapshot of scheduler-adjacent host signals.
*
* Fields are intentionally fixed width to maximise portability and allow the
* governance repository to consume binary dumps without per-platform quirks.
*/
typedef struct physics_host_snapshot {
physics_host_backend_t backend; /**< Selected backend (POSIX/L4Re/other) */
uint32_t scheduler_policy; /**< OS-specific scheduler policy identifier */
uint32_t scheduler_priority; /**< Nominal thread priority */
uint32_t scheduler_quantum_ns; /**< Reported quantum in nanoseconds (0 if unknown) */
uint32_t runnable_threads; /**< Runnable entities on this CPU set (best effort) */
uint32_t cpu_count; /**< Online logical CPU count */
uint32_t load_avg_milli; /**< Smoothed load average (×1000) */
uint32_t flags; /**< Backend-specific flags */
uint32_t psi_cpu_some_avg10_milli; /**< CPU PSI some avg10 ×1000 */
uint32_t psi_cpu_some_avg60_milli; /**< CPU PSI some avg60 ×1000 */
uint32_t psi_cpu_some_avg300_milli; /**< CPU PSI some avg300 ×1000 */
uint32_t psi_cpu_full_avg10_milli; /**< CPU PSI full avg10 ×1000 */
uint32_t psi_cpu_full_avg60_milli; /**< CPU PSI full avg60 ×1000 */
uint32_t psi_cpu_full_avg300_milli; /**< CPU PSI full avg300 ×1000 */
uint32_t psi_io_some_avg10_milli; /**< IO PSI some avg10 ×1000 */
uint32_t psi_io_some_avg60_milli; /**< IO PSI some avg60 ×1000 */
uint32_t psi_io_some_avg300_milli; /**< IO PSI some avg300 ×1000 */
uint32_t psi_io_full_avg10_milli; /**< IO PSI full avg10 ×1000 */
uint32_t psi_io_full_avg60_milli; /**< IO PSI full avg60 ×1000 */
uint32_t psi_io_full_avg300_milli; /**< IO PSI full avg300 ×1000 */
uint32_t psi_mem_some_avg10_milli; /**< Memory PSI some avg10 ×1000 */
uint32_t psi_mem_some_avg60_milli; /**< Memory PSI some avg60 ×1000 */
uint32_t psi_mem_some_avg300_milli; /**< Memory PSI some avg300 ×1000 */
uint64_t monotonic_time_ns; /**< Monotonic clock reading */
uint64_t realtime_ns; /**< Wall-clock timestamp (0 if unavailable) */
uint64_t backend_seq; /**< Backend-provided sequence counter */
uint64_t cpu_total_jiffies; /**< /proc/stat total jiffies snapshot */
uint64_t cpu_idle_jiffies; /**< /proc/stat idle jiffies snapshot */
uint64_t cgroup_cpu_usage_us; /**< cgroup v2 cpu.stat usage_usec (0 if unavailable) */
uint64_t cgroup_memory_current_bytes; /**< cgroup v2 memory.current (0 if unavailable) */
} physics_host_snapshot_t;
/** Event header written into the analytics ring buffer. */
typedef struct physics_analytics_event_header {
uint32_t channel; /**< Logical channel identifier */
uint16_t payload_bytes; /**< Size of event payload */
uint16_t reserved; /**< Reserved for alignment */
uint64_t timestamp_ns; /**< Producer timestamp */
} physics_analytics_event_header_t;
/** Analytics heap header seen by both producer (VM) and consumer (HOLA). */
typedef struct physics_analytics_header {
uint32_t magic; /**< HOLA_SHARED_MAGIC */
uint16_t version_major; /**< Protocol major version */
uint16_t version_minor; /**< Protocol minor version */
uint32_t heap_bytes; /**< Total heap size */
uint32_t ring_offset; /**< Offset to event ring */
uint32_t ring_bytes; /**< Event ring length */
uint32_t summary_offset; /**< Offset to summary/scratch region */
uint32_t summary_bytes; /**< Summary region length */
uint32_t scratch_offset; /**< Offset to reserved scratch area */
uint32_t scratch_bytes; /**< Scratch area length */
uint64_t produce_seq; /**< Monotonic sequence for producers */
uint64_t consume_seq; /**< Consumer-visible sequence */
uint32_t write_offset; /**< Producer write cursor */
uint32_t read_offset; /**< Consumer read cursor */
uint32_t dropped_events; /**< Count of dropped events */
uint32_t flags; /**< Heap flags (see docs) */
} physics_analytics_header_t;
/** Simple view onto the analytics heap layout. */
typedef struct physics_analytics_heap {
uint8_t *base; /**< Base pointer (owned by runtime) */
size_t bytes; /**< Total bytes reserved */
physics_analytics_header_t *header; /**< Shared header */
uint8_t *ring; /**< Event ring base */
size_t ring_bytes; /**< Event ring span */
uint8_t *summary; /**< Summary/statistics region */
size_t summary_bytes; /**< Summary region span */
uint8_t *scratch; /**< Scratch/reserved region */
size_t scratch_bytes; /**< Scratch region span */
} physics_analytics_heap_t;
/*
* @brief Initialise physics runtime services.
* @param analytics_heap_bytes Desired heap size (0 -> default 10 MiB)
* @return 0 on success, -1 on failure
*/
int physics_runtime_init(size_t analytics_heap_bytes);
/**
* @brief Tear down physics runtime services and release heap storage.
*/
void physics_runtime_shutdown(void);
/*
* @brief Capture a host snapshot.
* @param out Output buffer
* @return 0 on success, -1 on failure
*/
int physics_host_snapshot(physics_host_snapshot_t *out);
/*
* @brief Publish an event into the analytics ring buffer.
* @param channel Logical event channel
* @param payload Pointer to payload bytes
* @param payload_bytes Payload length (<= ring size)
* @return 0 on success, -1 if the payload could not be enqueued
*/
int physics_analytics_publish_event(uint32_t channel, const void *payload, uint16_t payload_bytes);
/**
* @brief Retrieve the analytics heap descriptor.
* @return Pointer to heap descriptor (NULL if runtime not initialised)
*/
const physics_analytics_heap_t *physics_analytics_heap_info(void);
#ifdef __cplusplus
}
#endif
#endif /* STARFORTH_PHYSICS_RUNTIME_H */
+86
View File
@@ -0,0 +1,86 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef STARFORTH_PLATFORM_ALLOC_H
#define STARFORTH_PLATFORM_ALLOC_H
/*
* platform_alloc.h - Portable memory allocation abstraction
*
* Host builds: Uses standard malloc/free
* Kernel builds: Uses static arena with bump allocator
*
* Usage:
* void* p = sf_malloc(size);
* sf_free(p);
* void* p2 = sf_calloc(count, size); // zero-initialized
*/
#include <stddef.h>
/*
* Allocate memory block of given size.
* Returns NULL on failure.
*/
void* sf_malloc(size_t size);
/*
* Allocate zero-initialized memory for array.
* Returns NULL on failure.
*/
void* sf_calloc(size_t count, size_t size);
/*
* Reallocate memory block to new size.
* Returns NULL on failure (original block unchanged).
*/
void* sf_realloc(void* ptr, size_t new_size);
/*
* Free previously allocated memory.
* Passing NULL is safe (no-op).
*/
void sf_free(void* ptr);
/*
* Initialize the allocator (required for kernel builds).
* Host builds: no-op.
* Kernel builds: sets up the static arena.
*
* Returns 0 on success, -1 on failure.
*/
int sf_alloc_init(void);
/*
* Get allocator statistics (for diagnostics).
*/
typedef struct sf_alloc_stats {
size_t total_bytes; /* Total arena size */
size_t used_bytes; /* Currently allocated */
size_t peak_bytes; /* High water mark */
size_t alloc_count; /* Number of allocations */
size_t free_count; /* Number of frees */
} sf_alloc_stats_t;
void sf_alloc_get_stats(sf_alloc_stats_t* stats);
#endif /* STARFORTH_PLATFORM_ALLOC_H */
+47
View File
@@ -0,0 +1,47 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* platform_io.h - Portable terminal-input-readiness check for StarForth
*
* One function, same shape as platform_lock.h/platform_time.h: a single
* portable declaration, implemented once per platform (POSIX select() on
* fd 0 for the hosted build, src/platform/linux/io.c; the kernel's own
* console/keyboard-event bridge for the freestanding build, shim.c).
* Backs the standard dictionary's ?TERMINAL word (io_words.c).
*/
#ifndef STARFORTH_PLATFORM_IO_H
#define STARFORTH_PLATFORM_IO_H
/**
* @brief Non-blocking check for whether a key/character is available to read.
*
* Must not block and must not consume the byte if one is found (a
* following KEY/getchar() must still return that same byte).
*
* @return 1 if input is ready, 0 otherwise
*/
int sf_terminal_ready(void);
#endif /* STARFORTH_PLATFORM_IO_H */
+71
View File
@@ -0,0 +1,71 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* platform_lock.h - Portable mutex abstraction for StarForth
*/
#ifndef STARFORTH_PLATFORM_LOCK_H
#define STARFORTH_PLATFORM_LOCK_H
/* HISTORICAL: this condition also included `|| defined(L4RE_TARGET)` --
* L4Re had no pthreads either, so it shared the spinlock path with
* STARFORTH_MINIMAL. L4Re support has been removed as an active target. */
#if defined(STARFORTH_MINIMAL)
typedef struct sf_mutex
{
volatile unsigned int state;
} sf_mutex_t;
#else
#include <pthread.h>
typedef struct sf_mutex
{
pthread_mutex_t handle;
} sf_mutex_t;
#endif
int sf_mutex_init(sf_mutex_t *mutex);
void sf_mutex_destroy(sf_mutex_t *mutex);
void sf_mutex_lock(sf_mutex_t *mutex);
void sf_mutex_unlock(sf_mutex_t *mutex);
#endif /* STARFORTH_PLATFORM_LOCK_H */
+177
View File
@@ -0,0 +1,177 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef STARFORTH_PLATFORM_TIME_H
#define STARFORTH_PLATFORM_TIME_H
/*
* StarForth Platform Time Abstraction
*
* Provides portable timing interface for POSIX builds. (HISTORICAL: also
* supported L4Re/StarshipOS through mid-2026; removed as an active target.)
* Uses vtable pattern similar to blkio subsystem.
*
* License: See LICENSE file
*/
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Time representation (nanoseconds since epoch) */
typedef uint64_t sf_time_ns_t;
/* Timestamp string buffer size */
#define SF_TIME_STAMP_SIZE 32
/* Platform time backend vtable */
typedef struct {
/**
* @brief Get monotonic time (for performance measurement)
* @return Nanoseconds since system boot (never decreases)
*/
sf_time_ns_t (*get_monotonic_ns)(void);
/**
* @brief Get wall-clock time
* @return Nanoseconds since Unix epoch (1970-01-01 00:00:00 UTC)
* Returns 0 if RTC unavailable
*/
sf_time_ns_t (*get_realtime_ns)(void);
/**
* @brief Set wall-clock time (may require privileges)
* @param ns_since_epoch Nanoseconds since Unix epoch
* @return 0 on success, -1 on error
*/
int (*set_realtime_ns)(sf_time_ns_t ns_since_epoch);
/**
* @brief Format timestamp as human-readable string
* @param ns_since_epoch Nanoseconds since Unix epoch
* @param buf Output buffer (SF_TIME_STAMP_SIZE bytes)
* @param format_24h Use 24-hour format (1) or 12-hour (0)
* @return 0 on success, -1 on error
*/
int (*format_timestamp)(sf_time_ns_t ns_since_epoch, char *buf, int format_24h);
/**
* @brief Check if real-time clock is available
* @return 1 if RTC available, 0 if not
*/
int (*has_rtc)(void);
} sf_time_backend_t;
/* Global backend pointer (set at initialization) */
extern const sf_time_backend_t *sf_time_backend;
/* Platform-specific backend registration */
extern const sf_time_backend_t sf_time_backend_posix; /* POSIX implementation */
extern const sf_time_backend_t sf_time_backend_l4re; /* HISTORICAL: L4Re/StarshipOS implementation, no longer wired into any build (see src/platform/l4re/time.c) */
/* Convenience wrappers (inline for zero overhead).
* Define PLATFORM_TIME_NO_INLINE before including this header to suppress
* these inline definitions (e.g. when providing concrete implementations).
* When suppressed, non-inline prototypes are declared so callers can link
* to a concrete implementation (e.g. shim.c in the kernel). */
#ifndef PLATFORM_TIME_NO_INLINE
static inline sf_time_ns_t sf_monotonic_ns(void) {
return sf_time_backend->get_monotonic_ns();
}
static inline sf_time_ns_t sf_realtime_ns(void) {
return sf_time_backend->get_realtime_ns();
}
static inline int sf_set_realtime_ns(sf_time_ns_t ns) {
return sf_time_backend->set_realtime_ns(ns);
}
static inline int sf_format_timestamp(sf_time_ns_t ns, char *buf, int format_24h) {
return sf_time_backend->format_timestamp(ns, buf, format_24h);
}
static inline int sf_has_rtc(void) {
return sf_time_backend->has_rtc();
}
#else /* PLATFORM_TIME_NO_INLINE */
/* Non-inline prototypes — provided by the platform's concrete implementation. */
sf_time_ns_t sf_monotonic_ns(void);
sf_time_ns_t sf_realtime_ns(void);
int sf_set_realtime_ns(sf_time_ns_t ns);
int sf_format_timestamp(sf_time_ns_t ns, char *buf, int format_24h);
int sf_has_rtc(void);
#endif /* PLATFORM_TIME_NO_INLINE */
/* Time conversion helpers */
static inline sf_time_ns_t sf_seconds_to_ns(uint64_t seconds) {
return seconds * 1000000000ULL;
}
static inline uint64_t sf_ns_to_seconds(sf_time_ns_t ns) {
return ns / 1000000000ULL;
}
static inline uint64_t sf_ns_to_ms(sf_time_ns_t ns) {
return ns / 1000000ULL;
}
static inline uint64_t sf_ns_to_us(sf_time_ns_t ns) {
return ns / 1000ULL;
}
/* Initialize platform time backend (call once at startup) */
void sf_time_init(void);
#ifdef __cplusplus
}
#endif
#endif /* STARFORTH_PLATFORM_TIME_H */
+265
View File
@@ -0,0 +1,265 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef STARFORTH_PROFILER_H
#define STARFORTH_PROFILER_H
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
/* Forward declarations */
typedef struct VM VM;
typedef struct DictEntry DictEntry;
/**
* @brief Profiling detail levels
*
* Controls the amount of profiling information collected during execution.
* Higher levels include all features from lower levels.
*/
typedef enum {
PROFILE_DISABLED = 0, /**< Profiling disabled (zero overhead) */
PROFILE_BASIC = 1, /**< Word execution frequency tracking (minimal overhead) */
PROFILE_DETAILED = 2, /**< Basic + word execution timing (5-10% overhead) */
PROFILE_VERBOSE = 3, /**< Detailed + stack/memory access patterns (15-20% overhead) */
PROFILE_FULL = 4 /**< Reserved for future use (full instrumentation) */
} ProfileLevel;
/**
* @brief Profiling measurement categories
*
* Different aspects of VM operation that can be profiled
*/
typedef enum {
PROF_VM_INIT = 0, /**< VM initialization */
PROF_VM_EXECUTE, /**< VM execution loop */
PROF_VM_COMPILE, /**< Word compilation */
PROF_DICT_LOOKUP, /**< Dictionary searches */
PROF_DICT_ADD, /**< Dictionary additions */
PROF_STACK_OPS, /**< Stack operations */
PROF_MEMORY_ACCESS, /**< Memory access operations */
PROF_WORD_EXEC, /**< Word execution */
PROF_IO_OPS, /**< I/O operations */
PROF_CONTROL_FLOW, /**< Control flow operations */
PROF_CATEGORY_COUNT /**< Total number of categories */
} ProfileCategory;
/**
* @brief Timer for measuring execution duration
*/
typedef struct {
uint64_t start_time; /**< Start timestamp */
uint64_t total_time; /**< Accumulated execution time */
uint64_t call_count; /**< Number of measurements */
uint64_t min_time; /**< Minimum recorded time */
uint64_t max_time; /**< Maximum recorded time */
const char *name; /**< Timer identifier */
} ProfileTimer;
/**
* @brief Execution statistics for a single word
*/
typedef struct {
const char *word_name; /**< Name of the profiled word */
uint64_t total_time; /**< Total execution time */
uint64_t call_count; /**< Number of executions */
uint64_t avg_time; /**< Average execution time */
uint64_t min_time; /**< Minimum execution time */
uint64_t max_time; /**< Maximum execution time */
double percentage; /**< Percentage of total runtime */
} WordProfile;
/* Memory access pattern tracking */
typedef struct {
uint64_t reads;
uint64_t writes;
uint64_t cache_hits;
uint64_t cache_misses;
uint64_t total_bytes_read;
uint64_t total_bytes_written;
} MemoryProfile;
/* Main profiler state */
typedef struct {
ProfileLevel level;
uint64_t profiling_overhead;
uint64_t total_execution_time;
/* Category timers */
ProfileTimer category_timers[PROF_CATEGORY_COUNT];
/* Word execution tracking */
WordProfile *word_profiles;
size_t word_profile_count;
size_t word_profile_capacity;
/* Memory access tracking */
MemoryProfile memory_stats;
/* Hot path detection */
uint64_t instruction_count;
uint64_t branch_count;
uint64_t loop_iterations;
/* Performance counters */
struct {
uint64_t vm_cycles;
uint64_t dict_lookups;
uint64_t stack_operations;
uint64_t memory_allocations;
uint64_t compile_operations;
} counters;
} Profiler;
/* Global profiler instance */
extern void *g_profiler;
/* Profiler lifecycle */
/**
* @brief Initialize the profiling system
* @param level Initial profiling detail level
* @return 0 on success, non-zero on failure
*/
int profiler_init(ProfileLevel level);
/**
* @brief Shutdown and cleanup the profiling system
*/
void profiler_shutdown(void);
/**
* @brief Reset all profiling statistics
*/
void profiler_reset(void);
/**
* @brief Change the current profiling detail level
* @param level New profiling level to set
*/
void profiler_set_level(ProfileLevel level);
/* Timing functions */
uint64_t profiler_get_time_ns(void);
void profiler_start_timer(ProfileCategory category);
void profiler_end_timer(ProfileCategory category);
/* Word profiling */
/**
* @brief Start timing a word execution (PROFILE_DETAILED+)
* @param entry Dictionary entry of the word being executed
*/
void profiler_word_enter(const DictEntry *entry);
/**
* @brief End timing a word execution (PROFILE_DETAILED+)
* @param entry Dictionary entry of the word being executed
*/
void profiler_word_exit(const DictEntry *entry);
/**
* @brief Track word execution frequency without timing (PROFILE_BASIC+)
* @param entry Dictionary entry of the word being executed
*
* This is a lightweight alternative to profiler_word_enter/exit that only
* increments call counters without timing overhead. Ideal for identifying
* hot words (frequently executed) for optimization.
*/
void profiler_word_count(const DictEntry *entry);
/* Memory profiling */
void profiler_memory_read(size_t bytes);
void profiler_memory_write(size_t bytes);
void profiler_memory_alloc(size_t bytes);
/* Counter increments */
void profiler_inc_vm_cycles(uint64_t cycles);
void profiler_inc_dict_lookup(void);
void profiler_inc_stack_op(void);
void profiler_inc_branch(void);
void profiler_inc_loop_iter(void);
/* Analysis and reporting */
void profiler_generate_report(void);
void profiler_print_summary(void);
void profiler_print_word_analysis(void);
void profiler_print_memory_analysis(void);
void profiler_print_hotspots(void);
/* Performance optimization suggestions */
void profiler_analyze_performance(VM *vm);
void profiler_suggest_optimizations(void);
/* Profiler macros for easy integration */
#define PROFILE_ENABLED() (0)
#define PROFILE_START(category) ((void)0)
#define PROFILE_END(category) ((void)0)
#define PROFILE_WORD_ENTER(name) ((void)0)
#define PROFILE_WORD_EXIT(name) ((void)0)
#define PROFILE_MEMORY_READ(bytes) ((void)0)
#define PROFILE_MEMORY_WRITE(bytes) ((void)0)
#define PROFILE_INC_CYCLES(n) ((void)0)
#define PROFILE_INC_DICT_LOOKUP() ((void)0)
#define PROFILE_INC_STACK_OP() ((void)0)
/* Scoped profiling helper */
typedef struct {
ProfileCategory category;
const char *word_name;
} ProfileScope;
#endif /* STARFORTH_PROFILER_H */
+305
View File
@@ -0,0 +1,305 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef Q48_16_H
#define Q48_16_H
#include <stdint.h>
/* ============================================================================
* Q48.16 Fixed-Point Arithmetic
* ============================================================================
*
* Format: uint64_t with fixed decimal point after bit 15
* - Bits 0-15: Fractional part (1/65536 resolution)
* - Bits 16-63: Integer part (up to 2^48-1)
*
* Example: 0x00010000 = 1.0, 0x00018000 = 1.5, 0x00010001 = 1 + 1/65536
*
* All operations are integer-only: no floating-point, no transcendental functions.
* Used for inference engine calculations (variance, regression, diagnostics).
*
* Why Q48.16?
* - 48-bit integer range: 0 to 281,474,976,710,655 (enough for VM metrics)
* - 16-bit fractional: ~0.0000152 resolution (precise enough for statistical calculations)
* - Perfect fit for 64-bit uint64_t: efficient on modern CPUs
*/
typedef uint64_t q48_16_t;
/* ============================================================================
* Core Arithmetic Operations (All Q48.16 ↔ Q48.16)
* ============================================================================
*/
/*
* @brief Multiply two Q48.16 values, keeping result in Q48.16
*
* Math: (a / 2^16) * (b / 2^16) * 2^16 = (a * b) / 2^16
* Returns: (a * b) >> 16
*
* @param a First Q48.16 operand
* @param b Second Q48.16 operand
* @return (a * b) >> 16 in Q48.16 format
*/
q48_16_t q48_mul(q48_16_t a, q48_16_t b);
/*
* @brief Divide two Q48.16 values, keeping result in Q48.16
*
* Math: (a / 2^16) / (b / 2^16) * 2^16 = (a << 16) / b
* Returns: (a << 16) / b
*
* @param a Dividend in Q48.16
* @param b Divisor in Q48.16 (non-zero)
* @return (a << 16) / b in Q48.16 format
*/
q48_16_t q48_div(q48_16_t a, q48_16_t b);
/* Guard: skip static inlines already provided by include/starkernel/q48_16.h
* (both headers share the same definitions; STARKERNEL_Q48_16_H is defined
* by the kernel version so we avoid -Werror redefinition in mixed builds). */
#ifndef STARKERNEL_Q48_16_H
/**
* @brief Add two Q48.16 values
*
* Math: (a / 2^16) + (b / 2^16) = (a + b) / 2^16
* Returns: a + b (no shift needed, both have same scaling)
*
* @param a First Q48.16 operand
* @param b Second Q48.16 operand
* @return a + b in Q48.16 format
*/
static inline q48_16_t q48_add(q48_16_t a, q48_16_t b) {
return a + b;
}
/**
* @brief Subtract two Q48.16 values
*
* Math: (a / 2^16) - (b / 2^16) = (a - b) / 2^16
* Returns: a - b (no shift needed)
*
* @param a Minuend in Q48.16
* @param b Subtrahend in Q48.16
* @return a - b in Q48.16 format
*/
static inline q48_16_t q48_sub(q48_16_t a, q48_16_t b) {
return a - b;
}
/**
* @brief Absolute value of Q48.16
*
* @param a Q48.16 value
* @return |a| in Q48.16 format
*/
static inline q48_16_t q48_abs(q48_16_t a) {
return (a < 0x8000000000000000ULL) ? a : (0ULL - a);
}
/* ============================================================================
* Conversion Operations (u64 ↔ Q48.16)
* ============================================================================
*/
/**
* @brief Convert unsigned 64-bit integer to Q48.16
*
* Math: u * 2^16 (shift left by 16 bits)
* Example: q48_from_u64(1) = 0x10000 (1.0 in Q48.16)
*
* @param u Unsigned 64-bit integer (max 2^48-1 to avoid overflow)
* @return u << 16 in Q48.16 format
*/
static inline q48_16_t q48_from_u64(uint64_t u) {
return u << 16;
}
/**
* @brief Convert Q48.16 to a 64-bit integer (truncate fractional)
*
* Math: q / 2^16 (arithmetic shift right by 16 bits)
* Example: q48_to_u64(0x10000) = 1, q48_to_u64(-0x10000) = -1
*
* q48_16_t values are two's-complement signed under the hood (q48_neg/
* q48_abs already treat them that way); a plain unsigned (logical) shift
* would corrupt negative inputs instead of sign-extending them, so this
* shifts via a signed intermediate. Bit-identical to the old behavior for
* non-negative q (the only case this function's other caller, the
* inference engine's sum-of-squares accumulation, ever produces).
*
* @param q Q48.16 value
* @return q >> 16, sign-extended, reinterpreted as uint64_t
*/
static inline uint64_t q48_to_u64(q48_16_t q) {
return (uint64_t)(((int64_t)q) >> 16);
}
#endif /* STARKERNEL_Q48_16_H */
/*
* @brief Convert double to Q48.16 (for testing/initialization only)
*
* NOT used in inference engine (inference is integer-only).
* Provided for testing and diagnostic tools only.
*
* @param d Double-precision float
* @return d * 2^16 as Q48.16
*/
q48_16_t q48_from_double(double d);
/*
* @brief Convert Q48.16 to double (for testing/diagnostics only)
*
* NOT used in inference engine (inference is integer-only).
* Provided for logging and RStudio dashboard output.
*
* @param q Q48.16 value
* @return q / 2^16 as double
*/
double q48_to_double(q48_16_t q);
/* ============================================================================
* Approximation Operations (Integer-Only)
* ============================================================================
*/
/*
* @brief Approximate natural logarithm in Q48.16 (integer-only)
*
* Purpose: Required for decay slope inference via exponential fitting
* Model: ln(heat[t]) = ln(h0) - slope*t
*
* Method: Piecewise linear approximation or Newton-Raphson iteration
* - Input x must be > 0 (assertion check)
* - Output is ln(x) in Q48.16 format
* - Precision: ~5% error acceptable (tuning parameter)
* - No floating-point operations
*
* Implementation Strategy:
* 1. Use bit position (floor(log2(x))) as coarse estimate
* 2. Refine with Newton iteration (3-4 cycles for Q48.16 precision)
* 3. Keep all arithmetic in integer space
*
* Example: q48_log_approx(65536) = log(1.0) in Q48.16 ≈ 0
* q48_log_approx(131072) = log(2.0) in Q48.16 ≈ 0x0000B172
*
* @param x Value to take logarithm of (x > 0)
* @return ln(x) in Q48.16 format
*/
q48_16_t q48_log_approx(uint64_t x);
/*
* @brief Approximate exponential e^x in Q48.16 (integer-only, future use)
*
* Purpose: Might be needed for inverse operations or model validation
* Status: Defined but not required for Phase 1 inference engine
* Method: Similar to q48_log_approx (piecewise + Newton)
*
* @param q Q48.16 exponent
* @return e^q in Q48.16 format
*/
q48_16_t q48_exp_approx(q48_16_t q);
/*
* @brief Approximate square root in Q48.16 (integer-only)
*
* Purpose: Compute R² (fit quality) for regression diagnostics
* Method: Newton-Raphson for integer-only sqrt
*
* @param q Q48.16 value
* @return sqrt(q) in Q48.16 format
*/
q48_16_t q48_sqrt_approx(q48_16_t q);
/*
* @brief Approximate sin(q) in Q48.16 (integer-only, Taylor series)
*
* Purpose: Console drawing fabric's CIRCLE/ARC/ELLIPSE (FABRIC-0.md item 4.3.3b)
* Method: Range-reduce into [-pi, pi], then Taylor series
*
* @param q Angle in Q48.16 format (radians, any magnitude)
* @return sin(q) in Q48.16 format
*/
q48_16_t q48_sin_approx(q48_16_t q);
/*
* @brief Approximate cos(q) in Q48.16 (integer-only, Taylor series)
*
* Purpose: Console drawing fabric's CIRCLE/ARC/ELLIPSE (FABRIC-0.md item 4.3.3b)
* Method: Range-reduce into [-pi, pi], then Taylor series
*
* @param q Angle in Q48.16 format (radians, any magnitude)
* @return cos(q) in Q48.16 format
*/
q48_16_t q48_cos_approx(q48_16_t q);
/* ============================================================================
* Diagnostic / Testing Utilities
* ============================================================================
*/
/*
* @brief Pretty-print Q48.16 value to string for debugging
*
* Format: "integer.fractional" (e.g., "1.50000")
* Uses internal static buffer - NOT thread-safe
*
* @param q Q48.16 value
* @return Pointer to static string (valid until next call)
*/
const char* q48_to_string(q48_16_t q);
/*
* @brief Validate Q48.16 value is within safe range
*
* Checks: Does not overflow on mul/div operations
* Returns: 1 if valid, 0 if risky
*
* @param q Q48.16 value
* @return 1 if safe, 0 if risky
*/
int q48_is_valid(q48_16_t q);
#endif /* Q48_16_H */
+55
View File
@@ -0,0 +1,55 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef REPL_H
#define REPL_H
#include "vm.h"
/*
* @brief Starts the REPL (Read-Eval-Print Loop) for the Forth virtual machine
* @param vm Pointer to the VM instance to run the REPL on
* @param script_mode If 1, suppress prompts and "ok" output (for piped input)
*/
void vm_repl(VM *vm, int script_mode);
#endif /* REPL_H */
+205
View File
@@ -0,0 +1,205 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
*** StarForth ***
rolling_window_knobs.h - Tunable Adaptive Shrinking Control
Knobs that control how the rolling window automatically learns and adapts
its size during execution. These are NOT the initial window size - they
control the self-tuning behavior.
All knobs are tunable via Makefile:
make ADAPTIVE_SHRINK_RATE=30 (shrink by 30% instead of default)
make ADAPTIVE_MIN_WINDOW_SIZE=512 (never shrink below 512)
etc.
*/
#ifndef ROLLING_WINDOW_KNOBS_H
#define ROLLING_WINDOW_KNOBS_H
#include "starforth_config.h"
/* ============================================================================
* Knob #8: ADAPTIVE_SHRINK_RATE - How aggressively to shrink the window
* ============================================================================
*
* When pattern diversity plateaus, the window shrinks by this percentage.
*
* Value: 0-99 (percentage to retain, not discard)
* ADAPTIVE_SHRINK_RATE=75 → shrink to 75% of current size (discard 25%)
* ADAPTIVE_SHRINK_RATE=50 → shrink to 50% of current size (discard 50%)
* ADAPTIVE_SHRINK_RATE=90 → shrink to 90% of current size (discard 10%)
*
* Rationale:
* Higher (90+): Conservative, slow learning, larger final window
* Default (75): Balanced - learns in 3-4 shrink cycles
* Lower (50): Aggressive, fast learning, minimal final window
*
* Trade-off:
* Higher = safer (more pattern capture margin, slower adaptation)
* Lower = leaner (less memory overhead, faster optimization)
*
* Default: see starforth_config.h (STARFORTH_CONFIG_ADAPTIVE_SHRINK_RATE_DEFAULT)
* Range: 50-95 (below 50 is too aggressive, above 95 barely shrinks)
*
* Tuning: make ADAPTIVE_SHRINK_RATE=80
*/
/* ============================================================================
* Knob #9: ADAPTIVE_MIN_WINDOW_SIZE - Minimum window size (safety floor)
* ============================================================================
*
* The window will never shrink below this size, even if diversity plateaus.
* Prevents over-optimization that might miss emerging patterns.
*
* Value: 64-2048 (in word IDs, not bytes)
* ADAPTIVE_MIN_WINDOW_SIZE=256 → don't shrink below 256
* ADAPTIVE_MIN_WINDOW_SIZE=128 → don't shrink below 128
* ADAPTIVE_MIN_WINDOW_SIZE=512 → don't shrink below 512
*
* Rationale:
* Smaller (128): Lean optimization, accept lower pattern capture
* Default (256): Balanced - typical workload needs 200-250 patterns
* Larger (512): Conservative, handles complex workloads
*
* Trade-off:
* Smaller = less memory, risk of missing edge-case patterns
* Larger = more memory, safer for unpredictable workloads
*
* Default: see starforth_config.h (STARFORTH_CONFIG_ADAPTIVE_MIN_WINDOW_SIZE_DEFAULT)
* Range: 64-1024
*
* Tuning: make ADAPTIVE_MIN_WINDOW_SIZE=512
*/
/* ============================================================================
* Knob #10: ADAPTIVE_CHECK_FREQUENCY - How often to check diversity
* ============================================================================
*
* The system checks pattern diversity every N executions.
* More frequent checks = faster response to saturation, more overhead.
*
* Value: 32-1024 (execution count between checks)
* ADAPTIVE_CHECK_FREQUENCY=128 → check after every 128 executions
* ADAPTIVE_CHECK_FREQUENCY=256 → check after every 256 executions
* ADAPTIVE_CHECK_FREQUENCY=512 → check after every 512 executions
*
* Rationale:
* More frequent (128): Responsive learning, detects saturation quickly
* Default (256): Balanced - good responsiveness, minimal overhead
* Less frequent (512): Lazy learning, smaller monitoring overhead
*
* Trade-off:
* More = faster adaptation, slightly more computation
* Less = slower adaptation, minimal overhead
*
* Default: see starforth_config.h (STARFORTH_CONFIG_ADAPTIVE_CHECK_FREQUENCY_DEFAULT)
* Range: 32-1024
*
* Tuning: make ADAPTIVE_CHECK_FREQUENCY=128
*/
/* ============================================================================
* Knob #11: ADAPTIVE_GROWTH_THRESHOLD - Pattern growth rate that signals saturation
* ============================================================================
*
* When pattern diversity growth drops below this rate, window can shrink.
* Expressed as percentage (0-100).
*
* Value: 0-10 (as percentage, 0=0%, 10=10%)
* ADAPTIVE_GROWTH_THRESHOLD=1 → shrink when growth < 1% (aggressive)
* ADAPTIVE_GROWTH_THRESHOLD=2 → shrink when growth < 2% (default, balanced)
* ADAPTIVE_GROWTH_THRESHOLD=5 → shrink when growth < 5% (conservative)
*
* Rationale:
* Lower (0.5%): Very aggressive, assumes patterns found early
* Default (1%): Balanced - empirically tested, works well
* Higher (5%): Conservative, waits for stronger saturation signal
*
* Trade-off:
* Lower = eager shrinking, risk of missing late-arriving patterns
* Higher = cautious shrinking, longer time to final window size
*
* Default: see starforth_config.h (STARFORTH_CONFIG_ADAPTIVE_GROWTH_THRESHOLD_DEFAULT)
* Range: 0-10
*
* Note: Internally, this is applied as: growth_rate < (threshold / 100.0)
* E.g., ADAPTIVE_GROWTH_THRESHOLD=1 means growth_rate < 0.01
*
* Tuning: make ADAPTIVE_GROWTH_THRESHOLD=2
*/
/* ============================================================================
* Summary Table: How Knobs Interact
* ============================================================================
*
* Scenario 1: "Fast Learning, Lean Final Size"
* ADAPTIVE_SHRINK_RATE=50
* ADAPTIVE_MIN_WINDOW_SIZE=128
* ADAPTIVE_CHECK_FREQUENCY=128
* ADAPTIVE_GROWTH_THRESHOLD=0
* → Shrinks aggressively, frequently, to minimal size
* → Best for: Memory-constrained systems, simple workloads
*
* Scenario 2: "Balanced (Default)" -- see starforth_config.h for the exact
* shipped default of each knob; illustrative values below.
* ADAPTIVE_SHRINK_RATE=50
* ADAPTIVE_MIN_WINDOW_SIZE=256
* ADAPTIVE_CHECK_FREQUENCY=512
* ADAPTIVE_GROWTH_THRESHOLD=5
* → Good balance, typical FORTH programs
* → Best for: General-purpose systems
*
* Scenario 3: "Conservative, Safe"
* ADAPTIVE_SHRINK_RATE=90
* ADAPTIVE_MIN_WINDOW_SIZE=512
* ADAPTIVE_CHECK_FREQUENCY=512
* ADAPTIVE_GROWTH_THRESHOLD=5
* → Slow, cautious shrinking, large safety margin
* → Best for: Complex workloads, unpredictable patterns
*
*/
#endif /* ROLLING_WINDOW_KNOBS_H */
+265
View File
@@ -0,0 +1,265 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
*** StarForth ***
rolling_window_of_truth.h - Execution history for deterministic metrics seeding
The "Rolling Window of Truth" captures actual execution sequences to:
1. Seed initial metrics after POST (hot-words cache, pipelining context)
2. Provide deterministic record of all word executions
3. Enable reproducible, provable optimization decisions
4. Form foundation for formal verification (Isabelle/HOL)
Vision: Every decision is observable, traceable, and mathematically provable.
*/
#ifndef ROLLING_WINDOW_OF_TRUTH_H
#define ROLLING_WINDOW_OF_TRUTH_H
#include <stdint.h>
#include "vm.h"
/* ============================================================================
* Rolling Window of Truth
* ============================================================================
*
* Circular buffer of word IDs representing recent execution sequence.
* Used to:
* 1. Seed metrics after POST completes
* 2. Provide execution context for pipelining decisions
* 3. Enable reproducibility and formal verification
*
* NOTE: Structure is defined in vm.h to avoid circular includes.
* See vm.h for structure definition and ROLLING_WINDOW_SIZE constant.
*/
/* ============================================================================
* Rolling Window API
* ============================================================================
*/
/*
* Initialize rolling window of truth.
* Called during VM initialization.
*
* @param window Pointer to uninitialized RollingWindowOfTruth
* @return 0 on success, -1 on malloc failure
*/
int rolling_window_init(RollingWindowOfTruth * window);
/*
* Record a word execution in the rolling window.
* Called after each word executes (in inner interpreter).
*
* THREAD SAFETY: This function is NOT thread-safe. Caller MUST hold
* vm->tuning_lock when calling from multiple threads (main VM + heartbeat).
*
* Called from:
* - Main VM thread (inner interpreter loop, src/vm.c:1162)
* - Heartbeat worker thread (through rolling_window_service, src/vm.c:743)
*
* MUST ALWAYS be protected by:
* sf_mutex_lock(&vm->tuning_lock);
* rolling_window_record_execution(&vm->rolling_window, word_id);
* sf_mutex_unlock(&vm->tuning_lock);
*
* Extremely lightweight (constant time):
* - Write to circular buffer
* - Increment counters
* - 2-3 CPU cycles (plus lock overhead ~100 cycles uncontended)
*
* @param window Rolling window to update
* @param word_id Word that just executed
* @return 0 on success, -1 on error
*/
int rolling_window_record_execution(RollingWindowOfTruth* window, uint32_t word_id);
/*
* Get recent execution sequence for context.
* Returns pointer to last N words (or fewer if not enough executions yet).
*
* @param window Rolling window
* @param depth How many previous words to return (1 to ROLLING_WINDOW_SIZE)
* @param out_sequence Output array (caller allocates, must be >= depth)
* @return Number of words written to out_sequence (may be < depth if cold start)
*/
uint32_t rolling_window_get_recent_sequence(const RollingWindowOfTruth* window,
uint32_t depth,
uint32_t* out_sequence);
/*
* Get most frequent word in rolling window.
* Useful for identifying hot words immediately after POST.
*
* @param window Rolling window
* @param dict_size Total number of words in dictionary (for bounds check)
* @return Word ID of most frequent execution, or 0 if window not warm
*/
uint32_t rolling_window_find_hottest_word(const RollingWindowOfTruth* window,
uint32_t dict_size);
/*
* Get transition frequency: how often word_b follows word_a in window.
*
* Useful for seeding pipelining context predictions.
*
* @param window Rolling window
* @param word_a First word in sequence
* @param word_b Second word in sequence
* @return Count of (word_a → word_b) transitions in window
*/
uint64_t rolling_window_count_transition(const RollingWindowOfTruth* window,
uint32_t word_a,
uint32_t word_b);
/*
* Check if window is warm (contains representative data).
* Seeding functions should check this before using window data.
*
* @param window Rolling window
* @return 1 if window is warm (>= ROLLING_WINDOW_SIZE executions), 0 otherwise
*/
int rolling_window_is_warm(const RollingWindowOfTruth* window);
/*
* Get detailed statistics string for diagnostics.
*
* @param window Rolling window
* @return Formatted string (caller must free)
*/
char* rolling_window_stats_string(const RollingWindowOfTruth* window);
/*
* Reset rolling window (clear all data).
*
* @param window Rolling window to reset
*/
void rolling_window_reset(RollingWindowOfTruth * window);
/*
* Free all memory allocated for rolling window.
* Called during VM cleanup.
*
* @param window Rolling window to clean up
*/
void rolling_window_cleanup(RollingWindowOfTruth * window);
/* ============================================================================
* Bootstrap Data Analysis API
* ============================================================================
*
* Used to analyze rolling window data and determine statistically significant
* window sizes for cold-start systems (un-physics VM).
*/
/*
* Export full execution history for external analysis.
* Used by bootstrap analyzer to determine optimal window size.
*
* Returns the complete circular buffer linearized in chronological order.
* Caller allocates output buffer; function fills it with execution sequence.
*
* @param window Rolling window to export
* @param out_sequence Output buffer (caller allocates, must be >= total_executions)
* @param max_count Maximum entries to write
* @return Number of word IDs written to out_sequence
*/
uint64_t rolling_window_export_execution_history(const RollingWindowOfTruth* window,
uint32_t* out_sequence,
uint64_t max_count);
/*
* Analyze pattern capture rate for a given window size.
* Measures what percentage of unique transitions are captured.
*
* Algorithm:
* 1. Scan execution history with sliding window of given size
* 2. Count unique (context, next_word) patterns observed
* 3. Return: (patterns_captured / total_unique_patterns) * 100
*
* Used for offline analysis to determine reasonable starting window size.
*
* @param window Rolling window with execution data
* @param test_window_size Window size to test (e.g., 256, 512, 1024)
* @return Pattern capture rate (0-100%)
*/
double rolling_window_pattern_capture_rate(const RollingWindowOfTruth* window,
uint32_t test_window_size);
/* ============================================================================
* Adaptive Window Sizing API
* ============================================================================
*
* Automatic continuous self-tuning of window size during execution.
* Called automatically during normal operation - no user intervention needed.
*/
/*
* Measure pattern diversity in rolling window.
* Counts unique adjacent transitions (word_a → word_b) to indicate workload variability.
*
* Used by dictionary heat optimization to decide lookup strategy:
* - High diversity (>70) = use heat-aware lookup (patterns are varied)
* - Low diversity (<30) = use naive lookup (patterns are stable)
*
* @param window Rolling window to analyze
* @return Count of unique transitions in window
*/
uint64_t rolling_window_measure_diversity(const RollingWindowOfTruth* window);
/*
* Check and apply adaptive window shrinking.
* Called periodically by rolling_window_record_execution.
* Measures pattern diversity and shrinks window if diminishing returns detected.
*
* This function is called automatically - users never invoke it directly.
* It runs every 256 executions after window becomes warm.
*
* @param window Rolling window (modified in-place if shrinking occurs)
*/
void rolling_window_check_adaptive_shrink(RollingWindowOfTruth* window);
void rolling_window_service(RollingWindowOfTruth* window);
#endif /* ROLLING_WINDOW_OF_TRUTH_H */
+267
View File
@@ -0,0 +1,267 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef SSM_JACQUARD_H
#define SSM_JACQUARD_H
#include <stdint.h>
#include "compudynamics.h"
#include "starforth_config.h"
/* ============================================================================
* SSM L8: Jacquard Mode Selector (Data-Driven Architecture)
* ============================================================================
*
* Based on 2^7 DoE with 300 reps (128 configurations, 38,400 total runs).
* Top 5% analysis reveals optimal loop combinations for speed + stability.
*
* Architecture (Experimentally Validated):
* - L1 (heat_tracking): DISABLED (harmful in 86% of top configs)
* - L4 (pipelining_metrics): DISABLED (harmful in 100% of top configs)
* - L7 (adaptive_heartrate): ALWAYS ON (beneficial in 71% of top configs)
* - L2, L3, L5, L6: Runtime-controlled by L8 (workload-dependent)
* - L8 (Jacquard): 4-bit selector (16 modes) controlling L2/L3/L5/L6
*
* L8 operates as a multi-dimensional classifier:
* L2 (window): ON if entropy > 0.75 (diversity tracking)
* L3 (decay): ON if temporal_decay > 0.5 (temporal locality)
* L5 (window_inf): ON if cv > 0.15 (variance adaptation)
* L6 (decay_inf): ON if cv > 0.15 AND temporal_decay > 0.3
*
* Top 5% Validated Modes:
* C4 (0100): L2=0, L3=1, L5=0, L6=0 - Temporal locality
* C7 (0111): L2=0, L3=1, L5=1, L6=1 - Full inference
* C9 (1001): L2=1, L3=0, L5=0, L6=1 - Diverse + decay_inf
* C11 (1011): L2=1, L3=0, L5=1, L6=1 - Diverse + inference
* C12 (1100): L2=1, L3=1, L5=0, L6=0 - Diverse + temporal
*/
/* ============================================================================
* L8 Mode Definitions (4-bit: L2/L3/L5/L6)
* ============================================================================
*/
typedef enum {
/* Bits: L2 L3 L5 L6 */
SSM_MODE_C0 = 0x0, /* 0000: Minimal (stable/predictable workloads) */
SSM_MODE_C1 = 0x1, /* 0001: L6 only (decay inference) */
SSM_MODE_C2 = 0x2, /* 0010: L5 only (window inference) */
SSM_MODE_C3 = 0x3, /* 0011: L5+L6 (volatile workloads) */
SSM_MODE_C4 = 0x4, /* 0100: L3 only (temporal locality) ✅ TOP 5% */
SSM_MODE_C5 = 0x5, /* 0101: L3+L6 (temporal + decay_inf) */
SSM_MODE_C6 = 0x6, /* 0110: L3+L5 (temporal + window_inf) */
SSM_MODE_C7 = 0x7, /* 0111: L3+L5+L6 (full inference) ✅ TOP 5% */
SSM_MODE_C8 = 0x8, /* 1000: L2 only (high diversity) */
SSM_MODE_C9 = 0x9, /* 1001: L2+L6 (diverse + decay_inf) ✅ TOP 5% */
SSM_MODE_C10 = 0xA, /* 1010: L2+L5 (diverse + window_inf) */
SSM_MODE_C11 = 0xB, /* 1011: L2+L5+L6 (diverse + inference) ✅ TOP 5% */
SSM_MODE_C12 = 0xC, /* 1100: L2+L3 (diverse + temporal) ✅ TOP 5% */
SSM_MODE_C13 = 0xD, /* 1101: L2+L3+L6 (complex workload) */
SSM_MODE_C14 = 0xE, /* 1110: L2+L3+L5 (full adaptive, no decay_inf) */
SSM_MODE_C15 = 0xF /* 1111: L2+L3+L5+L6 (full adaptive, all on) */
} ssm_l8_mode_t;
/* ============================================================================
* L8 Metrics (Input to Mode Selection)
* ============================================================================
*/
typedef struct {
double entropy; /* Rolling-window entropy/diversity (0.0-1.0) */
double cv; /* Coefficient of variation (short-term volatility) */
double temporal_decay; /* Temporal locality strength (0.0-1.0) */
double stability_score; /* Combined stability metric for hysteresis */
int inference_ran_this_tick; /* 1 if inference engine ran this tick */
int inference_early_exited; /* 1 if inference early-exited (ANOVA stable) */
} ssm_l8_metrics_t;
/* ============================================================================
* SSM Configuration (L1-L7 Mode Bits)
* ============================================================================
*
* L1/L4/L7 were runtime-inert until this field expansion: the adaptive
* table (SsmConfigTable, below) always selected across the full 128-config
* (7-bit) space, but ssm_apply_mode_from_table() and its per-tick scoring
* counterpart only ever wrote 4 of those 7 bits out here, so L1/L4/L7 were computed by
* the bandit's UCB selection and then silently discarded before reaching
* any real gate -- those three loops were governed purely by compile-time
* macros (ENABLE_HOTWORDS_CACHE, ENABLE_PIPELINING, HEARTBEAT_THREAD_ENABLED)
* for the whole process lifetime. Adding these fields, and wiring them at
* their four call sites alongside (not instead of) the compile-time macros,
* makes the bandit's full selection actually take effect. */
typedef struct {
int L1_heat_tracking; /* 1 = hotwords cache active, 0 = off */
int L2_rolling_window; /* 1 = window tracking active, 0 = off */
int L3_linear_decay; /* 1 = decay active, 0 = off */
int L4_pipelining; /* 1 = pipelining metrics active, 0 = off */
int L5_window_inference; /* 1 = window inference active, 0 = off */
int L6_decay_inference; /* 1 = decay inference active, 0 = off */
int L7_adaptive_heartrate; /* 1 = heartbeat thread active, 0 = off */
} ssm_config_t;
/* ============================================================================
* L8 Configuration Thresholds (Data-Driven from DoE)
* ============================================================================
* Defaults live in starforth_config.h (included above), the single source
* of truth for VM build-time defaults; overridable via -D exactly as before.
*/
/* ============================================================================
* Adaptive Table Constants (L8 Heat-Ranked 128-Config Selector)
* One clock only: the heartbeat tick (rev r). Scoring and UCB reselection
* both run every tick -- no derived multi-tick batching unit exists
* anywhere in this mechanism. The score/UCB/reward/weight constants live
* in cd_tuning_word() (compudynamics.h/.c) now, not here -- this is the
* word-level instantiation of the generic compudynamics module (rev s).
* ============================================================================
*/
/* ============================================================================
* 7-bit Config Bit Positions (b6=L1, b5=L2, b4=L3, b3=L4, b2=L5, b1=L6, b0=L7)
* ============================================================================
*/
#define SSM_CFG_L1 0x40u
#define SSM_CFG_L2 0x20u
#define SSM_CFG_L3 0x10u
#define SSM_CFG_L4 0x08u
#define SSM_CFG_L5 0x04u
#define SSM_CFG_L6 0x02u
#define SSM_CFG_L7 0x01u
/* ============================================================================
* L8 State (extended with adaptive table pointer)
* ============================================================================
*
* The adaptive table itself (CDConfigEntry/CDConfigTable) is the generic
* compudynamics bandit (compudynamics.h), sized to 128 configs x 8 regimes
* via cd_tuning_word() -- see ssm_l8_init_table().
*/
typedef struct {
ssm_l8_mode_t current_mode; /* Mirrored from table for diagnostics */
uint32_t hysteresis_counter; /* Legacy threshold mode hysteresis */
ssm_l8_mode_t pending_mode; /* Legacy threshold mode pending */
CDConfigTable *table; /* NULL = legacy threshold; non-NULL = adaptive */
} ssm_l8_state_t;
/* ============================================================================
* L8 API
* ============================================================================
*/
/**
* @brief Initialize L8 state (legacy fields only; call ssm_l8_init_table separately)
*/
void ssm_l8_init(ssm_l8_state_t *state, ssm_l8_mode_t initial_mode);
/**
* @brief Allocate and seed the adaptive config table
* Sets state->table; must be called after ssm_l8_init().
* On allocation failure, state->table remains NULL (legacy mode is used).
*/
void ssm_l8_init_table(ssm_l8_state_t *state);
/**
* @brief Free the adaptive config table
*/
void ssm_l8_free_table(ssm_l8_state_t *state);
/*
* @brief Per-tick update for the adaptive table path
*
* Classifies the current regime, scores the current config from this
* tick's outcome, and UCB-reselects -- every call, no batching -- and
* applies the selected config to ssm_config_t.
*
* The locality component of the regime classification (and of the
* joint-convergence reward signal) is computed here from recent_word_ids
* via cd_classify_ids() -- purely execution-count-derived, no wall-clock
* input. This replaced a decay-slope-derived temporal_decay signal (rev
* r) that made L8's regime classification wall-clock-tainted once it ran
* every tick instead of once per ~2000 ticks, breaking cross-architecture
* dict_hash determinism under QEMU/TCG's differing real emulation speed
* per ISA (rev s).
*
* @param state L8 state (must have state->table != NULL)
* @param metrics Current runtime metrics (entropy, cv, inference status)
* @param config SSM config updated this tick
* @param current_window VM's effective_window_size this tick
* @param recent_word_ids Recent word-execution-ID history, oldest first
* (e.g. from rolling_window_get_recent_sequence())
* @param recent_word_ids_count Number of valid entries in recent_word_ids
*/
void ssm_l8_update_table(ssm_l8_state_t *state, const ssm_l8_metrics_t *metrics,
ssm_config_t *config,
uint32_t current_window,
const uint32_t *recent_word_ids, uint32_t recent_word_ids_count);
/**
* @brief Apply the current table config to ssm_config_t
* Used on first tick to set the initial DoE-seeded config immediately.
*/
void ssm_apply_mode_from_table(const ssm_l8_state_t *state, ssm_config_t *config);
/**
* @brief Force the adaptive table onto an externally-chosen config
* (e.g. a DoE campaign driving a specific tick's config rather than
* letting the bandit's own UCB selection choose it). See definition for
* details on why this stays coherent with the bandit's own reward loop.
*/
void ssm_l8_force_config(ssm_l8_state_t *state, ssm_config_t *config, uint8_t config_idx);
/**
* @brief Update L8 state based on current metrics (legacy threshold path)
*/
void ssm_l8_update(const ssm_l8_metrics_t *metrics, ssm_l8_state_t *state);
/**
* @brief Apply L8 mode to SSM configuration (legacy threshold path)
*/
void ssm_apply_mode(const ssm_l8_state_t *state, ssm_config_t *config);
/**
* @brief Get human-readable mode name
*/
const char* ssm_l8_mode_name(ssm_l8_mode_t mode);
#endif /* SSM_JACQUARD_H */
+255
View File
@@ -0,0 +1,255 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* StarForth VM Feature Configuration
*
* This header is the single source of truth for VM build-time defaults.
* Build systems may override any option via -D<FLAG>=<value>, but every
* translation unit sees these defaults even if no -D is provided.
*
* Hosted + kernel builds both force-include this header (see Makefiles),
* so new toggles belong here with a documented owner.
*/
#ifndef STARFORTH_CONFIG_H
#define STARFORTH_CONFIG_H
/* Base defaults (match 2025-12-26 hosted baseline) */
#define STARFORTH_CONFIG_STRICT_PTR_DEFAULT 1
#define STARFORTH_CONFIG_ENABLE_HOTWORDS_CACHE_DEFAULT 0
#define STARFORTH_CONFIG_ENABLE_PIPELINING_DEFAULT 0
#define STARFORTH_CONFIG_ROLLING_WINDOW_SIZE_DEFAULT 4096
#define STARFORTH_CONFIG_TRANSITION_WINDOW_SIZE_DEFAULT 8
#define STARFORTH_CONFIG_ADAPTIVE_SHRINK_RATE_DEFAULT 50
#define STARFORTH_CONFIG_ADAPTIVE_MIN_WINDOW_SIZE_DEFAULT 256
#define STARFORTH_CONFIG_ADAPTIVE_CHECK_FREQUENCY_DEFAULT 512
#define STARFORTH_CONFIG_ADAPTIVE_GROWTH_THRESHOLD_DEFAULT 5
#define STARFORTH_CONFIG_INITIAL_DECAY_SLOPE_Q48_DEFAULT 21845
#define STARFORTH_CONFIG_DECAY_MIN_INTERVAL_DEFAULT 1000ULL
#define STARFORTH_CONFIG_DECAY_RATE_PER_US_Q16_DEFAULT 1
#define STARFORTH_CONFIG_HEARTBEAT_THREAD_ENABLED_DEFAULT 1
#define STARFORTH_CONFIG_HEARTBEAT_TICK_NS_DEFAULT 10000ULL
#define STARFORTH_CONFIG_HEARTBEAT_INFERENCE_FREQUENCY_DEFAULT 1000
#define STARFORTH_CONFIG_STADIUM_VM_MEMORY_PERCENT_DEFAULT 50
#define STARFORTH_CONFIG_STADIUM_CONTAINS_DEPTH_MAX_DEFAULT 5
#define STARFORTH_CONFIG_STADIUM_CAPACITY_TICK_DEFAULT 4000
#define STARFORTH_CONFIG_STADIUM_MEMORY_PERCENT_DEFAULT 1
#define STARFORTH_CONFIG_STADIUM_WORD_HEAT_QUANTUM_DEFAULT 2048
#define STARFORTH_CONFIG_STADIUM_WORD_COOL_RATE_Q48_DEFAULT 21845
#define STARFORTH_CONFIG_STADIUM_BLOCK_HEAT_QUANTUM_DEFAULT 2048
#define STARFORTH_CONFIG_STADIUM_BLOCK_COOL_RATE_Q48_DEFAULT 21845
#define STARFORTH_CONFIG_STADIUM_BLOCK_TRACK_CAP_MULT_DEFAULT 2
#define STARFORTH_CONFIG_HEARTBEAT_CHECK_FREQUENCY_DEFAULT 256
#define STARFORTH_CONFIG_HEARTBEAT_WINDOW_TUNING_FREQUENCY_DEFAULT 1000
#define STARFORTH_CONFIG_HEARTBEAT_SLOPE_VALIDATION_FREQUENCY_DEFAULT 5000
#define STARFORTH_CONFIG_SSM_ENTROPY_HIGH_THRESHOLD_DEFAULT 0.75
#define STARFORTH_CONFIG_SSM_CV_HIGH_THRESHOLD_DEFAULT 0.15
#define STARFORTH_CONFIG_SSM_TEMPORAL_DECAY_THRESHOLD_DEFAULT 0.5
#define STARFORTH_CONFIG_SSM_TEMPORAL_DECAY_LOW_THRESHOLD_DEFAULT 0.3
#define STARFORTH_CONFIG_SSM_HYSTERESIS_TICKS_DEFAULT 5
#define STARFORTH_CONFIG_SPECULATION_THRESHOLD_Q48_DEFAULT (0x8000LL)
#define STARFORTH_CONFIG_SPECULATION_DEPTH_DEFAULT 1
#define STARFORTH_CONFIG_MIN_SAMPLES_FOR_SPECULATION_DEFAULT 10
#define STARFORTH_CONFIG_MISPREDICTION_COST_Q48_DEFAULT (25LL << 16)
/* 1.10 in Q48.16 (1.10 * 65536 = 72089.6, rounds to 0x1199A). Was shipped
* for years as 0x11999AL (an extra hex digit, ~17.6 in Q48.16, an order of
* magnitude off from the documented 1.10 intent) -- found while migrating
* this constant to Kconfig (see rev x in
* docs/working/architecture/VM-FLEET-ATTRACTOR-DESIGN-20260705.md) and
* fixed on Bob's explicit instruction (rev y). */
#define STARFORTH_CONFIG_MINIMUM_PREFETCH_ROI_DEFAULT (0x1199AL)
/* Public macros (overridable via -D) */
#ifndef STRICT_PTR
#define STRICT_PTR STARFORTH_CONFIG_STRICT_PTR_DEFAULT
#endif
#ifndef VM_STRICT_PTR
#define VM_STRICT_PTR STRICT_PTR
#endif
#ifndef ENABLE_HOTWORDS_CACHE
#define ENABLE_HOTWORDS_CACHE STARFORTH_CONFIG_ENABLE_HOTWORDS_CACHE_DEFAULT
#endif
#ifndef ENABLE_PIPELINING
#define ENABLE_PIPELINING STARFORTH_CONFIG_ENABLE_PIPELINING_DEFAULT
#endif
#ifndef ROLLING_WINDOW_SIZE
#define ROLLING_WINDOW_SIZE STARFORTH_CONFIG_ROLLING_WINDOW_SIZE_DEFAULT
#endif
#ifndef TRANSITION_WINDOW_SIZE
#define TRANSITION_WINDOW_SIZE STARFORTH_CONFIG_TRANSITION_WINDOW_SIZE_DEFAULT
#endif
#ifndef ADAPTIVE_SHRINK_RATE
#define ADAPTIVE_SHRINK_RATE STARFORTH_CONFIG_ADAPTIVE_SHRINK_RATE_DEFAULT
#endif
#ifndef ADAPTIVE_MIN_WINDOW_SIZE
#define ADAPTIVE_MIN_WINDOW_SIZE STARFORTH_CONFIG_ADAPTIVE_MIN_WINDOW_SIZE_DEFAULT
#endif
#ifndef ADAPTIVE_CHECK_FREQUENCY
#define ADAPTIVE_CHECK_FREQUENCY STARFORTH_CONFIG_ADAPTIVE_CHECK_FREQUENCY_DEFAULT
#endif
#ifndef ADAPTIVE_GROWTH_THRESHOLD
#define ADAPTIVE_GROWTH_THRESHOLD STARFORTH_CONFIG_ADAPTIVE_GROWTH_THRESHOLD_DEFAULT
#endif
#ifndef INITIAL_DECAY_SLOPE_Q48
#define INITIAL_DECAY_SLOPE_Q48 STARFORTH_CONFIG_INITIAL_DECAY_SLOPE_Q48_DEFAULT
#endif
#ifndef DECAY_MIN_INTERVAL
#define DECAY_MIN_INTERVAL STARFORTH_CONFIG_DECAY_MIN_INTERVAL_DEFAULT
#endif
#ifndef DECAY_RATE_PER_US_Q16
#define DECAY_RATE_PER_US_Q16 STARFORTH_CONFIG_DECAY_RATE_PER_US_Q16_DEFAULT
#endif
#ifndef HEARTBEAT_THREAD_ENABLED
#define HEARTBEAT_THREAD_ENABLED STARFORTH_CONFIG_HEARTBEAT_THREAD_ENABLED_DEFAULT
#endif
#ifndef HEARTBEAT_TICK_NS
#define HEARTBEAT_TICK_NS STARFORTH_CONFIG_HEARTBEAT_TICK_NS_DEFAULT
#endif
#ifndef HEARTBEAT_INFERENCE_FREQUENCY
#define HEARTBEAT_INFERENCE_FREQUENCY STARFORTH_CONFIG_HEARTBEAT_INFERENCE_FREQUENCY_DEFAULT
#endif
#ifndef STADIUM_VM_MEMORY_PERCENT
#define STADIUM_VM_MEMORY_PERCENT STARFORTH_CONFIG_STADIUM_VM_MEMORY_PERCENT_DEFAULT
#endif
#ifndef STADIUM_CONTAINS_DEPTH_MAX
#define STADIUM_CONTAINS_DEPTH_MAX STARFORTH_CONFIG_STADIUM_CONTAINS_DEPTH_MAX_DEFAULT
#endif
#ifndef STADIUM_CAPACITY_TICK
#define STADIUM_CAPACITY_TICK STARFORTH_CONFIG_STADIUM_CAPACITY_TICK_DEFAULT
#endif
#ifndef STADIUM_MEMORY_PERCENT
#define STADIUM_MEMORY_PERCENT STARFORTH_CONFIG_STADIUM_MEMORY_PERCENT_DEFAULT
#endif
#ifndef STADIUM_WORD_HEAT_QUANTUM
#define STADIUM_WORD_HEAT_QUANTUM STARFORTH_CONFIG_STADIUM_WORD_HEAT_QUANTUM_DEFAULT
#endif
#ifndef STADIUM_WORD_COOL_RATE_Q48
#define STADIUM_WORD_COOL_RATE_Q48 STARFORTH_CONFIG_STADIUM_WORD_COOL_RATE_Q48_DEFAULT
#endif
#ifndef STADIUM_BLOCK_HEAT_QUANTUM
#define STADIUM_BLOCK_HEAT_QUANTUM STARFORTH_CONFIG_STADIUM_BLOCK_HEAT_QUANTUM_DEFAULT
#endif
#ifndef STADIUM_BLOCK_COOL_RATE_Q48
#define STADIUM_BLOCK_COOL_RATE_Q48 STARFORTH_CONFIG_STADIUM_BLOCK_COOL_RATE_Q48_DEFAULT
#endif
#ifndef STADIUM_BLOCK_TRACK_CAP_MULT
#define STADIUM_BLOCK_TRACK_CAP_MULT STARFORTH_CONFIG_STADIUM_BLOCK_TRACK_CAP_MULT_DEFAULT
#endif
#ifndef HEARTBEAT_CHECK_FREQUENCY
#define HEARTBEAT_CHECK_FREQUENCY STARFORTH_CONFIG_HEARTBEAT_CHECK_FREQUENCY_DEFAULT
#endif
#ifndef HEARTBEAT_WINDOW_TUNING_FREQUENCY
#define HEARTBEAT_WINDOW_TUNING_FREQUENCY STARFORTH_CONFIG_HEARTBEAT_WINDOW_TUNING_FREQUENCY_DEFAULT
#endif
#ifndef HEARTBEAT_SLOPE_VALIDATION_FREQUENCY
#define HEARTBEAT_SLOPE_VALIDATION_FREQUENCY STARFORTH_CONFIG_HEARTBEAT_SLOPE_VALIDATION_FREQUENCY_DEFAULT
#endif
#ifndef SSM_ENTROPY_HIGH_THRESHOLD
#define SSM_ENTROPY_HIGH_THRESHOLD STARFORTH_CONFIG_SSM_ENTROPY_HIGH_THRESHOLD_DEFAULT
#endif
#ifndef SSM_CV_HIGH_THRESHOLD
#define SSM_CV_HIGH_THRESHOLD STARFORTH_CONFIG_SSM_CV_HIGH_THRESHOLD_DEFAULT
#endif
#ifndef SSM_TEMPORAL_DECAY_THRESHOLD
#define SSM_TEMPORAL_DECAY_THRESHOLD STARFORTH_CONFIG_SSM_TEMPORAL_DECAY_THRESHOLD_DEFAULT
#endif
#ifndef SSM_TEMPORAL_DECAY_LOW_THRESHOLD
#define SSM_TEMPORAL_DECAY_LOW_THRESHOLD STARFORTH_CONFIG_SSM_TEMPORAL_DECAY_LOW_THRESHOLD_DEFAULT
#endif
#ifndef SSM_HYSTERESIS_TICKS
#define SSM_HYSTERESIS_TICKS STARFORTH_CONFIG_SSM_HYSTERESIS_TICKS_DEFAULT
#endif
#ifndef SPECULATION_THRESHOLD_Q48
#define SPECULATION_THRESHOLD_Q48 STARFORTH_CONFIG_SPECULATION_THRESHOLD_Q48_DEFAULT
#endif
#ifndef SPECULATION_DEPTH
#define SPECULATION_DEPTH STARFORTH_CONFIG_SPECULATION_DEPTH_DEFAULT
#endif
#ifndef MIN_SAMPLES_FOR_SPECULATION
#define MIN_SAMPLES_FOR_SPECULATION STARFORTH_CONFIG_MIN_SAMPLES_FOR_SPECULATION_DEFAULT
#endif
#ifndef MISPREDICTION_COST_Q48
#define MISPREDICTION_COST_Q48 STARFORTH_CONFIG_MISPREDICTION_COST_Q48_DEFAULT
#endif
#ifndef MINIMUM_PREFETCH_ROI
#define MINIMUM_PREFETCH_ROI STARFORTH_CONFIG_MINIMUM_PREFETCH_ROI_DEFAULT
#endif
#endif /* STARFORTH_CONFIG_H */
+86
View File
@@ -0,0 +1,86 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef STARFORTH_STARTUP_H
#define STARFORTH_STARTUP_H
#include "vm.h"
#include "cli.h"
/**
* @brief Initialize block I/O device from CLI configuration
*
* Opens disk image or allocates RAM disk based on CLIConfig.
* Must be called before vm_init().
*
* @param config CLI configuration
* @return 0 on success, 1 on error (logs messages internally)
*/
int startup_blkio_init(const CLIConfig* config);
/**
* @brief Clean up block I/O device
*
* Flushes and closes block device, frees RAM disk if allocated.
* Called automatically via atexit().
*/
void startup_blkio_cleanup(void);
/**
* @brief Initialize VM with defaults
*
* Initializes the VM state after block device is ready.
* @param vm Pointer to VM to initialize
* @return 0 on success, 1 on error
*/
int startup_vm_init(VM * vm);
/**
* @brief Initialize block subsystem
*
* Sets up block buffering and attaches blkio device.
* @param vm Pointer to initialized VM
* @return 0 on success, 1 on error
*/
int startup_block_subsys_init(VM * vm);
#endif /* STARFORTH_STARTUP_H */
+10
View File
@@ -0,0 +1,10 @@
#ifndef STARFORTH_VERSION_H
#define STARFORTH_VERSION_H
#define STARFORTH_VERSION "3.2.0"
#define STARFORTH_ARCH "x86_64"
#define STARFORTH_TARGET "standard"
#define STARFORTH_TIMESTAMP "2026-10-01T15:21:25-04:00"
#define STARFORTH_VERSION_FULL "StarForth v3.2.0 x86_64 standard 2026-10-01T15:21:25-04:00"
#endif /* STARFORTH_VERSION_H */
+887
View File
@@ -0,0 +1,887 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef VM_H
#define VM_H
/* Bare metal - no system headers, but we need FILE for heartbeat CSV export */
#include <stdint.h>
#include <stddef.h>
#if defined(__STDC_HOSTED__) && __STDC_HOSTED__
#include <stdio.h>
#endif
#include "platform_lock.h"
#include "starforth_config.h"
/* Forward declarations */
struct VM;
struct HotwordsCache_s; /* Physics hot-words cache (avoids circular include) */
typedef struct HotwordsCache_s HotwordsCache;
struct WordTransitionMetrics; /* Pipelining metrics (avoids circular include) */
typedef struct WordTransitionMetrics WordTransitionMetrics;
struct VMHostServices; /* Host allocator/time hooks (defined in vm_host.h) */
typedef struct VMHostServices VMHostServices;
struct InferenceOutputs; /* Adaptive tuning inference engine outputs (avoids circular include) */
typedef struct InferenceOutputs InferenceOutputs;
struct HeartbeatWorker; /* Background heartbeat dispatcher */
typedef struct HeartbeatWorker HeartbeatWorker;
/* Bare metal type definitions */
#if defined(__amd64__) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_X64)
typedef int64_t cell_t;
typedef uint64_t ucell_t;
#else
typedef signed long cell_t;
typedef unsigned long ucell_t;
#endif
/* ============================================================================
* Rolling Window of Truth (Embedded to avoid circular includes)
* ============================================================================
*
* Circular buffer capturing execution history for deterministic metric seeding.
* Defined here to avoid include dependency issues.
*
* Knob #7: ROLLING_WINDOW_SIZE (tunable initial/conservative size)
* - Default: 4096 (conservative capture of full pattern diversity at cold start)
* - Usage: make ROLLING_WINDOW_SIZE=8192 (or 2048, 16384, etc. for different workloads)
* - Starting large ensures statistical significance on cold systems
* - System automatically shrinks during execution if diminishing returns detected
* - Window is "warm" after ROLLING_WINDOW_SIZE executions
* (once warm, adaptive shrinking logic can reduce size if beneficial)
* - Knobs control adaptive shrinking behavior (rate, bounds, thresholds)
*/
typedef struct
{
uint32_t* execution_history; /* Circular buffer of word IDs */
uint32_t* snapshot_buffers[2]; /* Double-buffer snapshots for readers */
uint32_t window_pos; /* Current write position */
uint64_t total_executions; /* Lifetime execution counter */
int is_warm; /* 1 if window contains representative data */
/* Adaptive window sizing: continuous self-tuning during execution */
uint32_t effective_window_size; /* Current effective size (may shrink from initial) */
uint64_t last_pattern_diversity; /* Pattern diversity at last check */
uint64_t pattern_diversity_check_count; /* How many times we've checked diversity */
volatile uint32_t snapshot_index; /* Published snapshot buffer */
volatile uint32_t snapshot_pending; /* Writer signaled new data */
uint32_t snapshot_window_pos[2];
uint64_t snapshot_total_executions[2];
uint32_t snapshot_effective_window_size[2];
int snapshot_is_warm[2];
uint32_t adaptive_check_accumulator; /* Execution counter toward next check */
volatile uint32_t adaptive_pending; /* Heartbeat should run adaptive shrink */
} RollingWindowOfTruth;
typedef void (*word_func_t)(struct VM* vm);
/* ===== VM address model (Phase 1 scaffolding) ============================
* StarForth rule: addresses on the data stack are VM OFFSETS, not C pointers.
* vaddr_t is a byte offset into the VM's virtual address space.
* Implementations come in a later change; these are declarations only.
*/
typedef uint64_t vaddr_t;
/* Bounds check: nonzero if [addr, addr+len) is a valid VM range */
int vm_addr_ok(struct VM* vm, vaddr_t addr, size_t len);
/* Internal pointer materialization for subsystems (NOT for word sources) */
uint8_t* vm_ptr(struct VM* vm, vaddr_t addr);
/* Canonical memory accessors; all word code will funnel through these */
uint8_t vm_load_u8(struct VM* vm, vaddr_t addr);
void vm_store_u8(struct VM* vm, vaddr_t addr, uint8_t v);
cell_t vm_load_cell(struct VM* vm, vaddr_t addr); /* requires alignment */
void vm_store_cell(struct VM* vm, vaddr_t addr, cell_t v);
/* Numeric BASE accessors: keep the FORTH-visible BASE cell (vm->base_addr)
* and the host-mirror field (vm->base, read by number-output words via
* current_base()) in sync. Always use these instead of writing base_addr
* directly. */
unsigned vm_get_base(const struct VM* vm);
void vm_set_base(struct VM* vm, unsigned b);
/* Explicit stack<->offset conversions (keep intent obvious) */
static inline vaddr_t VM_ADDR(cell_t c) { return (vaddr_t)(uint64_t)c; }
static inline cell_t CELL(vaddr_t a) { return (cell_t)(int64_t)a; }
/* ======================================================================== */
#define STACK_SIZE 1024
#define DICTIONARY_SIZE 1024
#define WORD_ID_INVALID UINT32_MAX
#define VM_MEMORY_SIZE (5 * 1024 * 1024) /* 5 MB total VM memory */
#define INPUT_BUFFER_SIZE 1025
#define WORD_NAME_MAX 31
#define COMPILE_BUFFER_SIZE 1024
/* Block system configuration */
#define BLOCK_SIZE 1024 /* 1KB per block */
#define MAX_BLOCKS (VM_MEMORY_SIZE / BLOCK_SIZE) /* 5120 blocks from 5MB */
/* Block I/O window: FORTH-visible copies of block content inside vm->memory.
* Sits at the top of vm->memory, above the dictionary limit (2MB). BLOCK and
* BUFFER copy C-layer buffers into these slots and return VM offsets so that
* C@/C!/@ work correctly. UPDATE copies the slot back to the C layer. */
#define BLK_VM_SLOTS 4
#define BLK_VM_WINDOW_SIZE (BLK_VM_SLOTS * BLOCK_SIZE)
#define BLK_VM_WINDOW_BASE (VM_MEMORY_SIZE - BLK_VM_WINDOW_SIZE)
/* Memory layout constants */
#define DICTIONARY_BLOCKS 2048 /* First 2048 blocks (2MB) for dictionary */
#define DICTIONARY_MEMORY_SIZE (DICTIONARY_BLOCKS * BLOCK_SIZE)
#define USER_BLOCKS_START DICTIONARY_BLOCKS /* User blocks start at block 2048 */
/* Persistent log configuration */
#define LOG_LINE_MAX 64 /* Each log line is 64 bytes */
#define LOG_LINES_PER_BLOCK (BLOCK_SIZE / LOG_LINE_MAX) /* 16 lines per 1KB block */
#define LOG_BLOCKS_START 3072 /* Log starts at block 3072 */
#define LOG_BLOCKS_END 5120 /* Log ends at block 5120 (exclusive) */
#define LOG_BLOCKS (LOG_BLOCKS_END - LOG_BLOCKS_START) /* 2048 blocks (2MB) for logs */
#define LOG_LAYER1_MAX_LINES (LOG_BLOCKS * LOG_LINES_PER_BLOCK) /* 32768 max lines */
/* Word flags */
#define WORD_IMMEDIATE 0x80 /* Word executes immediately even in compile mode */
#define WORD_HIDDEN 0x40 /* Word is hidden from dictionary searches */
#define WORD_SMUDGED 0x20 /* Word is smudged (being defined) - FORTH-79 */
#define WORD_COMPILED 0x10 /* Word is user-compiled (not built-in) */
#define WORD_PINNED 0x08 /* Word's execution heat is pinned (cannot decay to zero) */
#define WORD_FROZEN 0x04 /* Word is frozen: execution heat does not decay (Phase 2) */
/* ACL (Access Control List) defaults - stub implementation */
#define ACL_USER_DEFAULT 0x01 /* Default access: users can execute and compile */
/* ACL mode constants */
#define ACL_MODE_TTL 0 /* Adaptive TTL countdown; 0 → calls ACL-RECHECK */
#define ACL_MODE_STRICT 1 /* Re-check every execution (STRICT mode) */
#define ACL_TTL_OPEN UINT32_MAX /* Permissive TTL before ACL.4th loads */
/* Physics model constants - execution heat drives optimization decisions */
#define SPIN_IDLE 0 /* Particle spin state: idle */
#define CHARGE_NEUTRAL 0 /* Particle charge state: neutral (execution heat acts as charge) */
/* Physics state flags */
#define PHYSICS_STATE_IMMEDIATE 0x01
#define PHYSICS_STATE_PINNED 0x02
#define PHYSICS_STATE_HIDDEN 0x04
#define PHYSICS_STATE_COMPILED 0x08
/* Phase 2: Decay mechanism configuration (tunable via starforth_config.h) */
#ifndef HEAT_CACHE_DEMOTION_THRESHOLD
#define HEAT_CACHE_DEMOTION_THRESHOLD 10 /* Demotion from hotwords cache threshold */
#endif
/* Heartbeat tuning frequencies (in ticks) */
/* Defaults live in starforth_config.h */
typedef struct
{
uint64_t published_tick; /* Tick counter when snapshot committed */
uint64_t published_ns; /* Host monotonic timestamp of publish */
uint32_t window_width; /* Effective rolling window size */
uint32_t actual_window_size; /* Actual ring buffer fill level: min(total_executions, ROLLING_WINDOW_SIZE) */
uint64_t decay_slope_q48; /* Active decay slope */
uint64_t hot_word_count; /* Words above heat threshold */
uint64_t stale_word_count; /* Words in cooling band */
uint64_t total_heat; /* Aggregate execution heat */
} HeartbeatSnapshot;
/* ===== Per-Tick Instrumentation (Phase 2: Multivariate Dynamics) ==========
*
* Lightweight circular buffer capture for multivariate systems analysis.
* Captures 7 key metrics every heartbeat tick (~1ms) with negligible overhead.
* Used to measure coupled dynamics, convergence, coupling strength, stability.
*/
typedef struct
{
uint32_t tick_number; /* Sequential tick counter */
uint64_t elapsed_ns; /* Total elapsed since run start */
uint64_t tick_interval_ns; /* Actual tick interval from prior tick */
uint32_t cache_hits_delta; /* Cache hits during this tick */
uint32_t bucket_hits_delta; /* Bucket hits during this tick */
uint32_t word_executions_delta; /* Words executed during this tick */
uint64_t hot_word_count; /* Words above heat threshold */
double avg_word_heat; /* Mean execution heat (Q48.16 / 65536) */
uint32_t window_width; /* Current rolling window size */
uint32_t actual_window_size; /* Actual ring buffer fill level: min(total_executions, ROLLING_WINDOW_SIZE) */
uint32_t predicted_label_hits; /* Successful context predictions */
double estimated_jitter_ns; /* Deviation from nominal tick */
uint8_t l8_mode; /* L8 Jacquard mode selector state (0-15) */
} HeartbeatTickSnapshot;
#define HEARTBEAT_TICK_BUFFER_SIZE 100000 /* ~100K ticks = ~100 seconds at 1kHz */
/* Phase 1 physics metadata */
typedef struct
{
uint16_t temperature_q8; /* Execution-heat-backed hotness scaled to Q8 */
uint64_t last_active_ns; /* Monotonic timestamp of last execution -- diagnostics only, never read for decay */
uint64_t last_decay_ns; /* Timestamp of last decay application -- diagnostics only, never read for decay */
uint64_t last_decay_tick; /* vm->heartbeat.tick_count as of last decay application -- the only clock Loop #3 decay reads */
uint32_t mass_bytes; /* Header + body footprint */
uint32_t avg_latency_ns; /* Rolling average latency (PROFILE_DETAILED+) */
uint8_t state_flags; /* Encoded execution traits (immediate, pinned, etc.) */
uint8_t acl_hint; /* Reserved for governance-driven ACLs */
uint16_t pubsub_mask; /* Reserved topic bitmap */
} DictPhysics;
/* ===== VM Heartbeat Architecture (Loop #3 & #5 Coordination) ============
*
* Centralized time-driven tuning dispatcher coordinating:
* - Loop #3: Heat decay validation (slope_validator, replaced by inference engine)
* - Loop #5: Context-aware window tuning (window_tuner, replaced by inference engine)
* - Unified Inference Engine: ANOVA early-exit + window width + decay slope inference
*
* Can run synchronously (now) or in background thread (future).
* Designed as plugin architecture for extensibility.
*/
typedef struct
{
uint64_t tick_count; /* Total heartbeat ticks since VM init */
uint64_t last_inference_tick; /* Last tick we ran full inference engine */
uint32_t check_counter; /* Counter to trigger vm_tick() every N executions */
int heartbeat_enabled; /* 1 = heartbeat active, 0 = disabled */
uint64_t tick_target_ns; /* Wake-up cadence (ns) */
volatile uint32_t snapshot_index; /* Published snapshot slot */
HeartbeatSnapshot snapshots[2]; /* Double-buffered read-only snapshots */
HeartbeatWorker* worker; /* Background thread context (opaque) */
/* === DoE Observation Counters (2^7 Factorial) === */
uint64_t inference_run_count; /* Times inference engine was invoked */
uint64_t early_exit_count; /* ANOVA early-exits (variance stable) */
uint64_t words_executed; /* Total word executions */
uint64_t dictionary_lookups; /* Dictionary search operations */
/* === Per-tick instrumentation (Phase 2: Multivariate Dynamics) === */
HeartbeatTickSnapshot* tick_buffer; /* Circular buffer of per-tick snapshots */
uint32_t tick_buffer_size; /* Allocated size (HEARTBEAT_TICK_BUFFER_SIZE) */
uint64_t tick_buffer_write_index; /* Current write position (wraps) */
uint64_t tick_count_total; /* Total ticks since run start (monotonic) */
uint64_t run_start_ns; /* Monotonic time at run start */
uint32_t tick_number_offset; /* Tick counter for this run */
/* === L8 Attractor Bucket Statistics === */
double bucket_sum_K; /* Sum of K values across bucket */
double bucket_sum_K_squared; /* Sum of K^2 for variance calculation */
double bucket_sum_window_variance; /* Sum of window variance across bucket */
double bucket_sum_heat_variance; /* Sum of heat variance across bucket */
uint64_t bucket_mode_transitions; /* Count of L8 mode transitions in bucket */
uint32_t bucket_collapse_flag; /* Flag indicating bucket collapse event */
uint32_t bucket_tick_count; /* Number of ticks in current bucket */
/* === M5 Time Trust Fields === */
uint64_t m5_time_trust; /* TIME-TRUST in Q48.16 format */
uint64_t m5_variance; /* Timing variance in Q48.16 format */
} HeartbeatState;
/* ===== Pipelining Global Metrics (Loop #4 & #5 Feedback) ================
*
* Aggregated metrics for speculative prefetch accuracy tracking.
* Used to guide window size tuning via binary chop (Loop #5).
*/
typedef struct
{
uint64_t prefetch_attempts; /* Total speculative prefetch calls */
uint64_t prefetch_hits; /* Successful hits (word was looked up next) */
uint64_t window_tuning_checks; /* How many times we've checked window size */
uint32_t last_checked_window_size; /* Window size at last tuning check */
double last_checked_accuracy; /* Prefetch accuracy at last check */
uint32_t suggested_next_size; /* What binary chop recommends trying */
} PipelineGlobalMetrics;
/* Dictionary entry - enhanced for FORTH-79 compatibility */
typedef struct DictEntry
{
struct DictEntry* link; /* Previous word (linked list) */
word_func_t func; /* Function pointer for execution */
cell_t flags; /* Word flags - increased to cell_t for alignment and absolute flags if needed */
uint8_t name_len; /* Length of name */
cell_t execution_heat; /* Execution frequency counter */
uint8_t acl_default; /* Access control list default permissions */
uint32_t acl_ttl; /* countdown; 0 → ACL-RECHECK; default 0 */
uint8_t acl_allow; /* cached decision: 1=allow 0=deny; default 1 */
uint8_t acl_mode; /* ACL_MODE_TTL=0, ACL_MODE_STRICT=1; default TTL */
uint8_t acl_pinned; /* 1=immutable forever; default 0 */
uint32_t word_id; /* Stable dictionary identifier */
DictPhysics physics; /* Physics properties */
WordTransitionMetrics* transition_metrics; /* Absolute pointer to metrics */
char name[]; /* Variable-length name */
} __attribute__((aligned(8))) DictEntry;
/* VM modes */
typedef enum
{
MODE_INTERPRET = 0,
MODE_COMPILE = 1
} vm_mode_t;
/* Per-VM vocabulary (VOCABULARY/DEFINITIONS/CONTEXT/CURRENT/FORTH) state.
* Was a set of file-scope statics in vocabulary_words.c, shared by every VM
* in the process -- one VM's VOCABULARY/DEFINITIONS/FORTH silently changed
* where every other VM looked up and defined words (proof/FINDINGS.md #1,
* item #5, "highest severity in the sweep"). Moved onto struct VM so each
* VM in the fleet keeps its own vocabulary chains, search order, and VM-cell
* addresses -- same shape as hold_addr/hold_pos below. */
#define VOCAB_FC_BUCKETS 256
typedef struct {
int initialized;
/* Host-side vocabulary chain heads */
DictEntry *forth_vocab; /* FORTH vocabulary head (root of system) */
DictEntry *context_vocab; /* CONTEXT vocabulary head */
DictEntry *current_vocab; /* CURRENT vocabulary head */
/* VM-visible variables: cell addresses in this VM's own memory */
vaddr_t context_var_addr; /* cell containing (DictEntry*) CONTEXT */
vaddr_t current_var_addr; /* cell containing (DictEntry*) CURRENT */
/* First-character search index per chain, lazily rebuilt when that
* chain's head changes (see vocab_find_word in vocabulary_words.c) */
DictEntry **ctx_fc[VOCAB_FC_BUCKETS];
size_t ctx_n[VOCAB_FC_BUCKETS];
DictEntry *ctx_cached_head;
DictEntry **forth_fc[VOCAB_FC_BUCKETS];
size_t forth_n[VOCAB_FC_BUCKETS];
DictEntry *forth_cached_head;
} VocabularyState;
/* Compile-time control-flow bookkeeping for IF/ELSE/THEN, BEGIN/WHILE/REPEAT/
* UNTIL/AGAIN, DO/?DO/LOOP/+LOOP/LEAVE, and CASE/OF/ENDOF/ENDCASE.
* Was a set of file-scope statics in control_words.c, shared by every VM in
* the process -- two VMs compiling control structures at overlapping times
* would corrupt each other's nesting state (proof/FINDINGS.md #1, item #1,
* "the two that would actually corrupt VM behavior in the live Tripod fleet
* today"). Moved onto struct VM so each VM keeps its own compile-time stacks. */
#define CF_STACK_MAX 64
typedef enum {
CF_BEGIN, /* Address of BEGIN target */
CF_IF, /* Address of IF's 0BRANCH literal */
CF_ELSE, /* Address of ELSE's BRANCH literal */
CF_WHILE, /* Address of WHILE's 0BRANCH literal (paired with prior BEGIN) */
CF_DO, /* Address of loop body start (back target for LOOP/+LOOP) */
CF_CASE, /* Marker for CASE statement start */
CF_OF /* Address of OF's 0BRANCH literal */
} cf_tag_t;
typedef struct {
size_t addr; /* byte offset in vm->memory used for patching/back edges */
cf_tag_t tag;
} cf_item_t;
typedef struct {
cf_item_t cf_stack[CF_STACK_MAX];
int cf_sp;
int cf_last_mode; /* reset cf_stack on mode transitions (INTERPRET/COMPILE) */
/* LEAVE patch sites: one entry per LEAVE, collected until LOOP/+LOOP */
size_t leave_addrs[CF_STACK_MAX];
int leave_sp;
/* One mark per DO nesting: leave_sp at DO/?DO entry, restored at LOOP/+LOOP */
int leave_mark_stack[CF_STACK_MAX];
int leave_mark_sp;
/* ENDOF patch sites: one entry per ENDOF, collected until ENDCASE */
size_t endof_addrs[CF_STACK_MAX];
int endof_sp;
/* One mark per CASE nesting: endof_sp at CASE entry, restored at ENDCASE */
int endof_mark_stack[CF_STACK_MAX];
int endof_mark_sp;
} ControlFlowState;
#ifdef __STARKERNEL__
#include "starkernel/vm_uuid.h" /* VMUuid -- item 4.2, VM.stadium_vm_id */
#include "starkernel/vm_identity.h" /* VMIdentity -- FABRIC-2.md §F.2/§F.16 */
/* Saved interpreter state for VM lifecycle calls (BIRTH, VM-EXEC, START).
* Dynamically allocated; grows on demand via sf_realloc. */
typedef struct {
int rsp;
int exit_colon;
int ecw_nesting;
} VMCallState;
#endif
/**
* @struct VM
* @brief Main virtual machine state container
*
* Contains all state information for a StarForth VM instance including stacks,
* dictionary, memory, and execution state.
*/
typedef struct VM
{
/** @name Stack Management
* @{
*/
cell_t data_stack[STACK_SIZE]; /**< Parameter stack storage */
cell_t return_stack[STACK_SIZE]; /**< Return stack storage */
int dsp; /**< Data stack pointer */
int rsp; /**< Return stack pointer */
int exit_colon; /**< Exit flag for colon definitions */
int abort_requested; /**< ABORT flag for immediate termination */
int ecw_nesting; /**< Depth of execute_colon_word call stack; >0 means inside a colon word */
volatile int doe_row_printing; /**< Non-zero while EMIT-ROW assembles a row; heartbeat output must defer */
uint8_t emergency_console; /**< 1 = fault handler active; bypasses all ACL checks (C-only write) */
uint8_t zuse_session; /**< 1 = zuse authenticated at console; shows zuse)ok> prompt */
uint8_t acl_skip; /**< 1 = skip all ACL hooks (Ananke enforcement VM; prevents recursion) */
uint8_t zuse_cert_installed; /**< 1 = zuse_cert_seed/pubkey hold a real minted cert (one-time fuse) */
uint8_t zuse_cert_seed[32]; /**< Ed25519 seed, this instance's Zuse identity. C-only write via
* vm_zuse_cert_install(); no FORTH word can read or write it --
* the seed is the private key, never exposed past this struct. */
uint8_t zuse_cert_pubkey[32]; /**< Ed25519 public key derived from the seed at mint time, cached
* here so callers don't need to re-derive it. No FORTH word can
* write it -- deliberately kept out of the dictionary so ACL-PIN's
* redefinition-only guarantee can't be bypassed via >BODY on a
* CONSTANT (see FABRIC-2.md). Read-only FORTH access via
* ZUSE-PUBKEY@. */
uint8_t zuse_root_pubkey_known; /**< 1 = zuse_root_pubkey holds a real value, loaded from the
* persistent genesis-marker fence (zuse_genesis_marker_t,
* capsule_zuse_boot_load_root_pubkey()) independently of whether
* Zuse's own thumbdrive is attached this boot. Deliberately
* separate from zuse_cert_installed/zuse_cert_seed above -- that
* pair is a one-way fuse gating MINT (needs her live private
* seed); this pair gates WIREBIND cert *verification* only (needs
* nothing but her already-public key), so a WIREBIND identity can
* be verified and attach without requiring Zuse's own live session
* this boot. Bob, 2026-09-09: "zuse is zuse, [each identity] is
* [itself]" -- one identity's attach must not depend on another's
* live presence. */
uint8_t zuse_root_pubkey[32]; /**< Ed25519 public key, loaded from the genesis-marker fence.
* Same value zuse_cert_pubkey ends up holding once Zuse herself
* attaches -- this is just available earlier and unconditionally. */
/** @} */
/** @name Dictionary Management
* @{
*/
const VMHostServices* host; /**< Host allocator/time services */
uint8_t* memory; /**< Unified VM memory buffer */
uintptr_t here; /**< Next free memory location (byte offset or absolute pointer if M7) */
DictEntry* latest; /**< Most recent word */
DictEntry* word_id_map[DICTIONARY_SIZE]; /**< Stable ID → entry map for speculation */
uint32_t recycled_word_ids[DICTIONARY_SIZE]; /**< Reusable ID stack for FORGET */
uint32_t recycled_word_id_count; /**< Depth of recycled stack */
uint32_t next_word_id; /**< Next fresh ID when recycle stack empty */
/* Dictionary protection fence: words at/older than this are protected from FORGET */
DictEntry* dict_fence_latest;
size_t dict_fence_here;
sf_mutex_t dict_lock; /**< Protects dictionary structural mutations */
/* Input system */
char input_buffer[INPUT_BUFFER_SIZE];
size_t input_length;
size_t input_pos;
/* Compiler state */
vm_mode_t mode;
DictEntry* compiling_word; /* Word being compiled */
int interpreter_enabled;
/* Compilation support */
char current_word_name[WORD_NAME_MAX + 1];
cell_t* compile_buffer;
size_t compile_pos;
size_t compile_size;
/* FORTH-79 Dictionary manipulation support */
cell_t state_var; /* STATE variable (0 interpret, -1 compile) */
/* Execution bookkeeping */
DictEntry* current_executing_entry;
cell_t* ip; /* direct-threaded instruction pointer (points into memory in CELLS) */
/* VM state */
int error;
int halted;
/* Numeric base (Forth BASE). Default 10. */
cell_t base;
/* TIB stuff */
unsigned char* tib_buf; /* pointer to TIB buffer (host alloc for now) */
size_t tib_cap; /* capacity in bytes */
cell_t* in_var; /* >IN (legacy; will migrate to VM addr) */
cell_t* span_var; /* SPAN (legacy; will migrate to VM addr) */
/* Block system (VM-backed variables / addresses) */
vaddr_t scr_addr;
vaddr_t state_addr; /* VM cell holding STATE (0=interp, -1=compile) */
vaddr_t base_addr; /* VM cell: numeric base (2..36), default 10 */
vaddr_t hold_addr; /* VM memory offset of the 64-byte pictured-number hold buffer */
int hold_pos; /* Current fill count in hold buffer (<# ... #> state) */
VocabularyState vocab; /* Per-VM VOCABULARY/DEFINITIONS/CONTEXT/CURRENT state */
ControlFlowState cf; /* Per-VM compile-time control-flow bookkeeping */
/* Block I/O window — BLK_VM_SLOTS slots at BLK_VM_WINDOW_BASE in vm->memory.
* BLOCK/BUFFER copy C-layer buffers here; UPDATE copies back; lbn==0 means empty. */
uint32_t blk_vm_lbn[BLK_VM_SLOTS]; /* LBN currently in each slot (0 = empty) */
uint8_t *blk_vm_cbuf[BLK_VM_SLOTS]; /* C buffer pointer from blk_get_buffer */
uint8_t blk_vm_dirty[BLK_VM_SLOTS]; /* 1 = vm->memory modified since last sync */
int blk_vm_next; /* Round-robin eviction cursor */
uint64_t blk_vm_epoch; /* blk_subsys_epoch() as of last cache validation --
* see block_words.c's blk_vm_find() */
/** @name Physics Hot-Words Cache
* @{
*/
HotwordsCache* hotwords_cache; /**< Frequency-driven dictionary acceleration cache */
/** @} */
/** @name Rolling Window of Truth (Deterministic Metrics)
* @{
*/
RollingWindowOfTruth rolling_window; /**< Execution history for reproducible seeding */
/** @} */
/** @name Phase 2: Heat-Aware Dictionary Optimization
* @{
*/
cell_t heat_threshold_25th; /**< 25th percentile heat (bucket search priority) */
cell_t heat_threshold_50th; /**< 50th percentile heat */
cell_t heat_threshold_75th; /**< 75th percentile heat */
uint64_t last_bucket_reorg_ns; /**< When we last reorganized buckets */
int lookup_strategy; /**< 0=naive, 1=heat-aware, 2=inference-reorg */
/** @} */
/** @name VM Heartbeat (Loop #3 & #5 Coordination)
* @{
*/
HeartbeatState heartbeat; /**< Centralized time-driven tuning dispatcher */
uint32_t heartbeat_decay_cursor_id; /**< Continuation cursor for background decay sweeps */
/** @} */
/** @name Pipelining Global Metrics (Loop #4 & #5 Feedback)
* @{
*/
PipelineGlobalMetrics pipeline_metrics; /**< Aggregated prefetch accuracy tracking */
/** @} */
/** @name Physics Loop #3: Adaptive Heat Decay Tuning
* @{
*/
uint64_t decay_slope_q48; /**< Current decay rate (Q48.16 fixed-point, starts at 2:1 = 131072) */
uint64_t last_decay_check_ns; /**< Timestamp of last decay effectiveness measurement */
uint64_t total_heat_at_last_check; /**< Total dictionary heat at last check (for slope inference) */
uint64_t hot_word_count_at_check; /**< Hot words observed at last inference */
uint64_t stale_word_count_at_check; /**< Stale words at last check (for trend analysis) */
uint32_t word_count_at_check; /**< Total word count at last check (for ratio calculation) */
int decay_slope_direction; /**< -1 = decrease slope, 0 = stable, +1 = increase slope (inference) */
sf_mutex_t tuning_lock; /**< Guards shared tuning knobs (slope, snapshots) */
/** @} */
/** @name Unified Inference Engine (Phase 2: Replacing Loops #3 & #5)
* @{
*/
InferenceOutputs* last_inference_outputs; /**< Cached inference outputs (for ANOVA early-exit and tuning application) */
/** @} */
/** @name SSM L8: Jacquard Mode Selector (Steady State Machine)
* @{
*/
void* ssm_l8_state; /**< Opaque pointer to ssm_l8_state_t (avoid circular include) */
void* ssm_config; /**< Opaque pointer to ssm_config_t (L3/L4 mode bits) */
/** @} */
#ifdef __STARKERNEL__
/** @name VM Lifecycle Call State Stack
* @{
*/
VMCallState *call_stack; /**< Dynamically-allocated lifecycle state stack */
int call_sp; /**< Current depth (0 = empty) */
int call_stack_cap; /**< Allocated capacity in entries */
int call_stack_max; /**< High-water mark depth (DoE metric) */
/** @} */
/** @name Native execution stack (FABRIC-3.md §XXVIII, Stage 1, 2026-09-13)
* @{
*/
uint64_t native_stack_paddr; /**< Physical base (for teardown); 0 = not allocated */
uint64_t native_stack_guard_vaddr; /**< Virtual base of the whole guarded region */
uint64_t native_stack_top; /**< Top of the allocated region (fixed, for the
* initial frame construction); 0 = not allocated. */
uint64_t native_stack_saved_sp; /**< Stage 2 (FABRIC-3.md §XXVIII, 2026-09-13): the
* parked SP of a live, switched-out register
* context on this VM's own native stack. 0 = never
* entered (no context parked yet); non-zero only
* while VMRegistryEntry.state ==
* VM_STATE_SWITCHED_OUT for this VM. */
struct VM *native_switch_back_to; /**< Stage 3 correction (FABRIC-3.md §XXVIII,
* 2026-09-13): who most recently switched INTO
* this VM. Updated by sk_vm_context_switch() on
* every switch-in (first-ever or resumed), so the
* placeholder trampoline (sk_vm_switch_entry())
* always yields back to whoever actually switched
* in, not a stale value captured once at this
* VM's very first entry -- a single global there
* let a later switch-in from a different VM get
* silently bounced to the original caller instead
* of itself. */
/** @} */
/** @name Stadium Identity (item 4.2, FABRIC-0.md §25.5)
* @{
*/
VMUuid stadium_vm_id; /**< This VM's own identity, set once at birth from
* VMRegistryEntry.vm_id. Lets vm_core.c's
* stadium_word_dispatch() call sites attribute
* word-execution heat to the VM that actually
* ran the word, instead of item 4.1's hardcoded
* vm_uuid_hera(). */
/** @} */
/** @name Owner identity + ACL capabilities (FABRIC-2.md §F.2/§F.16)
* @{
*/
VMIdentity identity; /**< This VM's owning identity. installed=0 (the
* zero-init default) means no lock -- every
* VM today, until D.5's per-VM-identity work
* and CERTVERIFY/MINT actually populate it. */
/** @} */
#endif
} VM;
/** @name Core VM Functions
* @{
*/
/*
* @brief Initialize a new VM instance
* @param vm Pointer to VM structure to initialize
*/
void vm_init(VM* vm);
/**
* @brief Initialize VM with explicit host services
* @param vm Pointer to VM structure to initialize
* @param host Host services (allocator/time). If NULL, uses build default.
*/
void vm_init_with_host(VM* vm, const VMHostServices *host);
/*
* @brief Interpret a string of Forth code
* @param vm VM instance to use
* @param input String containing Forth code to interpret
*/
void vm_interpret(VM* vm, const char* input);
void vm_enable_interpreter(VM* vm);
/*
* @brief Start the VM's read-eval-print loop
* @param vm VM instance to run
* @param script_mode If 1, suppress prompts and "ok" output (for piped input)
*/
void vm_repl(VM* vm, int script_mode);
/** @} */
/* Stack operations */
void vm_push(VM* vm, cell_t value);
cell_t vm_pop(VM* vm);
void vm_rpush(VM* vm, cell_t value);
cell_t vm_rpop(VM* vm);
/* Dictionary operations */
DictEntry* vm_find_word(VM* vm, const char* name, size_t len);
/* Arbitrated bucket scan: returns the newest visible (non-hidden, non-smudged)
* entry matching name among ALL candidates in the bucket. Buckets can be
* reordered by heat (dict_reorganize_buckets_by_heat), so first-match scans
* are unsound for shadowed/redefined names; every lookup path must resolve
* through this. Age is arbitrated via the vm->latest link chain. */
DictEntry* vm_dict_resolve_in_bucket(VM* vm, DictEntry** bucket, size_t n,
const char* name, size_t len);
DictEntry* vm_create_word(VM* vm, const char* name, size_t len, word_func_t func);
void vm_make_immediate(VM* vm);
void vm_bootstrap_root_vocabulary(VM *vm, const char *name);
void vm_hide_word(VM* vm);
void vm_smudge_word(VM* vm); /* Added for FORTH-79 SMUDGE */
void vm_pin_execution_heat(VM* vm); /* Pin execution heat (prevent decay) */
void vm_unpin_execution_heat(VM* vm); /* Unpin execution heat (allow decay) */
/* Enhanced dictionary search functions */
DictEntry* vm_dictionary_find_by_func(VM* vm, word_func_t func);
DictEntry* vm_dictionary_find_latest_by_func(VM* vm, word_func_t func);
DictEntry* vm_dictionary_lookup_by_word_id(VM* vm, uint32_t word_id);
/* Validate that `candidate` is a live, currently-registered dictionary entry
* for this VM (walks vm->latest under dict_lock; never dereferences an
* unverified pointer). Use before executing/inspecting through any xt/addr
* popped off the data stack (EXECUTE, ENTROPY@/!, etc). */
int vm_dict_entry_ok(VM* vm, DictEntry* candidate);
void vm_dictionary_track_entry(VM* vm, DictEntry* entry);
void vm_dictionary_untrack_entry(VM* vm, DictEntry* entry);
cell_t* vm_dictionary_get_data_field(DictEntry* entry);
void vm_compile_word(VM* vm, DictEntry* entry);
/* Zuse cert one-time install (blows the fuse). seed and pubkey are each 32
* bytes, copied into the VM struct. Returns 0 on success, -1 if already
* installed -- a second call is a caller bug, not a runtime error to
* recover from silently. C-only: no FORTH word wraps this. */
int vm_zuse_cert_install(VM* vm, const uint8_t seed[32], const uint8_t pubkey[32]);
/* Memory management */
void* vm_allot(VM* vm, size_t bytes);
void vm_align(VM* vm);
/* Input parsing */
int vm_parse_word(VM* vm, char* word, size_t max_len);
int vm_parse_number(VM* vm, const char* str, cell_t* value);
/* Compilation */
void vm_enter_compile_mode(VM* vm, const char* name, size_t len);
void vm_exit_compile_mode(VM* vm);
/* Colon word execution (exposed for SEE decompiler) */
void execute_colon_word(VM* vm);
void vm_compile_call(VM* vm, word_func_t func);
void vm_compile_literal(VM* vm, cell_t value);
void vm_compile_exit(VM* vm);
void vm_interpret_word(VM* vm, const char* word_str, size_t len);
/* Block system integration */
void* vm_get_block_addr(VM* vm, int block_num);
int vm_addr_to_block(VM* vm, void* addr);
/* Testing functions */
void vm_run_smoke_tests(VM* vm);
/* Add cleanup function declaration */
void vm_cleanup(VM* vm);
/* ===== VM Heartbeat (Time-Driven Tuning Dispatcher) =====================
* Centralized coordination of Loop #3 and Loop #5 operations.
* Called periodically from main execution loop.
*/
void vm_tick(VM* vm); /* Main heartbeat dispatcher */
void vm_tick_inference_engine(VM* vm); /* Unified inference engine (Phase 2, replaces Loops #3 & #5) */
void vm_tick_window_tuner(VM* vm); /* Loop #5: Context-aware window tuning (legacy, kept for compatibility) */
void vm_tick_slope_validator(VM* vm); /* Loop #3: Heat decay validation (legacy, kept for compatibility) */
void vm_snapshot_read(const VM* vm, HeartbeatSnapshot* out_snapshot); /* Copy latest heartbeat snapshot */
/* Real-time heartbeat tick emission (Phase 2: Multivariate Dynamics) */
void heartbeat_capture_tick_snapshot(VM* vm, HeartbeatTickSnapshot* snapshot); /* Capture current VM metrics into tick snapshot */
void heartbeat_emit_tick_row(VM* vm, HeartbeatTickSnapshot* snapshot); /* Emit tick snapshot as CSV row to stderr */
/* Start background heartbeat thread and/or enable heartbeat processing. */
void vm_heartbeat_start(VM* vm);
/* ===== Performance optimizations for release builds ==================== */
#ifdef STARFORTH_PERFORMANCE
/* Fast inline stack operations - skip bounds checking in performance builds */
static inline void vm_push_fast(VM* vm, cell_t value)
{
vm->data_stack[++vm->dsp] = value;
}
static inline cell_t vm_pop_fast(VM* vm)
{
return vm->data_stack[vm->dsp--];
}
static inline void vm_rpush_fast(VM* vm, cell_t value)
{
vm->return_stack[++vm->rsp] = value;
}
static inline cell_t vm_rpop_fast(VM* vm)
{
return vm->return_stack[vm->rsp--];
}
/* Performance macros - use fast paths in hot code */
#define VM_PUSH(vm, val) vm_push_fast(vm, val)
#define VM_POP(vm) vm_pop_fast(vm)
#define VM_RPUSH(vm, val) vm_rpush_fast(vm, val)
#define VM_RPOP(vm) vm_rpop_fast(vm)
/* Likely/unlikely branch prediction hints */
#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
/* Safe versions with bounds checking for debug builds */
#define VM_PUSH(vm, val) vm_push(vm, val)
#define VM_POP(vm) vm_pop(vm)
#define VM_RPUSH(vm, val) vm_rpush(vm, val)
#define VM_RPOP(vm) vm_rpop(vm)
#define LIKELY(x) (x)
#define UNLIKELY(x) (x)
#endif
#endif /* VM_H */
+130
View File
@@ -0,0 +1,130 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @file vm_api.h
* @brief StarForth Virtual Machine Public API
*
* This header provides the public interface for interacting with the StarForth
* Virtual Machine, particularly focusing on input handling and memory management.
*/
#ifndef VM_API_H
#define VM_API_H
#include "vm.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup vm_input VM Input Operations
* @{
* Public API for managing VM input operations including Terminal Input Buffer (TIB),
* input pointer (>IN), and input span (SPAN) handling.
*/
/*
* @brief Ensures input buffer initialization
* @param vm Pointer to VM instance
* @return 0 on success, non-zero on failure
*
* Allocates and initializes input buffer if not already done.
*/
int vm_input_ensure(VM * vm);
/*
* @brief Retrieves Terminal Input Buffer address
* @param vm Pointer to VM instance
* @return Pointer to TIB buffer
*/
unsigned char *vm_input_tib(VM * vm);
/*
* @brief Retrieves input pointer address
* @param vm Pointer to VM instance
* @return Pointer to >IN cell
*/
cell_t *vm_input_in(VM * vm);
/*
* @brief Retrieves span cell address
* @param vm Pointer to VM instance
* @return Pointer to SPAN cell
*/
cell_t *vm_input_span(VM * vm);
/*
* @brief Loads a line into TIB
* @param vm Pointer to VM instance
* @param src Source string to copy
* @param n Number of bytes to copy
*
* Copies data into TIB, updates SPAN, and resets >IN.
*/
void vm_input_load_line(VM *vm, const char *src, size_t n);
/*
* @brief Gets current input source
* @param vm Pointer to VM instance
* @param out_addr Output parameter for source address
* @param out_len Output parameter for source length
*
* Returns SOURCE pair (address, length) for current input.
*/
void vm_input_source(VM * vm, cell_t * out_addr, cell_t * out_len);
/*
* @brief Converts host pointer to Forth address
* @param vm Pointer to VM instance
* @param p Host memory pointer
* @return Forth memory address (offset into vm->memory)
*/
cell_t vm_addr_from_ptr(VM *vm, void *p);
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* VM_API_H */
+606
View File
@@ -0,0 +1,606 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
*** StarForth ***
vm_asm_opt.h - x86_64 Inline Assembly Optimizations
Compatible with: Linux, bare metal x86_64
HISTORICAL: the "L4Re Compatibility" notes throughout this file describe
a target that was supported through mid-2026. L4Re support has since
been removed as an active target; these notes are retained as historical
design rationale (pure computational/unprivileged code has no reason to
become L4Re-incompatible, so the analysis still holds, it's just no
longer relevant to any build).
This work is released under license terms in LICENSE file.
No warranty. Use at your own risk.
*/
#ifndef VM_ASM_OPT_H
#define VM_ASM_OPT_H
#include "vm.h"
#include <stdint.h>
/*
* Configuration: Define USE_ASM_OPT=1 to enable assembly optimizations
* Define USE_ASM_OPT=0 or leave undefined to use standard C implementations
*/
#ifndef USE_ASM_OPT
#define USE_ASM_OPT 0
#endif
/**
* @defgroup stack_ops Stack Operations
* @{
*
* @brief Optimized stack manipulation operations
* @details Critical optimizations for vm_push, vm_pop, vm_rpush, vm_rpop
*
* Impact: EXTREME - These are called on nearly every Forth word execution
*
* Benefits:
* - Eliminates function call overhead (inline)
* - Reduces branch mispredictions with conditional moves
* - Keeps frequently-used values in registers
* - Uses lea for pointer arithmetic (no flags affected)
*
* L4Re Compatibility: YES - Pure computational code, no syscalls
*/
#if USE_ASM_OPT
/* vm_push optimized - Data stack push */
/**
* @brief Optimized data stack push operation
* @param vm Pointer to VM context
* @param value Value to push onto stack
* @return void
* @note Sets vm->error on stack overflow
*/
static inline void vm_push_asm(VM *vm, cell_t value) {
int error = 0;
__asm__ __volatile__(
/* Load current dsp into register with sign extension */
"movslq %[dsp], %%rax\n\t"
/* Check for overflow: dsp >= STACK_SIZE-1 (1023) */
"cmpq $1022, %%rax\n\t"
"jg 1f\n\t" /* Jump if overflow */
/* No overflow: increment dsp */
"addq $1, %%rax\n\t" /* rax = dsp + 1 */
"movl %%eax, %[dsp]\n\t" /* Save new dsp (32-bit) */
/* Store value: vm->data_stack[dsp] = value (dsp already incremented) */
"movq %[val], (%[stack], %%rax, 8)\n\t"
/* Success: set error = 0 */
"xorl %[err], %[err]\n\t"
"jmp 2f\n\t"
/* Overflow path */
"1:\n\t"
"movl $1, %[err]\n\t"
"2:\n\t"
: [dsp]"+m"(vm->dsp),
[err]"=&r"(error)
: [val]"r"(value),
[stack]"r"(vm->data_stack)
: "rax", "cc", "memory"
);
if (__builtin_expect(error, 0)) {
vm->error = 1;
/* Note: Logging omitted for performance - enable in debug builds only */
}
}
/* vm_pop optimized - Data stack pop */
/**
* @brief Optimized data stack pop operation
* @param vm Pointer to VM context
* @return Top value from stack, 0 on underflow
* @note Sets vm->error on stack underflow
*/
static inline cell_t vm_pop_asm(VM *vm) {
cell_t value = 0;
int error = 0;
__asm__ __volatile__(
/* Load current dsp with sign extension */
"movslq %[dsp], %%rax\n\t"
/* Check for underflow: dsp < 0 */
"testq %%rax, %%rax\n\t"
"js 1f\n\t" /* Jump if negative (underflow) */
/* No underflow: load value */
"movq (%[stack], %%rax, 8), %[val]\n\t"
/* Decrement dsp */
"subq $1, %%rax\n\t"
"movl %%eax, %[dsp]\n\t"
/* Success: set error = 0 */
"xorl %[err], %[err]\n\t"
"jmp 2f\n\t"
/* Underflow path */
"1:\n\t"
"movl $1, %[err]\n\t"
"xorq %%rax, %%rax\n\t" /* Return 0 on error */
"movq %%rax, %[val]\n\t"
"2:\n\t"
: [dsp]"+m"(vm->dsp),
[val]"=&r"(value),
[err]"=&r"(error)
: [stack]"r"(vm->data_stack)
: "rax", "cc", "memory"
);
if (__builtin_expect(error, 0)) {
vm->error = 1;
}
return value;
}
/* vm_rpush optimized - Return stack push */
/**
* @brief Optimized return stack push operation
* @param vm Pointer to VM context
* @param value Value to push onto return stack
* @return void
* @note Sets vm->error on stack overflow
*/
static inline void vm_rpush_asm(VM *vm, cell_t value) {
int error = 0;
__asm__ __volatile__(
"movslq %[rsp], %%rax\n\t"
"cmpq $1022, %%rax\n\t"
"jg 1f\n\t"
"addq $1, %%rax\n\t"
"movl %%eax, %[rsp]\n\t"
"movq %[val], (%[stack], %%rax, 8)\n\t"
"xorl %[err], %[err]\n\t"
"jmp 2f\n\t"
"1:\n\t"
"movl $1, %[err]\n\t"
"2:\n\t"
: [rsp]"+m"(vm->rsp),
[err]"=&r"(error)
: [val]"r"(value),
[stack]"r"(vm->return_stack)
: "rax", "cc", "memory"
);
if (__builtin_expect(error, 0)) {
vm->error = 1;
}
}
/* vm_rpop optimized - Return stack pop */
/**
* @brief Optimized return stack pop operation
* @param vm Pointer to VM context
* @return Top value from return stack, 0 on underflow
* @note Sets vm->error on stack underflow
*/
static inline cell_t vm_rpop_asm(VM *vm) {
cell_t value = 0;
int error = 0;
__asm__ __volatile__(
"movslq %[rsp], %%rax\n\t"
"testq %%rax, %%rax\n\t"
"js 1f\n\t"
"movq (%[stack], %%rax, 8), %[val]\n\t"
"subq $1, %%rax\n\t"
"movl %%eax, %[rsp]\n\t"
"xorl %[err], %[err]\n\t"
"jmp 2f\n\t"
"1:\n\t"
"movl $1, %[err]\n\t"
"xorq %%rax, %%rax\n\t"
"movq %%rax, %[val]\n\t"
"2:\n\t"
: [rsp]"+m"(vm->rsp),
[val]"=&r"(value),
[err]"=&r"(error)
: [stack]"r"(vm->return_stack)
: "rax", "cc", "memory"
);
if (__builtin_expect(error, 0)) {
vm->error = 1;
}
return value;
}
/* ============================================================================
* OPTIMIZATION 2: Fast Arithmetic with Overflow Detection
* ============================================================================
*
* IMPACT: MEDIUM-HIGH
*
* Benefits:
* - Uses CPU overflow flag instead of manual checks
* - Eliminates branches in common (no overflow) case
* - Single instruction for addition/subtraction with flag check
*
* L4Re Compatibility: YES
* ============================================================================
*/
/* Fast add with overflow detection */
/**
* @brief Performs addition with hardware overflow detection
* @param a First operand
* @param b Second operand
* @param result Pointer to store result
* @return 1 if overflow occurred, 0 otherwise
*/
static inline int vm_add_check_overflow(cell_t a, cell_t b, cell_t *result) {
cell_t res;
int overflow;
__asm__(
"movq %[a], %%rax\n\t"
"addq %[b], %%rax\n\t"
"movq %%rax, %[res]\n\t"
"seto %%al\n\t" /* Set AL to 1 if overflow occurred */
"movzbl %%al, %[ovf]\n\t"
: [res]"=r"(res),
[ovf]"=r"(overflow)
: [a]"r"(a),
[b]"r"(b)
: "rax", "cc"
);
*result = res;
return overflow;
}
/* Fast subtract with overflow detection */
/**
* @brief Performs subtraction with hardware overflow detection
* @param a First operand (minuend)
* @param b Second operand (subtrahend)
* @param result Pointer to store result
* @return 1 if overflow occurred, 0 otherwise
*/
static inline int vm_sub_check_overflow(cell_t a, cell_t b, cell_t *result) {
cell_t res;
int overflow;
__asm__(
"movq %[a], %%rax\n\t"
"subq %[b], %%rax\n\t"
"movq %%rax, %[res]\n\t"
"seto %%al\n\t" /* Set AL to 1 if overflow occurred */
"movzbl %%al, %[ovf]\n\t"
: [res]"=r"(res),
[ovf]"=r"(overflow)
: [a]"r"(a),
[b]"r"(b)
: "rax", "cc"
);
*result = res;
return overflow;
}
/* Fast multiply with double-width result for */ /* MOD operations */
/**
* @brief Performs double-width multiplication
* @param a First operand
* @param b Second operand
* @param hi Pointer to store high 64 bits
* @param lo Pointer to store low 64 bits
*/
static inline void vm_mul_double(cell_t a, cell_t b, cell_t *hi, cell_t *lo) {
__asm__(
"movq %[a], %%rax\n\t"
"imulq %[b]\n\t" /* rdx:rax = rax * b (signed) */
"movq %%rax, %[lo]\n\t"
"movq %%rdx, %[hi]\n\t"
: [lo]"=r"(*lo),
[hi]"=r"(*hi)
: [a]"r"(a),
[b]"r"(b)
: "rax", "rdx", "cc"
);
}
/* Fast divide with remainder (for /MOD) */
/**
* @brief Performs division with remainder
* @param dividend Dividend value
* @param divisor Divisor value
* @param quotient Pointer to store quotient
* @param remainder Pointer to store remainder
*/
static inline void vm_divmod(cell_t dividend, cell_t divisor,
cell_t *quotient, cell_t *remainder) {
cell_t quot, rem;
__asm__(
"movq %[dividend], %%rax\n\t"
"cqo\n\t" /* Sign-extend rax into rdx:rax */
"idivq %[divisor]\n\t" /* rax = quotient, rdx = remainder */
"movq %%rax, %[quot]\n\t"
"movq %%rdx, %[rem]\n\t"
: [quot]"=r"(quot),
[rem]"=r"(rem)
: [dividend]"r"(dividend),
[divisor]"r"(divisor)
: "rax", "rdx", "cc"
);
*quotient = quot;
*remainder = rem;
}
/* ============================================================================
* OPTIMIZATION 3: Fast String Operations for Dictionary Lookup
* ============================================================================
*
* IMPACT: HIGH
*
* Benefits:
* - Uses hardware string comparison (rep cmpsb)
* - Processes bytes in parallel
* - Much faster than byte-by-byte C loop
*
* L4Re Compatibility: YES
* ============================================================================
*/
/* Fast string comparison - returns 0 if equal, non-zero otherwise */
/**
* @brief Hardware-accelerated string comparison
* @param s1 First string
* @param s2 Second string
* @param len Length to compare
* @return 0 if equal, non-zero otherwise
*/
static inline int vm_strcmp_asm(const char *s1, const char *s2, size_t len) {
int result;
__asm__(
"movq %[s1], %%rsi\n\t"
"movq %[s2], %%rdi\n\t"
"movq %[len], %%rcx\n\t"
"xorl %%eax, %%eax\n\t"
"repe cmpsb\n\t" /* Compare bytes while equal */
"je 1f\n\t" /* If all equal, result = 0 */
"movl $1, %%eax\n\t" /* Not equal, result = 1 */
"1:\n\t"
: "=a"(result)
: [s1]"r"(s1),
[s2]"r"(s2),
[len]"r"(len)
: "rsi", "rdi", "rcx", "cc", "memory"
);
return result;
}
/* Fast memory copy for block operations */
/**
* @brief Hardware-accelerated memory copy
* @param dest Destination buffer
* @param src Source buffer
* @param len Number of bytes to copy
*/
static inline void vm_memcpy_asm(void *dest, const void *src, size_t len) {
__asm__ __volatile__(
"movq %[dst], %%rdi\n\t"
"movq %[src], %%rsi\n\t"
"movq %[len], %%rcx\n\t"
"rep movsb\n\t" /* Copy bytes */
:
: [dst]"r"(dest),
[src]"r"(src),
[len]"r"(len)
: "rdi", "rsi", "rcx", "memory"
);
}
/* Fast memory zero for alignment padding */
/**
* @brief Hardware-accelerated memory zero
* @param dest Buffer to zero
* @param len Number of bytes to zero
*/
static inline void vm_memzero_asm(void *dest, size_t len) {
__asm__ __volatile__(
"movq %[dst], %%rdi\n\t"
"xorl %%eax, %%eax\n\t"
"movq %[len], %%rcx\n\t"
"rep stosb\n\t" /* Store zeros */
:
: [dst]"r"(dest),
[len]"r"(len)
: "rdi", "rax", "rcx", "memory"
);
}
/* ============================================================================
* OPTIMIZATION 4: Branchless Min/Max
* ============================================================================
*
* IMPACT: MEDIUM
*
* Benefits:
* - No branches = no mispredictions
* - Single cmov instruction
*
* L4Re Compatibility: YES
* ============================================================================
*/
/**
* @brief Branchless minimum calculation
* @param a First value
* @param b Second value
* @return Smaller of a and b
*/
static inline cell_t vm_min_asm(cell_t a, cell_t b) {
cell_t result;
__asm__(
"movq %[a], %%rax\n\t"
"movq %[b], %%rcx\n\t"
"cmpq %%rcx, %%rax\n\t"
"cmovg %%rcx, %%rax\n\t" /* Move if a > b */
"movq %%rax, %[res]\n\t"
: [res]"=r"(result)
: [a]"r"(a),
[b]"r"(b)
: "rax", "rcx", "cc"
);
return result;
}
/**
* @brief Branchless maximum calculation
* @param a First value
* @param b Second value
* @return Larger of a and b
*/
static inline cell_t vm_max_asm(cell_t a, cell_t b) {
cell_t result;
__asm__(
"movq %[a], %%rax\n\t"
"movq %[b], %%rcx\n\t"
"cmpq %%rcx, %%rax\n\t"
"cmovl %%rcx, %%rax\n\t" /* Move if a < b */
"movq %%rax, %[res]\n\t"
: [res]"=r"(result)
: [a]"r"(a),
[b]"r"(b)
: "rax", "rcx", "cc"
);
return result;
}
/* ============================================================================
* OPTIMIZATION 5: CPU Feature Detection
* ============================================================================
*
* For future SIMD optimizations
* L4Re Compatibility: YES (cpuid is unprivileged)
* ============================================================================
*/
/**
* @brief Wrapper for CPUID instruction
* @param leaf CPUID leaf number
* @param eax Pointer to store EAX result
* @param ebx Pointer to store EBX result
* @param ecx Pointer to store ECX result
* @param edx Pointer to store EDX result
*/
static inline void vm_cpuid(uint32_t leaf, uint32_t *eax, uint32_t *ebx,
uint32_t *ecx, uint32_t *edx) {
__asm__(
"cpuid"
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
: "a"(leaf)
);
}
/* Check for SSE4.2 support (for fast string operations) */
/**
* @brief Checks for SSE4.2 support
* @return 1 if SSE4.2 is supported, 0 otherwise
*/
static inline int vm_has_sse42(void) {
uint32_t eax, ebx, ecx, edx;
vm_cpuid(1, &eax, &ebx, &ecx, &edx);
return (ecx >> 20) & 1; /* Bit 20 = SSE4.2 */
}
#else /* !USE_ASM_OPT */
/* Fallback to standard implementations when assembly is disabled */
#define vm_push_asm(vm, val) vm_push(vm, val)
#define vm_pop_asm(vm) vm_pop(vm)
#define vm_rpush_asm(vm, val) vm_rpush(vm, val)
#define vm_rpop_asm(vm) vm_rpop(vm)
#endif /* USE_ASM_OPT */
/* ============================================================================
* L4Re SPECIFIC NOTES
* ============================================================================
*
* All optimizations above are L4Re-compatible because they:
* 1. Don't use privileged instructions
* 2. Don't make syscalls
* 3. Don't access hardware directly
* 4. Are pure computational optimizations
*
* For L4Re integration:
* - Compile with -march=native or -march=x86-64-v3 for modern CPUs
* - Use L4Re's memory allocators for VM memory
* - Consider using L4Re IPC for inter-VM communication
* - Profile using L4Re's performance counters
*
* Build flags for optimal performance:
* -DUSE_ASM_OPT=1 -O3 -march=native -flto
*
* For StarshipOS integration:
* - These functions can be used in both userspace and kernel contexts
* - No floating point operations (safe for kernel mode)
* - No stack-intensive operations (safe for small kernel stacks)
* ============================================================================
*/
#endif /* VM_ASM_OPT_H */
+783
View File
@@ -0,0 +1,783 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef VM_ASM_OPT_ARM64_H
#define VM_ASM_OPT_ARM64_H
#include "vm.h"
#include <stdint.h>
#ifdef __linux__
#include <sys/auxv.h> /* For getauxval, AT_HWCAP */
#include <asm/hwcap.h> /* For HWCAP_ASIMD */
#endif
/*
* Configuration: Define USE_ASM_OPT=1 to enable assembly optimizations
*/
#ifndef USE_ASM_OPT
#define USE_ASM_OPT 0
#endif
/*
* ARM64 Architecture Notes:
* =========================
*
* Registers (64-bit):
* - x0-x7: Argument/result registers
* - x8: Indirect result location
* - x9-x15: Temporary registers (caller-saved)
* - x16-x17: Intra-procedure-call temporary (IP0, IP1)
* - x18: Platform register (reserved on some platforms)
* - x19-x28: Callee-saved registers (must preserve)
* - x29: Frame pointer (FP)
* - x30: Link register (LR)
* - sp: Stack pointer
*
* 32-bit variants: w0-w30 (lower 32 bits of x0-x30)
*
* Advantages over x86_64:
* - More registers (31 general-purpose vs 16)
* - Conditional execution on most instructions
* - Load/store with auto-increment
* - Better power efficiency
*
* Raspberry Pi 4 specifics:
* - Cortex-A72 CPU (4 cores @ 1.5GHz)
* - ARMv8-A architecture
* - 32KB L1I + 32KB L1D per core
* - 1MB shared L2 cache
* - NEON SIMD support
*/
/* ============================================================================
* OPTIMIZATION 1: Stack Operations (vm_push, vm_pop, vm_rpush, vm_rpop)
* ============================================================================
*
* ARM64 advantages:
* - Conditional execution eliminates branches
* - Load/store with immediate offset
* - Post-increment addressing modes
*
* ============================================================================
*/
#if USE_ASM_OPT
/* vm_push optimized - Data stack push */
/**
* @brief Optimized assembly implementation of data stack push operation
* @param vm Pointer to VM instance
* @param value Value to push onto data stack
* @note Sets vm->error on stack overflow
*/
static inline void vm_push_asm(VM *vm, cell_t value) {
int error = 0;
__asm__ __volatile__(
/* Load current dsp */
"ldr w0, %[dsp]\n\t"
/* Check for overflow: dsp >= 1022 */
"cmp w0, #1022\n\t"
"b.ge 1f\n\t" /* Branch if >= */
/* No overflow: increment dsp */
"add w1, w0, #1\n\t" /* w1 = dsp + 1 */
"str w1, %[dsp]\n\t"
/* Store value: vm->data_stack[dsp+1] = value */
/* Calculate address: base + (dsp+1)*8 */
"str %[val], [%[stack], w1, sxtw #3]\n\t" /* base + index*8 */
/* Set error=0 in success path */
"mov w3, #0\n\t"
"str w3, %[err]\n\t"
"b 2f\n\t"
/* Overflow path */
"1:\n\t"
"mov w3, #1\n\t"
"str w3, %[err]\n\t"
"2:\n\t"
: [dsp]"+m"(vm->dsp),
[err]"=m"(error)
: [val]"r"(value),
[stack]"r"(vm->data_stack)
: "x0", "x1", "x3", "cc", "memory"
);
if (__builtin_expect(error, 0)) {
vm->error = 1;
}
}
/* vm_pop optimized - Data stack pop */
/**
* @brief Optimized assembly implementation of data stack pop operation
* @param vm Pointer to VM instance
* @return Popped value, or 0 on underflow
* @note Sets vm->error on stack underflow
*/
static inline cell_t vm_pop_asm(VM *vm) {
cell_t value = 0;
int error = 0;
__asm__ __volatile__(
/* Load current dsp */
"ldr w0, %[dsp]\n\t"
/* Check for underflow: dsp < 0 */
"cmp w0, #0\n\t"
"b.lt 1f\n\t" /* Branch if less than */
/* No underflow: load value */
"ldr %[val], [%[stack], w0, sxtw #3]\n\t" /* base + index*8 */
/* Decrement dsp */
"sub w1, w0, #1\n\t"
"str w1, %[dsp]\n\t"
/* Set error=0 in success path */
"mov w3, #0\n\t"
"str w3, %[err]\n\t"
"b 2f\n\t"
/* Underflow path */
"1:\n\t"
"mov w3, #1\n\t"
"str w3, %[err]\n\t"
"mov %[val], #0\n\t" /* Return 0 on error */
"2:\n\t"
: [dsp]"+m"(vm->dsp),
[val]"=r"(value),
[err]"=m"(error)
: [stack]"r"(vm->data_stack)
: "x0", "x1", "x3", "cc", "memory"
);
if (__builtin_expect(error, 0)) {
vm->error = 1;
}
return value;
}
/* vm_rpush optimized - Return stack push */
/**
* @brief Optimized assembly implementation of return stack push operation
* @param vm Pointer to VM instance
* @param value Value to push onto return stack
* @note Sets vm->error on stack overflow
*/
static inline void vm_rpush_asm(VM *vm, cell_t value) {
int error = 0;
__asm__ __volatile__(
"ldr w0, %[rsp]\n\t"
"cmp w0, #1022\n\t"
"b.ge 1f\n\t"
"add w1, w0, #1\n\t"
"str w1, %[rsp]\n\t"
"str %[val], [%[stack], w1, sxtw #3]\n\t"
"mov w3, #0\n\t"
"str w3, %[err]\n\t"
"b 2f\n\t"
"1:\n\t"
"mov w3, #1\n\t"
"str w3, %[err]\n\t"
"2:\n\t"
: [rsp]"+m"(vm->rsp),
[err]"=m"(error)
: [val]"r"(value),
[stack]"r"(vm->return_stack)
: "x0", "x1", "x3", "cc", "memory"
);
if (__builtin_expect(error, 0)) {
vm->error = 1;
}
}
/* vm_rpop optimized - Return stack pop */
/**
* @brief Optimized assembly implementation of return stack pop operation
* @param vm Pointer to VM instance
* @return Popped value, or 0 on underflow
* @note Sets vm->error on stack underflow
*/
static inline cell_t vm_rpop_asm(VM *vm) {
cell_t value = 0;
int error = 0;
__asm__ __volatile__(
"ldr w0, %[rsp]\n\t"
"cmp w0, #0\n\t"
"b.lt 1f\n\t"
"ldr %[val], [%[stack], w0, sxtw #3]\n\t"
"sub w1, w0, #1\n\t"
"str w1, %[rsp]\n\t"
"mov w3, #0\n\t"
"str w3, %[err]\n\t"
"b 2f\n\t"
"1:\n\t"
"mov w3, #1\n\t"
"str w3, %[err]\n\t"
"mov %[val], #0\n\t"
"2:\n\t"
: [rsp]"+m"(vm->rsp),
[val]"=r"(value),
[err]"=m"(error)
: [stack]"r"(vm->return_stack)
: "x0", "x1", "x3", "cc", "memory"
);
if (__builtin_expect(error, 0)) {
vm->error = 1;
}
return value;
}
/* ============================================================================
* OPTIMIZATION 2: Fast Arithmetic with Overflow Detection
* ============================================================================
*
* ARM64 has excellent support for overflow detection via condition flags
* and conditional select instructions
*
* ============================================================================
*/
/* Fast add with overflow detection using ADDS and conditional select */
/**
* @brief Fast addition with overflow detection using ARM64 ADDS instruction
* @param a First operand
* @param b Second operand
* @param result Pointer to store result
* @return 1 if overflow occurred, 0 otherwise
*/
static inline int vm_add_check_overflow(cell_t a, cell_t b, cell_t *result) {
cell_t res;
int overflow;
__asm__(
"adds %[res], %[a], %[b]\n\t" /* Add and set flags */
"cset %w[ovf], vs\n\t" /* Set overflow flag (V flag) */
: [res]"=r"(res),
[ovf]"=r"(overflow)
: [a]"r"(a),
[b]"r"(b)
: "cc"
);
*result = res;
return overflow;
}
/* Fast subtract with overflow detection */
/**
* @brief Fast subtraction with overflow detection using ARM64 SUBS instruction
* @param a First operand
* @param b Second operand
* @param result Pointer to store result
* @return 1 if overflow occurred, 0 otherwise
*/
static inline int vm_sub_check_overflow(cell_t a, cell_t b, cell_t *result) {
cell_t res;
int overflow;
__asm__(
"subs %[res], %[a], %[b]\n\t"
"cset %w[ovf], vs\n\t"
: [res]"=r"(res),
[ovf]"=r"(overflow)
: [a]"r"(a),
[b]"r"(b)
: "cc"
);
*result = res;
return overflow;
}
/* Fast multiply with high-word result (for star-slash-MOD) */
/**
* @brief Fast 128-bit multiplication using ARM64 SMULH instruction
* @param a First operand
* @param b Second operand
* @param hi Pointer to store high 64 bits of result
* @param lo Pointer to store low 64 bits of result
*/
static inline void vm_mul_double(cell_t a, cell_t b, cell_t *hi, cell_t *lo) {
__asm__(
"mul %[lo], %[a], %[b]\n\t" /* Low 64 bits */
"smulh %[hi], %[a], %[b]\n\t" /* High 64 bits (signed) */
: [lo]"=r"(*lo),
[hi]"=r"(*hi)
: [a]"r"(a),
[b]"r"(b)
);
}
/* Fast divide with remainder (for /MOD) */
/**
* @brief Fast division with remainder using ARM64 SDIV and MSUB instructions
* @param dividend Dividend value
* @param divisor Divisor value
* @param quotient Pointer to store quotient
* @param remainder Pointer to store remainder
*/
static inline void vm_divmod(cell_t dividend, cell_t divisor,
cell_t *quotient, cell_t *remainder) {
cell_t quot, rem;
__asm__(
"sdiv %[quot], %[dividend], %[divisor]\n\t" /* Quotient */
"msub %[rem], %[quot], %[divisor], %[dividend]\n\t" /* Remainder = dividend - quot*divisor */
: [quot]"=r"(quot),
[rem]"=r"(rem)
: [dividend]"r"(dividend),
[divisor]"r"(divisor)
);
*quotient = quot;
*remainder = rem;
}
/* ============================================================================
* OPTIMIZATION 3: Fast String Operations for Dictionary Lookup
* ============================================================================
*
* ARM64 doesn't have rep-style instructions, but we can:
* - Use NEON for parallel comparison
* - Use load-multiple for cache efficiency
* - Unroll loops for small strings
*
* ============================================================================
*/
/* Fast string comparison for short strings (<=32 bytes) */
/**
* @brief Optimized string comparison for short strings (<=32 bytes)
* @param s1 First string
* @param s2 Second string
* @param len Length to compare
* @return 0 if equal, 1 if different
*/
static inline int vm_strcmp_short_asm(const char *s1, const char *s2, size_t len) {
int result = 0;
if (len <= 8) {
/* Use single 64-bit load for strings <= 8 bytes */
__asm__(
"mov x0, #0\n\t" /* result = 0 */
"cbz %[len], 2f\n\t" /* if len==0, equal */
"ldr x1, [%[s1]]\n\t"
"ldr x2, [%[s2]]\n\t"
"eor x3, x1, x2\n\t" /* XOR to find differences */
/* Mask off bits beyond length */
"mov x4, #64\n\t"
"lsl x5, %[len], #3\n\t" /* len * 8 */
"sub x4, x4, x5\n\t"
"lsr x1, x1, x4\n\t"
"lsr x2, x2, x4\n\t"
"eor x3, x1, x2\n\t"
"cmp x3, #0\n\t"
"cset %w[res], ne\n\t" /* result = (x3 != 0) */
"2:\n\t"
: [res]"=r"(result)
: [s1]"r"(s1),
[s2]"r"(s2),
[len]"r"(len)
: "x0", "x1", "x2", "x3", "x4", "x5", "cc", "memory"
);
} else {
/* Fallback to byte-by-byte for longer strings */
for (size_t i = 0; i < len; i++) {
if (s1[i] != s2[i]) {
result = 1;
break;
}
}
}
return result;
}
/* Fast string comparison using NEON (for longer strings) */
#ifdef __ARM_NEON
static inline int vm_strcmp_neon(const char *s1, const char *s2, size_t len) {
int result = 0;
/* Process 16 bytes at a time using NEON */
while (len >= 16) {
__asm__ __volatile__(
"ld1 {v0.16b}, [%[s1]], #16\n\t" /* Load 16 bytes from s1, post-increment */
"ld1 {v1.16b}, [%[s2]], #16\n\t" /* Load 16 bytes from s2, post-increment */
"cmeq v2.16b, v0.16b, v1.16b\n\t" /* Compare equal */
"uminv b3, v2.16b\n\t" /* Get minimum (all must be 0xFF for equal) */
"fmov %w[res], s3\n\t" /* Move to general register */
"cmp %w[res], #0xff\n\t"
"b.ne 1f\n\t" /* If not all equal, exit */
"1:\n\t"
: [res]"=r"(result),
[s1]"+r"(s1),
[s2]"+r"(s2)
:
: "v0", "v1", "v2", "v3", "cc", "memory"
);
if (result != 0xFF) {
return 1; /* Not equal */
}
len -= 16;
}
/* Handle remaining bytes */
while (len > 0) {
if (*s1++ != *s2++) return 1;
len--;
}
return 0;
}
#endif
/* Main string comparison wrapper */
static inline int vm_strcmp_asm(const char *s1, const char *s2, size_t len) {
#ifdef __ARM_NEON
if (len>= 16) {
return vm_strcmp_neon(s1, s2, len);
}
#endif
return vm_strcmp_short_asm(s1, s2, len);
}
/* Fast memory copy optimized for ARM64 */
/**
* @brief Optimized memory copy using ARM64 load/store pair instructions
* @param dest Destination buffer
* @param src Source buffer
* @param len Number of bytes to copy
*/
static inline void vm_memcpy_asm(void *dest, const void *src, size_t len) {
/* ARM64 has efficient load/store pair instructions */
if (len >= 16) {
__asm__ __volatile__(
"1:\n\t"
"ldp x0, x1, [%[src]], #16\n\t" /* Load pair, post-increment */
"stp x0, x1, [%[dst]], #16\n\t" /* Store pair, post-increment */
"subs %[len], %[len], #16\n\t"
"b.ge 1b\n\t"
: [src]"+r"(src),
[dst]"+r"(dest),
[len]"+r"(len)
:
: "x0", "x1", "cc", "memory"
);
len += 16; /* Adjust for overshoot */
}
/* Copy remaining bytes */
char *d = (char *) dest;
const char *s = (const char *) src;
while (len-- > 0) {
*d++ = *s++;
}
}
/* Fast memory zero using store pair */
/**
* @brief Fast memory zeroing using ARM64 store pair instructions
* @param dest Buffer to zero
* @param len Number of bytes to zero
*/
static inline void vm_memzero_asm(void *dest, size_t len) {
if (len >= 16) {
__asm__ __volatile__(
"1:\n\t"
"stp xzr, xzr, [%[dst]], #16\n\t" /* Store zero pair */
"subs %[len], %[len], #16\n\t"
"b.ge 1b\n\t"
: [dst]"+r"(dest),
[len]"+r"(len)
:
: "cc", "memory"
);
len += 16;
}
/* Zero remaining bytes */
char *d = (char *) dest;
while (len-- > 0) {
*d++ = 0;
}
}
/* ============================================================================
* OPTIMIZATION 4: Branchless Min/Max using CSEL
* ============================================================================
*
* ARM64's conditional select is perfect for branchless operations
*
* ============================================================================
*/
static inline cell_t vm_min_asm(cell_t a, cell_t b) {
cell_t result;
__asm__(
"cmp %[a], %[b]\n\t"
"csel %[res], %[a], %[b], lt\n\t" /* Select a if a < b, else b */
: [res]"=r"(result)
: [a]"r"(a),
[b]"r"(b)
: "cc"
);
return result;
}
static inline cell_t vm_max_asm(cell_t a, cell_t b) {
cell_t result;
__asm__(
"cmp %[a], %[b]\n\t"
"csel %[res], %[a], %[b], gt\n\t" /* Select a if a > b, else b */
: [res]"=r"(result)
: [a]"r"(a),
[b]"r"(b)
: "cc"
);
return result;
}
/* Absolute value using conditional negate */
static inline cell_t vm_abs_asm(cell_t a) {
cell_t result;
__asm__(
"cmp %[a], #0\n\t"
"cneg %[res], %[a], lt\n\t" /* Negate if negative */
: [res]"=r"(result)
: [a]"r"(a)
: "cc"
);
return result;
}
/* ============================================================================
* OPTIMIZATION 5: Bit Manipulation
* ============================================================================
*
* ARM64 has excellent bit manipulation instructions
*
* ============================================================================
*/
/* Count leading zeros */
static inline int vm_clz(cell_t x) {
int result;
__asm__("clz %[res], %[x]": [res]"=r"(result): [x]"r"(x));
return result;
}
/* Count trailing zeros */
static inline int vm_ctz(cell_t x) {
int result;
__asm__(
"rbit %[tmp], %[x]\n\t" /* Reverse bits */
"clz %[res], %[tmp]\n\t" /* Count leading zeros of reversed */
: [res]"=r"(result),
[tmp]"=r"(x)
: [x]"r"(x)
);
return result;
}
/* Population count (count set bits) */
static inline int vm_popcnt(cell_t x) {
int result;
__asm__(
"fmov d0, %[x]\n\t" /* Move to NEON register */
"cnt v0.8b, v0.8b\n\t" /* Count bits in each byte */
"addv b0, v0.8b\n\t" /* Sum all bytes */
"fmov %w[res], s0\n\t" /* Move back to general register */
: [res]"=r"(result)
: [x]"r"(x)
: "v0"
);
return result;
}
/* ============================================================================
* OPTIMIZATION 6: CPU Feature Detection
* ============================================================================
*
* ARM64 uses a different mechanism than x86 CPUID
*
* ============================================================================
*/
/* Read ARM64 CPU ID register */
static inline uint64_t vm_read_midr(void) {
uint64_t midr;
__asm__("mrs %0, midr_el1": "=r"(midr));
return midr;
}
/* Check for specific CPU features via auxiliary control register */
static inline int vm_has_neon(void) {
#ifdef __ARM_NEON
return 1;
#else
return 0;
#endif
}
/* Check for Cortex-A72 (Raspberry Pi 4) */
static inline int vm_is_cortex_a72(void) {
/* Cortex-A72 MIDR: 0x410FD083 */
/* Note: Reading MIDR requires EL1, may not work in userspace */
/* Use hwcaps instead for userspace detection */
#ifdef __linux__
unsigned long hwcap = getauxval(AT_HWCAP);
return (hwcap &HWCAP_ASIMD)!= 0; /* ASIMD = Advanced SIMD (NEON) */
#else
return 0;
#endif
}
/* ============================================================================
* OPTIMIZATION 7: Cache Management (Raspberry Pi 4 specific)
* ============================================================================
*
* Raspberry Pi 4 cache hierarchy:
* - L1I: 32KB (3-way, 64-byte lines)
* - L1D: 32KB (2-way, 64-byte lines)
* - L2: 1MB (16-way, 64-byte lines)
*
* ============================================================================
*/
/* Prefetch data into cache */
static inline void vm_prefetch(const void *addr) {
__asm__ __volatile__(
"prfm pldl1keep, [%0]\n\t" /* Prefetch for load, L1 cache, temporal */
:
: "r"(addr)
: "memory"
);
}
/* Prefetch with streaming hint (for sequential access) */
static inline void vm_prefetch_stream(const void *addr) {
__asm__ __volatile__(
"prfm pldl1strm, [%0]\n\t" /* Prefetch for load, L1 cache, streaming */
:
: "r"(addr)
: "memory"
);
}
/* Data cache zero (fast way to zero cache lines) */
static inline void vm_dc_zva(void *addr) {
__asm__ __volatile__(
"dc zva, %0\n\t" /* Zero entire cache line (64 bytes) */
:
: "r"(addr)
: "memory"
);
}
#else /* !USE_ASM_OPT */
/* Fallback to standard implementations */
#define vm_push_asm(vm, val) vm_push(vm, val)
#define vm_pop_asm(vm) vm_pop(vm)
#define vm_rpush_asm(vm, val) vm_rpush(vm, val)
#define vm_rpop_asm(vm) vm_rpop(vm)
#endif /* USE_ASM_OPT */
/* ============================================================================
* RASPBERRY PI 4 SPECIFIC NOTES
* ============================================================================
*
* CPU: Broadcom BCM2711 (Quad-core Cortex-A72 @ 1.5GHz)
* Architecture: ARMv8-A
* L1 Cache: 32KB I + 32KB D per core
* L2 Cache: 1MB shared
* RAM: 1GB/2GB/4GB/8GB LPDDR4-3200
*
* Optimization Tips:
* 1. Align hot data to 64-byte cache lines
* 2. Use NEON for parallel operations
* 3. Prefetch sequential data access
* 4. Keep hot code < 32KB (fits in L1I)
* 5. Use load/store pairs (ldp/stp) for efficiency
*
* Thermal Throttling:
* - CPU throttles at 80°C
* - Add heatsink for sustained performance
* - Monitor: vcgencmd measure_temp
*
* Build Flags:
* -march=armv8-a+crc+simd -mtune=cortex-a72 -O3 -DUSE_ASM_OPT=1
*
* Cross-compilation:
* CC=aarch64-linux-gnu-gcc
*
* ============================================================================
*/
#endif /* VM_ASM_OPT_ARM64_H */
+525
View File
@@ -0,0 +1,525 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef VM_ASM_OPT_RISCV64_H
#define VM_ASM_OPT_RISCV64_H
#include "vm.h"
#include <stdint.h>
/*
* Configuration: Define USE_ASM_OPT=1 to enable assembly optimizations
*/
#ifndef USE_ASM_OPT
#define USE_ASM_OPT 0
#endif
/*
* RISC-V 64 Architecture Notes:
* ==============================
*
* Register ABI (RV64GC / rv64gc):
* - x0 (zero): hardwired zero
* - x1 (ra): return address
* - x2 (sp): stack pointer
* - x3 (gp): global pointer
* - x4 (tp): thread pointer
* - x5–x7 (t0–t2): temporaries (caller-saved)
* - x8–x9 (s0–s1): callee-saved / frame pointer
* - x10–x17 (a0–a7): argument/return (caller-saved)
* - x18–x27 (s2–s11): callee-saved
* - x28–x31 (t3–t6): temporaries (caller-saved)
*
* Key differences vs x86_64 / ARM64:
* - Pure load/store architecture — all ops work on registers
* - No flags register; overflow detection requires explicit instructions
* - No conditional move in base ISA (Zicond ext adds czero.eqz/czero.nez)
* - No string-comparison instructions; byte loops required
* - M extension: mul, mulh, div, rem — all present in rv64gc
* - B extension (Zba/Zbb/Zbc): clz, ctz, cpop, etc. — NOT guaranteed in rv64gc
*
* Branchless min/max without Zicond:
* slt t0, a, b # t0 = (a < b) ? 1 : 0
* neg t0, t0 # t0 = (a < b) ? -1 : 0 (all-ones mask or zero)
* sub t1, a, b # t1 = a - b
* and t1, t1, t0 # t1 = (a-b) if a<b, else 0
* add res, b, t1 # res = b + (a-b if a<b) = min(a,b)
*
* Overflow detection (no flags):
* add res, a, b
* # signed overflow iff same-sign operands produce opposite-sign result
* xor t0, a, b # t0[63]=0 means same sign
* xor t1, a, res # t1[63]=1 means sign changed
* srai t0, t0, 63 # 0 or -1
* srai t1, t1, 63 # 0 or -1
* xori t0, t0, -1 # invert: -1 -> 0, 0 -> -1
* and ovf, t0, t1 # -1 iff overflow
* snez ovf, ovf # 0 or 1
*/
#if USE_ASM_OPT
/* ============================================================================
* OPTIMIZATION 1: Stack Operations
* ============================================================================
*/
static inline void vm_push_asm(VM *vm, cell_t value) {
int error = 0;
__asm__ __volatile__(
"lw t0, %[dsp]\n\t" /* t0 = dsp */
"li t1, 1022\n\t"
"bge t0, t1, 1f\n\t" /* branch if dsp >= 1022 (overflow) */
"addi t0, t0, 1\n\t" /* dsp++ */
"sw t0, %[dsp]\n\t"
"slli t1, t0, 3\n\t" /* t1 = dsp * 8 */
"add t1, %[stack], t1\n\t" /* t1 = &data_stack[dsp] */
"sd %[val], 0(t1)\n\t" /* store value */
"sw zero, %[err]\n\t"
"j 2f\n\t"
"1:\n\t"
"li t0, 1\n\t"
"sw t0, %[err]\n\t"
"2:\n\t"
: [dsp]"+m"(vm->dsp),
[err]"=m"(error)
: [val]"r"(value),
[stack]"r"(vm->data_stack)
: "t0", "t1", "memory"
);
if (__builtin_expect(error, 0)) {
vm->error = 1;
}
}
static inline cell_t vm_pop_asm(VM *vm) {
cell_t value = 0;
int error = 0;
__asm__ __volatile__(
"lw t0, %[dsp]\n\t"
"bltz t0, 1f\n\t" /* branch if dsp < 0 (underflow) */
"slli t1, t0, 3\n\t"
"add t1, %[stack], t1\n\t" /* t1 = &data_stack[dsp] (old dsp) */
"addi t0, t0, -1\n\t"
"sw t0, %[dsp]\n\t" /* write dsp back BEFORE loading the
value: %[dsp] is a memory operand
referenced twice in this template
(read above, write here); loading
%[val] (a register output) before
this write-back let clang's
allocator reuse %[dsp]'s address
register for %[val], corrupting
the write-back — GCC happened to
pick different registers and
never hit it. Loading %[val] last
means %[dsp]'s final use has
already happened by the time the
output register is live. */
"ld %[val], 0(t1)\n\t"
"sw zero, %[err]\n\t"
"j 2f\n\t"
"1:\n\t"
"li t0, 1\n\t"
"sw t0, %[err]\n\t"
"mv %[val], zero\n\t"
"2:\n\t"
: [dsp]"+m"(vm->dsp),
[val]"=r"(value),
[err]"=m"(error)
: [stack]"r"(vm->data_stack)
: "t0", "t1", "memory"
);
if (__builtin_expect(error, 0)) {
vm->error = 1;
}
return value;
}
static inline void vm_rpush_asm(VM *vm, cell_t value) {
int error = 0;
__asm__ __volatile__(
"lw t0, %[rsp]\n\t"
"li t1, 1022\n\t"
"bge t0, t1, 1f\n\t"
"addi t0, t0, 1\n\t"
"sw t0, %[rsp]\n\t"
"slli t1, t0, 3\n\t"
"add t1, %[stack], t1\n\t"
"sd %[val], 0(t1)\n\t"
"sw zero, %[err]\n\t"
"j 2f\n\t"
"1:\n\t"
"li t0, 1\n\t"
"sw t0, %[err]\n\t"
"2:\n\t"
: [rsp]"+m"(vm->rsp),
[err]"=m"(error)
: [val]"r"(value),
[stack]"r"(vm->return_stack)
: "t0", "t1", "memory"
);
if (__builtin_expect(error, 0)) {
vm->error = 1;
}
}
static inline cell_t vm_rpop_asm(VM *vm) {
cell_t value = 0;
int error = 0;
__asm__ __volatile__(
"lw t0, %[rsp]\n\t"
"bltz t0, 1f\n\t"
"slli t1, t0, 3\n\t"
"add t1, %[stack], t1\n\t" /* t1 = &return_stack[rsp] (old rsp) */
"addi t0, t0, -1\n\t"
"sw t0, %[rsp]\n\t" /* write rsp back before loading the
value — see vm_pop_asm for why */
"ld %[val], 0(t1)\n\t"
"sw zero, %[err]\n\t"
"j 2f\n\t"
"1:\n\t"
"li t0, 1\n\t"
"sw t0, %[err]\n\t"
"mv %[val], zero\n\t"
"2:\n\t"
: [rsp]"+m"(vm->rsp),
[val]"=r"(value),
[err]"=m"(error)
: [stack]"r"(vm->return_stack)
: "t0", "t1", "memory"
);
if (__builtin_expect(error, 0)) {
vm->error = 1;
}
return value;
}
/* ============================================================================
* OPTIMIZATION 2: Arithmetic with Overflow Detection
* ============================================================================
*
* RISC-V has no flags register. Overflow is detected by inspecting operand
* and result sign bits directly.
*
* Signed addition overflows iff operands have the same sign and the result
* has the opposite sign:
* ovf = ~(a XOR b)[63] AND (a XOR result)[63]
* ============================================================================
*/
static inline int vm_add_check_overflow(cell_t a, cell_t b, cell_t *result) {
cell_t res;
cell_t ovf;
__asm__(
"add %[res], %[a], %[b]\n\t"
"xor t0, %[a], %[b]\n\t" /* t0[63]=0 if same sign */
"xor t1, %[a], %[res]\n\t" /* t1[63]=1 if sign changed */
"not t0, t0\n\t" /* invert: same-sign -> MSB=1 */
"and t0, t0, t1\n\t" /* both conditions met */
"srli %[ovf], t0, 63\n\t" /* extract MSB as 0 or 1 */
: [res]"=r"(res), [ovf]"=r"(ovf)
: [a]"r"(a), [b]"r"(b)
: "t0", "t1"
);
*result = res;
return (int)ovf;
}
static inline int vm_sub_check_overflow(cell_t a, cell_t b, cell_t *result) {
cell_t res;
cell_t ovf;
__asm__(
"sub %[res], %[a], %[b]\n\t"
/* Signed subtraction a-b overflows iff a and b have different signs
* AND result has different sign from a:
* ovf = (a XOR b)[63] AND (a XOR result)[63] */
"xor t0, %[a], %[b]\n\t" /* t0[63]=1 if different sign */
"xor t1, %[a], %[res]\n\t" /* t1[63]=1 if result sign differs */
"and t0, t0, t1\n\t"
"srli %[ovf], t0, 63\n\t"
: [res]"=r"(res), [ovf]"=r"(ovf)
: [a]"r"(a), [b]"r"(b)
: "t0", "t1"
);
*result = res;
return (int)ovf;
}
/* RV64M: mulh gives the high 64 bits of signed 64×64 product */
static inline void vm_mul_double(cell_t a, cell_t b, cell_t *hi, cell_t *lo) {
__asm__(
"mul %[lo], %[a], %[b]\n\t" /* low 64 bits */
"mulh %[hi], %[a], %[b]\n\t" /* high 64 bits (signed) */
: [lo]"=r"(*lo), [hi]"=r"(*hi)
: [a]"r"(a), [b]"r"(b)
);
}
/* RV64M: div and rem */
static inline void vm_divmod(cell_t dividend, cell_t divisor,
cell_t *quotient, cell_t *remainder) {
cell_t quot, rem;
__asm__(
"div %[quot], %[dividend], %[divisor]\n\t"
"rem %[rem], %[dividend], %[divisor]\n\t"
: [quot]"=r"(quot), [rem]"=r"(rem)
: [dividend]"r"(dividend), [divisor]"r"(divisor)
);
*quotient = quot;
*remainder = rem;
}
/* ============================================================================
* OPTIMIZATION 3: String Operations
* ============================================================================
*
* RISC-V has no string or SIMD instructions in the base ISA (rv64gc).
* We write tight scalar loops in asm to avoid libc call overhead inside
* the inner interpreter hot path.
* ============================================================================
*/
static inline int vm_strcmp_asm(const char *s1, const char *s2, size_t len) {
int result = 0;
__asm__ __volatile__(
"beqz %[len], 2f\n\t"
"1:\n\t"
"lb t0, 0(%[s1])\n\t"
"lb t1, 0(%[s2])\n\t"
"bne t0, t1, 3f\n\t"
"addi %[s1], %[s1], 1\n\t"
"addi %[s2], %[s2], 1\n\t"
"addi %[len], %[len], -1\n\t"
"bnez %[len], 1b\n\t"
"2:\n\t"
"mv %[res], zero\n\t"
"j 4f\n\t"
"3:\n\t"
"li %[res], 1\n\t"
"4:\n\t"
: [res]"=r"(result),
[s1]"+r"(s1), [s2]"+r"(s2), [len]"+r"(len)
:
: "t0", "t1", "memory"
);
return result;
}
static inline void vm_memcpy_asm(void *dest, const void *src, size_t len) {
/* Copy 8 bytes at a time, then handle remainder */
__asm__ __volatile__(
"srli t2, %[len], 3\n\t" /* t2 = len / 8 */
"beqz t2, 2f\n\t"
"1:\n\t"
"ld t0, 0(%[src])\n\t"
"sd t0, 0(%[dst])\n\t"
"addi %[src], %[src], 8\n\t"
"addi %[dst], %[dst], 8\n\t"
"addi t2, t2, -1\n\t"
"bnez t2, 1b\n\t"
"2:\n\t"
"andi %[len], %[len], 7\n\t" /* remaining bytes */
"beqz %[len], 4f\n\t"
"3:\n\t"
"lb t0, 0(%[src])\n\t"
"sb t0, 0(%[dst])\n\t"
"addi %[src], %[src], 1\n\t"
"addi %[dst], %[dst], 1\n\t"
"addi %[len], %[len], -1\n\t"
"bnez %[len], 3b\n\t"
"4:\n\t"
: [src]"+r"(src), [dst]"+r"(dest), [len]"+r"(len)
:
: "t0", "t2", "memory"
);
}
static inline void vm_memzero_asm(void *dest, size_t len) {
__asm__ __volatile__(
"srli t2, %[len], 3\n\t"
"beqz t2, 2f\n\t"
"1:\n\t"
"sd zero, 0(%[dst])\n\t"
"addi %[dst], %[dst], 8\n\t"
"addi t2, t2, -1\n\t"
"bnez t2, 1b\n\t"
"2:\n\t"
"andi %[len], %[len], 7\n\t"
"beqz %[len], 4f\n\t"
"3:\n\t"
"sb zero, 0(%[dst])\n\t"
"addi %[dst], %[dst], 1\n\t"
"addi %[len], %[len], -1\n\t"
"bnez %[len], 3b\n\t"
"4:\n\t"
: [dst]"+r"(dest), [len]"+r"(len)
:
: "t2", "memory"
);
}
/* ============================================================================
* OPTIMIZATION 4: Branchless Min/Max
* ============================================================================
*
* RISC-V base ISA has no conditional move. The bit-mask trick avoids branches:
* mask = -(a < b) → all-ones if a<b, zero otherwise
* min = b + (a-b) & mask
* ============================================================================
*/
static inline cell_t vm_min_asm(cell_t a, cell_t b) {
cell_t result;
__asm__(
"slt t0, %[a], %[b]\n\t" /* t0 = (a < b) ? 1 : 0 */
"neg t0, t0\n\t" /* t0 = mask (-1 or 0) */
"sub t1, %[a], %[b]\n\t"
"and t1, t1, t0\n\t"
"add %[res], %[b], t1\n\t"
: [res]"=r"(result)
: [a]"r"(a), [b]"r"(b)
: "t0", "t1"
);
return result;
}
static inline cell_t vm_max_asm(cell_t a, cell_t b) {
cell_t result;
__asm__(
"slt t0, %[b], %[a]\n\t" /* t0 = (b < a) ? 1 : 0 → (a > b) */
"neg t0, t0\n\t"
"sub t1, %[a], %[b]\n\t"
"and t1, t1, t0\n\t"
"add %[res], %[b], t1\n\t"
: [res]"=r"(result)
: [a]"r"(a), [b]"r"(b)
: "t0", "t1"
);
return result;
}
static inline cell_t vm_abs_asm(cell_t a) {
cell_t result;
__asm__(
"srai t0, %[a], 63\n\t" /* t0 = sign mask (-1 or 0) */
"xor %[res], %[a], t0\n\t" /* flip bits if negative */
"sub %[res], %[res], t0\n\t" /* add 1 if negative (two's complement) */
: [res]"=r"(result)
: [a]"r"(a)
: "t0"
);
return result;
}
/* ============================================================================
* OPTIMIZATION 5: Bit Manipulation
* ============================================================================
*
* rv64gc does NOT include the B extension (Zbb), so clz/ctz/cpop are not
* available as single instructions. GCC builtins map to the best available
* form — if the toolchain targets Zbb they'll lower to single instructions;
* on plain rv64gc they fall back to efficient multi-instruction sequences.
* ============================================================================
*/
static inline int vm_clz(cell_t x) {
return __builtin_clzll((unsigned long long)x);
}
static inline int vm_ctz(cell_t x) {
return __builtin_ctzll((unsigned long long)x);
}
static inline int vm_popcnt(cell_t x) {
return __builtin_popcountll((unsigned long long)x);
}
/* ============================================================================
* OPTIMIZATION 6: Cache Operations
* ============================================================================
*
* RISC-V uses fence.i for instruction cache coherence.
* There is no user-space prefetch instruction in the base ISA;
* we use GCC's __builtin_prefetch which lowers to a hint if the target
* supports one (e.g., Zicbop extension), otherwise it's a no-op.
* ============================================================================
*/
static inline void vm_prefetch(const void *addr) {
__builtin_prefetch(addr, 0, 3);
}
static inline void vm_prefetch_stream(const void *addr) {
__builtin_prefetch(addr, 0, 0);
}
/* ============================================================================
* OPTIMIZATION 7: CPU Feature Detection
* ============================================================================
*
* RISC-V has no CPUID equivalent in user space. Extension detection requires
* either kernel support (/proc/cpuinfo, riscv_hwprobe syscall on Linux 6.4+)
* or compiler defines. We expose a simple interface here.
* ============================================================================
*/
static inline int vm_has_riscv_m_ext(void) {
#if defined(__riscv_mul)
return 1;
#else
return 0;
#endif
}
static inline int vm_has_riscv_b_ext(void) {
#if defined(__riscv_zbb)
return 1;
#else
return 0;
#endif
}
#else /* !USE_ASM_OPT */
#define vm_push_asm(vm, val) vm_push(vm, val)
#define vm_pop_asm(vm) vm_pop(vm)
#define vm_rpush_asm(vm, val) vm_rpush(vm, val)
#define vm_rpop_asm(vm) vm_rpop(vm)
#endif /* USE_ASM_OPT */
#endif /* VM_ASM_OPT_RISCV64_H */
+71
View File
@@ -0,0 +1,71 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/** @cond */
#ifndef VM_DEBUG_H
#define VM_DEBUG_H
/** @endcond */
#include "vm.h"
/**
* @brief Sets the VM instance to dump state for when signals occur
* @param vm Pointer to the VM instance to monitor
* @note Should be called once after vm_init
*/
void vm_debug_set_current_vm(VM * vm);
/**
* @brief Installs signal handlers for SIGSEGV and SIGABRT
* @details The handlers will print VM state before re-raising the signal
*/
void vm_debug_install_signal_handlers(void);
/**
* @brief Manually dumps the state of a VM instance
* @param vm The VM instance to dump state for
* @param reason Description of why the dump was triggered
* @note Typically called before exit(1) on test failure
*/
void vm_debug_dump_state(const VM *vm, const char *reason);
#endif /* VM_DEBUG_H */
+178
View File
@@ -0,0 +1,178 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* vm_host.h - Host services abstraction for StarForth VM
*
* Provides pluggable allocator, time, and mutex services.
* Hosted builds use libc; kernel builds use PMM/kmalloc/TSC.
*
* M7 Non-Negotiable: All allocation goes through these hooks.
* No direct malloc/free in VM code.
*/
#ifndef STARKERNEL_VM_HOST_H
#define STARKERNEL_VM_HOST_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
struct DictEntry;
/**
* Host allocator function signature
* Returns NULL on failure
*/
typedef void* (*sk_alloc_fn)(size_t size, size_t align);
/**
* Host free function signature
*/
typedef void (*sk_free_fn)(void *ptr);
/**
* Host monotonic time function signature
* Returns nanoseconds since arbitrary epoch
* In PARITY_MODE, returns deterministic fake time
*/
typedef uint64_t (*sk_time_fn)(void);
/**
* Host mutex operations (no-op in kernel single-threaded mode)
*/
typedef int (*sk_mutex_init_fn)(void *mutex);
typedef int (*sk_mutex_lock_fn)(void *mutex);
typedef int (*sk_mutex_unlock_fn)(void *mutex);
typedef void (*sk_mutex_destroy_fn)(void *mutex);
/**
* Host console output (for words that print)
* Returns number of bytes written, or -1 on error
*/
typedef int (*sk_puts_fn)(const char *str);
typedef int (*sk_putc_fn)(int c);
/**
* Host execution-token validation
* Returns 1 if pointer is executable, 0 otherwise
*/
typedef bool (*sk_xt_validate_fn)(const void *ptr);
/**
* Host dictionary entry ownership check
*/
typedef bool (*sk_xt_entry_owns_fn)(const void *entry, size_t bytes);
/**
* Host fatal handler (panic)
*/
typedef void (*sk_panic_fn)(const char *message);
/**
* VMHostServices - pluggable host operations
*
* Hosted builds point these at libc functions.
* Kernel builds point these at kernel implementations.
*/
typedef struct VMHostServices {
/* Memory allocation */
sk_alloc_fn alloc;
sk_free_fn free;
/* Monotonic time source */
sk_time_fn monotonic_ns;
/* Mutex operations (can be no-ops) */
sk_mutex_init_fn mutex_init;
sk_mutex_lock_fn mutex_lock;
sk_mutex_unlock_fn mutex_unlock;
sk_mutex_destroy_fn mutex_destroy;
/* Console output */
sk_puts_fn puts;
sk_putc_fn putc;
/* XT validation */
sk_xt_validate_fn is_executable_ptr;
sk_xt_entry_owns_fn owns_xt_entry;
sk_panic_fn panic;
/* Flags */
int parity_mode; /* 1 = deterministic time */
int verbose; /* 1 = enable logging */
} VMHostServices;
/**
* Get the current host services instance.
* Returns hosted or kernel services depending on build.
*/
const VMHostServices* sk_host_services(void);
/**
* Initialize host services for kernel build.
* Must be called before any VM operations.
*/
#ifdef __STARKERNEL__
void sk_host_init(void);
#endif
/**
* Convenience macros for common operations
* Use these instead of direct malloc/free/etc.
*/
#define SK_ALLOC(size) (sk_host_services()->alloc((size), sizeof(void*)))
#define SK_ALLOC_ALIGNED(s, a) (sk_host_services()->alloc((s), (a)))
#define SK_FREE(ptr) (sk_host_services()->free(ptr))
#define SK_TIME_NS() (sk_host_services()->monotonic_ns())
#define SK_PUTS(str) (sk_host_services()->puts(str))
#define SK_PUTC(c) (sk_host_services()->putc(c))
/**
* Mutex wrapper macros
*/
#define SK_MUTEX_INIT(m) (sk_host_services()->mutex_init(m))
#define SK_MUTEX_LOCK(m) (sk_host_services()->mutex_lock(m))
#define SK_MUTEX_UNLOCK(m) (sk_host_services()->mutex_unlock(m))
#define SK_MUTEX_DESTROY(m) (sk_host_services()->mutex_destroy(m))
#endif /* STARKERNEL_VM_HOST_H */
+556
View File
@@ -0,0 +1,556 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef VM_INNER_INTERP_ARM64_H
#define VM_INNER_INTERP_ARM64_H
#include "vm.h"
/*
* ARM64 DIRECT-THREADED INNER INTERPRETER
* ========================================
*
* ARM64 advantages over x86_64:
* - More registers (31 vs 16)
* - Better branch prediction
* - Conditional execution
* - Load/store with auto-increment
* - Lower power consumption
*
* Register Allocation (using callee-saved registers):
* - x19: VM pointer (callee-saved)
* - x20: Instruction Pointer (IP)
* - x21: Data Stack Pointer (DSP) - actual pointer
* - x22: Return Stack Pointer (RSP) - actual pointer
* - x23: Top of Stack cache (TOS) - keep in register
* - x24-x28: Available for future use
*
* Why more registers help:
* - Can keep TOS in register (x23)
* - More scratch registers for complex operations
* - Less memory traffic
*
* Performance on Raspberry Pi 4:
* - Expected 3-5x speedup for threaded code
* - Better than x86_64 due to more registers
* - Lower power = less thermal throttling
*/
#ifndef USE_DIRECT_THREADING
#define USE_DIRECT_THREADING 0
#endif
#if USE_DIRECT_THREADING
/*
* NEXT macro - ARM64 version
*
* On ARM64, we can use:
* - Load with post-increment (ldr x0, [x20], #8)
* - Indirect branch (br x0)
*/
#define NEXT_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x20], #8\n\t" /* Load word ptr from [IP], IP += 8 */ \
"br x0\n\t" /* Branch to word */ \
::: "x0", "memory" \
)
/*
* DOCOL - Enter a colon definition (ARM64)
*/
static inline void vm_docol_arm64(VM *vm) {
__asm__ __volatile__(
/* Save current IP on return stack */
"str x20, [x22], #8\n\t" /* [RSP] = IP, RSP += 8 */
/* IP = address of colon definition body */
"ldr x0, %[entry]\n\t"
"ldr x20, [x0]\n\t" /* IP = entry->code_field */
/* Continue with NEXT */
"ldr x0, [x20], #8\n\t"
"br x0\n\t"
:
: [entry]"m"(vm->current_executing_entry)
: "x0", "x20", "x22", "memory"
);
}
/*
* EXIT - Return from a colon definition (ARM64)
*/
static inline void vm_exit_arm64(void) {
__asm__ __volatile__(
/* Restore IP from return stack */
"ldr x20, [x22, #-8]!\n\t" /* RSP -= 8, IP = [RSP] (pre-decrement) */
/* Continue with NEXT */
"ldr x0, [x20], #8\n\t"
"br x0\n\t"
::: "x0", "x20", "x22", "memory"
);
}
/*
* Setup registers before entering inner loop
*/
static inline void vm_setup_registers_arm64(VM *vm) {
__asm__ __volatile__(
/* Save callee-saved registers (required by ARM64 ABI) */
"stp x19, x20, [sp, #-80]!\n\t"
"stp x21, x22, [sp, #16]\n\t"
"stp x23, x24, [sp, #32]\n\t"
"stp x25, x26, [sp, #48]\n\t"
"stp x27, x28, [sp, #64]\n\t"
/* Load VM pointer */
"mov x19, %[vm]\n\t" /* x19 = VM pointer */
/* Load IP */
"ldr x20, %[ip]\n\t" /* x20 = IP */
/* Calculate data stack pointer */
"ldr w0, %[dsp]\n\t"
"ldr x1, %[dstack]\n\t"
"add x21, x1, x0, sxtw #3\n\t" /* x21 = &data_stack[dsp] */
/* Calculate return stack pointer */
"ldr w0, %[rsp]\n\t"
"ldr x1, %[rstack]\n\t"
"add x22, x1, x0, sxtw #3\n\t" /* x22 = &return_stack[rsp] */
/* Load TOS into x23 if stack not empty */
"ldr w0, %[dsp]\n\t"
"cmp w0, #0\n\t"
"b.lt 1f\n\t"
"ldr x23, [x21]\n\t" /* x23 = TOS (cached) */
"1:\n\t"
:
: [vm]"r"(vm),
[ip]"m"(vm->ip),
[dsp]"m"(vm->dsp),
[rsp]"m"(vm->rsp),
[dstack]"m"(vm->data_stack),
[rstack]"m"(vm->return_stack)
: "x0", "x1", "x19", "x20", "x21", "x22", "x23", "x24",
"x25", "x26", "x27", "x28", "memory"
);
}
/*
* Save registers back to VM structure
*/
static inline void vm_save_registers_arm64(VM *vm) {
__asm__ __volatile__(
/* Save TOS back to stack */
"str x23, [x21]\n\t"
/* Save IP */
"str x20, %[ip]\n\t"
/* Calculate and save dsp */
"ldr x0, %[dstack]\n\t"
"sub x1, x21, x0\n\t"
"lsr x1, x1, #3\n\t" /* / 8 */
"str w1, %[dsp]\n\t"
/* Calculate and save rsp */
"ldr x0, %[rstack]\n\t"
"sub x1, x22, x0\n\t"
"lsr x1, x1, #3\n\t"
"str w1, %[rsp]\n\t"
/* Restore callee-saved registers */
"ldp x27, x28, [sp, #64]\n\t"
"ldp x25, x26, [sp, #48]\n\t"
"ldp x23, x24, [sp, #32]\n\t"
"ldp x21, x22, [sp, #16]\n\t"
"ldp x19, x20, [sp], #80\n\t"
: [ip]"=m"(vm->ip),
[dsp]"=m"(vm->dsp),
[rsp]"=m"(vm->rsp)
: [dstack]"m"(vm->data_stack),
[rstack]"m"(vm->return_stack)
: "x0", "x1", "memory"
);
}
/**
* @defgroup primitives Fast Primitive Word Implementations
* @brief Register-based stack primitives optimized for ARM64
*
* Key optimization: TOS stays in x23, avoiding memory traffic
* @{
*/
/** @brief DUP primitive - duplicates top of stack item */
#define PRIM_DUP_ARM64() \
__asm__ __volatile__ ( \
"str x23, [x21, #8]!\n\t" /* Store TOS, advance DSP */ \
::: "x21", "memory" \
)
/* DROP - remove top of stack */
#define PRIM_DROP_ARM64() \
__asm__ __volatile__ ( \
"ldr x23, [x21], #-8\n\t" /* Load new TOS, decrement DSP */ \
::: "x21", "x23", "memory" \
)
/* SWAP - swap top two stack items */
#define PRIM_SWAP_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21]\n\t" /* Load NOS */ \
"str x23, [x21]\n\t" /* Store TOS to NOS position */ \
"mov x23, x0\n\t" /* TOS = old NOS */ \
::: "x0", "x23", "memory" \
)
/* OVER - copy second item to top */
#define PRIM_OVER_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21]\n\t" /* Load NOS */ \
"str x23, [x21, #8]!\n\t" /* Store TOS, advance DSP */ \
"mov x23, x0\n\t" /* TOS = NOS */ \
::: "x0", "x21", "x23", "memory" \
)
/* ROT - rotate top three items */
#define PRIM_ROT_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21]\n\t" /* Load item2 */ \
"ldr x1, [x21, #-8]\n\t" /* Load item3 */ \
"str x1, [x21]\n\t" /* item3 -> NOS */ \
"str x23, [x21, #-8]\n\t" /* TOS -> item3 */ \
"mov x23, x0\n\t" /* item2 -> TOS */ \
::: "x0", "x1", "x23", "memory" \
)
/* + - add top two stack items */
#define PRIM_PLUS_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" /* Load NOS, decrement DSP */ \
"add x23, x23, x0\n\t" /* TOS = TOS + NOS */ \
::: "x0", "x21", "x23" \
)
/* - - subtract top from second */
#define PRIM_MINUS_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" /* Load NOS, decrement DSP */ \
"sub x23, x0, x23\n\t" /* TOS = NOS - TOS */ \
::: "x0", "x21", "x23" \
)
/* * - multiply top two stack items */
#define PRIM_STAR_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" /* Load NOS, decrement DSP */ \
"mul x23, x23, x0\n\t" /* TOS = TOS * NOS */ \
::: "x0", "x21", "x23" \
)
/* / - divide second by top */
#define PRIM_SLASH_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" /* Load NOS (dividend), decrement DSP */ \
"sdiv x23, x0, x23\n\t" /* TOS = NOS / TOS */ \
::: "x0", "x21", "x23" \
)
/* MOD - modulo */
#define PRIM_MOD_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" /* Load NOS, decrement DSP */ \
"sdiv x1, x0, x23\n\t" /* quotient = NOS / TOS */ \
"msub x23, x1, x23, x0\n\t" /* TOS = NOS - quotient * TOS */ \
::: "x0", "x1", "x21", "x23" \
)
/* AND - bitwise and */
#define PRIM_AND_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" \
"and x23, x23, x0\n\t" \
::: "x0", "x21", "x23" \
)
/* OR - bitwise or */
#define PRIM_OR_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" \
"orr x23, x23, x0\n\t" \
::: "x0", "x21", "x23" \
)
/* XOR - bitwise xor */
#define PRIM_XOR_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" \
"eor x23, x23, x0\n\t" \
::: "x0", "x21", "x23" \
)
/* INVERT - bitwise not */
#define PRIM_INVERT_ARM64() \
__asm__ __volatile__ ( \
"mvn x23, x23\n\t" \
::: "x23" \
)
/* NEGATE - two's complement */
#define PRIM_NEGATE_ARM64() \
__asm__ __volatile__ ( \
"neg x23, x23\n\t" \
::: "x23" \
)
/* @ - fetch cell from memory */
#define PRIM_FETCH_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x19]\n\t" /* Load VM->memory */ \
"ldr x23, [x0, x23]\n\t" /* TOS = memory[TOS] */ \
::: "x0", "x23", "memory" \
)
/* ! - store cell to memory */
#define PRIM_STORE_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" /* Load address, decrement DSP */ \
"ldr x1, [x19]\n\t" /* Load VM->memory */ \
"str x23, [x1, x0]\n\t" /* memory[addr] = value */ \
"ldr x23, [x21]\n\t" /* Load new TOS */ \
::: "x0", "x1", "x21", "x23", "memory" \
)
/* C@ - fetch byte from memory */
#define PRIM_C_FETCH_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x19]\n\t" \
"ldrb w23, [x0, x23]\n\t" /* Load byte, zero-extend */ \
::: "x0", "x23", "memory" \
)
/* C! - store byte to memory */
#define PRIM_C_STORE_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" \
"ldr x1, [x19]\n\t" \
"strb w23, [x1, x0]\n\t" /* Store byte */ \
"ldr x23, [x21]\n\t" \
::: "x0", "x1", "x21", "x23", "memory" \
)
/* >R - push to return stack */
#define PRIM_TO_R_ARM64() \
__asm__ __volatile__ ( \
"str x23, [x22], #8\n\t" /* Store to RS, advance RSP */ \
"ldr x23, [x21], #-8\n\t" /* Load new TOS, decrement DSP */ \
::: "x21", "x22", "x23", "memory" \
)
/* R> - pop from return stack */
#define PRIM_R_FROM_ARM64() \
__asm__ __volatile__ ( \
"str x23, [x21, #8]!\n\t" /* Store TOS, advance DSP */ \
"ldr x23, [x22, #-8]!\n\t" /* Load from RS, decrement RSP */ \
::: "x21", "x22", "x23", "memory" \
)
/* R@ - copy top of return stack to data stack */
#define PRIM_R_FETCH_ARM64() \
__asm__ __volatile__ ( \
"str x23, [x21, #8]!\n\t" /* Store TOS, advance DSP */ \
"ldr x23, [x22, #-8]\n\t" /* Load from RS (don't pop) */ \
::: "x21", "x23", "memory" \
)
/* 2DUP - duplicate top two items */
#define PRIM_2DUP_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21]\n\t" /* Load NOS */ \
"stp x0, x23, [x21, #8]\n\t" /* Store pair */ \
"add x21, x21, #16\n\t" /* DSP += 2 */ \
::: "x0", "x21", "memory" \
)
/* 2DROP - drop top two items */
#define PRIM_2DROP_ARM64() \
__asm__ __volatile__ ( \
"ldr x23, [x21, #-8]\n\t" /* Load new TOS */ \
"sub x21, x21, #16\n\t" /* DSP -= 2 */ \
::: "x21", "x23", "memory" \
)
/* 0= - test if zero */
#define PRIM_ZERO_EQUALS_ARM64() \
__asm__ __volatile__ ( \
"cmp x23, #0\n\t" \
"cset x23, eq\n\t" /* Set to 1 if zero, else 0 */ \
"neg x23, x23\n\t" /* Convert to -1 (true flag) */ \
::: "x23", "cc" \
)
/* 0< - test if negative */
#define PRIM_ZERO_LESS_ARM64() \
__asm__ __volatile__ ( \
"cmp x23, #0\n\t" \
"cset x23, lt\n\t" \
"neg x23, x23\n\t" \
::: "x23", "cc" \
)
/* = - test equality */
#define PRIM_EQUALS_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" \
"cmp x0, x23\n\t" \
"cset x23, eq\n\t" \
"neg x23, x23\n\t" \
::: "x0", "x21", "x23", "cc" \
)
/* < - test less than (signed) */
#define PRIM_LESS_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" \
"cmp x0, x23\n\t" \
"cset x23, lt\n\t" \
"neg x23, x23\n\t" \
::: "x0", "x21", "x23", "cc" \
)
/* > - test greater than (signed) */
#define PRIM_GREATER_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x21], #-8\n\t" \
"cmp x0, x23\n\t" \
"cset x23, gt\n\t" \
"neg x23, x23\n\t" \
::: "x0", "x21", "x23", "cc" \
)
/*
* Control flow primitives
*/
/* BRANCH - unconditional branch */
#define PRIM_BRANCH_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x20]\n\t" /* Load offset */ \
"add x20, x20, x0\n\t" /* IP += offset */ \
::: "x0", "x20", "memory" \
)
/* 0BRANCH - branch if TOS is zero */
#define PRIM_ZBRANCH_ARM64() \
__asm__ __volatile__ ( \
"ldr x0, [x20]\n\t" /* Load offset */ \
"cmp x23, #0\n\t" \
"add x1, x20, x0\n\t" /* Calculate target */ \
"add x2, x20, #8\n\t" /* Calculate skip */ \
"csel x20, x1, x2, eq\n\t" /* Select target if zero */ \
"ldr x23, [x21], #-8\n\t" /* Load new TOS */ \
::: "x0", "x1", "x2", "x20", "x21", "x23", "cc", "memory" \
)
/* EXECUTE - execute word whose xt is on stack */
#define PRIM_EXECUTE_ARM64() \
__asm__ __volatile__ ( \
"ldr x23, [x21], #-8\n\t" /* Load new TOS */ \
"br x0\n\t" /* Branch to xt */ \
::: "x0", "x21", "x23", "memory" \
)
#else /* !USE_DIRECT_THREADING */
/* Fallback definitions */
#define NEXT_ARM64() do {} while(0)
#define vm_setup_registers_arm64(vm) do {} while(0)
#define vm_save_registers_arm64(vm) do {} while(0)
#endif /* USE_DIRECT_THREADING */
/*
* ============================================================================
* RASPBERRY PI 4 PERFORMANCE TUNING
* ============================================================================
*
* Cache Optimization:
* -------------------
* 1. Align VM structure to cache line (64 bytes):
* typedef struct VM { ... } __attribute__((aligned(64))) VM;
*
* 2. Separate hot/cold data:
* - Keep stacks, IP, SP together (hot)
* - Move error handling, I/O to separate structure (cold)
*
* 3. Prefetch dictionary entries:
* vm_prefetch(vm->latest);
* vm_prefetch(vm->latest->link);
*
* Thermal Management:
* -------------------
* 1. Monitor temperature:
* vcgencmd measure_temp
*
* 2. Add heatsink or fan for sustained load
*
* 3. Reduce CPU frequency if needed:
* echo 1200000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
*
* Power Optimization:
* -------------------
* 1. Use WFE (Wait For Event) in idle loops
* 2. Disable unused cores if single-threaded
* 3. Use NEON for parallel operations (lower power than scalar)
*
* Build Configuration:
* --------------------
* Native: -march=armv8-a+crc+simd -mtune=cortex-a72
* Cross: CC=aarch64-linux-gnu-gcc
*
* ============================================================================
*/
#endif /* VM_INNER_INTERP_ARM64_H */
+643
View File
@@ -0,0 +1,643 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
*** StarForth ***
vm_inner_interp_asm.h - Optimized Inner Interpreter using Direct Threading
Compatible with: Linux, bare metal x86_64
HISTORICAL: the "L4Re Compatibility"/"L4RE INTEGRATION NOTES" throughout
this file describe a target that was supported through mid-2026. L4Re
support has since been removed as an active target; these notes are
retained as historical design rationale.
This work is released under license terms in LICENSE file.
No warranty. Use at your own risk.
*/
#ifndef VM_INNER_INTERP_ASM_H
#define VM_INNER_INTERP_ASM_H
#include "vm.h"
/*
* INNER INTERPRETER OPTIMIZATION
* ===============================
*
* This is the MOST CRITICAL optimization for Forth performance.
*
* Background:
* -----------
* Forth uses "threaded code" where compiled words are sequences of pointers
* to other words. The inner interpreter's job is to:
* 1. Fetch the next word pointer (IP)
* 2. Execute that word
* 3. Increment IP
* 4. Repeat
*
* Traditional C implementation has massive overhead:
* - Function call/return for each word (push/pop return address)
* - Register spilling
* - No tail-call optimization in most C compilers
*
* This optimization uses:
* - Direct threading: Each word ends with a jump to NEXT
* - Register allocation: Keep IP, SP, RP in registers
* - Tail calls: Use jmp instead of call/ret
* - Computed goto: Fast dispatch using indirect jump
*
* Performance Impact:
* ------------------
* Can improve interpreter speed by 2-5x for threaded code execution
*
* L4Re Compatibility: YES
* All operations are unprivileged and pure computation
*/
#ifndef USE_DIRECT_THREADING
#define USE_DIRECT_THREADING 0
#endif
#if USE_DIRECT_THREADING
/**
* @brief Register allocation strategy for optimized execution
*
* Register usage:
* @arg r12 VM pointer (callee-saved, preserved across calls)
* @arg r13 Instruction Pointer (IP) - points to current cell
* @arg r14 Data Stack Pointer (DSP) - points to vm->data_stack[vm->dsp]
* @arg r15 Return Stack Pointer (RSP) - points to vm->return_stack[vm->rsp]
*
* Callee-saved registers are used because:
* - They're preserved across function calls
* - Allows mixing C and assembly code
* - No need to save/restore on each word execution
*/
/**
* @brief Core inner interpreter macro - executes next word
* @details Fetches next word pointer from IP, increments IP, and jumps to word
*/
#define NEXT_ASM() \
__asm__ __volatile__ ( \
"movq (%%r13), %%rax\n\t" /* Load word pointer from [IP] */ \
"addq $8, %%r13\n\t" /* IP++ */ \
"jmpq *%%rax\n\t" /* Jump to word's code */ \
::: "rax", "memory" \
)
/**
* @brief Enter a colon definition (DOCOL)
* @param vm Pointer to VM instance
* @details Called when executing a user-defined word
*/
static inline void vm_docol_asm(VM *vm) {
__asm__ __volatile__(
/* Save current IP on return stack */
"movq %%r13, (%%r15)\n\t" /* *RSP = IP */
"addq $8, %%r15\n\t" /* RSP++ */
/* IP = address of colon definition body (from DictEntry) */
"movq %[entry], %%rax\n\t"
"movq (%%rax), %%r13\n\t" /* IP = entry->code_field */
/* Continue with NEXT */
"movq (%%r13), %%rax\n\t"
"addq $8, %%r13\n\t"
"jmpq *%%rax\n\t"
:
: [entry]"r"(vm->current_executing_entry)
: "rax", "r13", "r15", "memory"
);
}
/**
* @brief Return from a colon definition (EXIT)
* @details Pops IP from return stack and continues execution
*/
static inline void vm_exit_asm(void) {
__asm__ __volatile__(
/* Restore IP from return stack */
"subq $8, %%r15\n\t" /* RSP-- */
"movq (%%r15), %%r13\n\t" /* IP = *RSP */
/* Continue with NEXT */
"movq (%%r13), %%rax\n\t"
"addq $8, %%r13\n\t"
"jmpq *%%rax\n\t"
::: "rax", "r13", "r15", "memory"
);
}
/**
* @brief Main interpreter loop implementation
* @details Replaces execute_colon_word function with optimized assembly
*
* Usage example:
* @code
* vm_setup_registers(vm);
* VM_INNER_LOOP();
* @endcode
*/
/* Setup registers before entering inner loop */
static inline void vm_setup_registers(VM *vm) {
__asm__ __volatile__(
"movq %[vm], %%r12\n\t" /* R12 = VM pointer */
"movq %[ip], %%r13\n\t" /* R13 = IP */
/* Calculate actual stack pointers */
"movl %[dsp], %%eax\n\t"
"leaq (%[dstack], %%rax, 8), %%r14\n\t" /* R14 = &data_stack[dsp] */
"movl %[rsp], %%eax\n\t"
"leaq (%[rstack], %%rax, 8), %%r15\n\t" /* R15 = &return_stack[rsp] */
:
: [vm]"r"(vm),
[ip]"r"(vm->ip),
[dsp]"m"(vm->dsp),
[rsp]"m"(vm->rsp),
[dstack]"r"(vm->data_stack),
[rstack]"r"(vm->return_stack)
: "r12", "r13", "r14", "r15", "rax", "memory"
);
}
/* Save registers back to VM structure */
static inline void vm_save_registers(VM *vm) {
__asm__ __volatile__(
"movq %%r13, %[ip]\n\t" /* Save IP */
/* Calculate dsp from R14 */
"movq %%r14, %%rax\n\t"
"subq %[dstack], %%rax\n\t"
"shrq $3, %%rax\n\t" /* / 8 */
"movl %%eax, %[dsp]\n\t"
/* Calculate rsp from R15 */
"movq %%r15, %%rax\n\t"
"subq %[rstack], %%rax\n\t"
"shrq $3, %%rax\n\t"
"movl %%eax, %[rsp]\n\t"
: [ip]"=m"(vm->ip),
[dsp]"=m"(vm->dsp),
[rsp]"=m"(vm->rsp)
: [dstack]"r"(vm->data_stack),
[rstack]"r"(vm->return_stack)
: "rax", "memory"
);
}
/*
* Fast primitive word implementations using register-based stacks
*
* These use the register-allocated stack pointers (R14/R15) directly
* without going through memory or function calls
*/
/* DUP - duplicate top of stack */
#define PRIM_DUP() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load TOS */ \
"addq $8, %%r14\n\t" /* DSP++ */ \
"movq %%rax, (%%r14)\n\t" /* Store duplicate */ \
::: "rax", "r14", "memory" \
)
/* DROP - remove top of stack */
#define PRIM_DROP() \
__asm__ __volatile__ ( \
"subq $8, %%r14\n\t" /* DSP-- */ \
::: "r14" \
)
/* SWAP - swap top two stack items */
#define PRIM_SWAP() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load TOS */ \
"movq -8(%%r14), %%rcx\n\t" /* Load NOS */ \
"movq %%rcx, (%%r14)\n\t" /* Store NOS to TOS */ \
"movq %%rax, -8(%%r14)\n\t" /* Store TOS to NOS */ \
::: "rax", "rcx", "memory" \
)
/* + - add top two stack items */
#define PRIM_PLUS() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load TOS */ \
"addq %%rax, -8(%%r14)\n\t" /* Add to NOS */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
::: "rax", "r14", "cc", "memory" \
)
/* - - subtract top from second */
#define PRIM_MINUS() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load TOS */ \
"subq %%rax, -8(%%r14)\n\t" /* Subtract from NOS */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
::: "rax", "r14", "cc", "memory" \
)
/* * - multiply top two stack items */
#define PRIM_STAR() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load TOS */ \
"imulq -8(%%r14), %%rax\n\t" /* Multiply with NOS */ \
"movq %%rax, -8(%%r14)\n\t" /* Store result */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
::: "rax", "rdx", "r14", "cc", "memory" \
)
/* @ - fetch cell from memory */
#define PRIM_FETCH() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load address from TOS */ \
"movq (%%r12), %%rcx\n\t" /* Load VM->memory */ \
"movq (%%rcx, %%rax, 1), %%rax\n\t" /* Load cell from memory */ \
"movq %%rax, (%%r14)\n\t" /* Store to TOS */ \
::: "rax", "rcx", "memory" \
)
/* ! - store cell to memory */
#define PRIM_STORE() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load address from TOS */ \
"movq -8(%%r14), %%rcx\n\t" /* Load value from NOS */ \
"movq (%%r12), %%rdx\n\t" /* Load VM->memory */ \
"movq %%rcx, (%%rdx, %%rax, 1)\n\t" /* Store to memory */ \
"subq $16, %%r14\n\t" /* DSP -= 2 */ \
::: "rax", "rcx", "rdx", "r14", "memory" \
)
/* >R - push to return stack */
#define PRIM_TO_R() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load from TOS */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
"movq %%rax, (%%r15)\n\t" /* Store to return stack */ \
"addq $8, %%r15\n\t" /* RSP++ */ \
::: "rax", "r14", "r15", "memory" \
)
/* R> - pop from return stack */
#define PRIM_R_FROM() \
__asm__ __volatile__ ( \
"subq $8, %%r15\n\t" /* RSP-- */ \
"movq (%%r15), %%rax\n\t" /* Load from return stack */ \
"addq $8, %%r14\n\t" /* DSP++ */ \
"movq %%rax, (%%r14)\n\t" /* Store to TOS */ \
::: "rax", "r14", "r15", "memory" \
)
/* R@ - copy top of return stack to data stack */
#define PRIM_R_FETCH() \
__asm__ __volatile__ ( \
"movq -8(%%r15), %%rax\n\t" /* Load from top of return stack */ \
"addq $8, %%r14\n\t" /* DSP++ */ \
"movq %%rax, (%%r14)\n\t" /* Store to TOS */ \
::: "rax", "r14", "memory" \
)
/*
* BRANCH and 0BRANCH - Control flow primitives
* These modify IP based on conditions
*/
/* BRANCH - unconditional branch */
#define PRIM_BRANCH() \
__asm__ __volatile__ ( \
"movq (%%r13), %%rax\n\t" /* Load offset from IP */ \
"addq %%rax, %%r13\n\t" /* IP += offset */ \
::: "rax", "r13", "memory" \
)
/* 0BRANCH - branch if TOS is zero */
#define PRIM_ZBRANCH() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load TOS */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
"testq %%rax, %%rax\n\t" /* Test if zero */ \
"jnz 1f\n\t" /* Skip if non-zero */ \
"movq (%%r13), %%rax\n\t" /* Load offset */ \
"addq %%rax, %%r13\n\t" /* IP += offset */ \
"jmp 2f\n\t" \
"1:\n\t" \
"addq $8, %%r13\n\t" /* Skip offset */ \
"2:\n\t" \
::: "rax", "r13", "r14", "cc", "memory" \
)
/* OVER - copy second item to top of stack */
#define PRIM_OVER() \
__asm__ __volatile__ ( \
"movq -8(%%r14), %%rax\n\t" /* Load NOS */ \
"addq $8, %%r14\n\t" /* DSP++ */ \
"movq %%rax, (%%r14)\n\t" /* Store NOS as new TOS */ \
::: "rax", "r14", "memory" \
)
/* ROT - rotate top three items ( a b c -- b c a ) */
#define PRIM_ROT() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* c = TOS */ \
"movq -8(%%r14), %%rcx\n\t" /* b = NOS */ \
"movq -16(%%r14), %%rdx\n\t" /* a = third */ \
"movq %%rdx, -8(%%r14)\n\t" /* a -> NOS */ \
"movq %%rax, -16(%%r14)\n\t" /* c -> third */ \
"movq %%rcx, (%%r14)\n\t" /* b -> TOS */ \
::: "rax", "rcx", "rdx", "memory" \
)
/* / - signed divide second by top */
#define PRIM_SLASH() \
__asm__ __volatile__ ( \
"movq -8(%%r14), %%rax\n\t" /* Load dividend (NOS) */ \
"cqo\n\t" /* Sign-extend rax to rdx:rax */ \
"idivq (%%r14)\n\t" /* Divide by TOS */ \
"movq %%rax, -8(%%r14)\n\t" /* Store quotient to NOS slot */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
::: "rax", "rdx", "r14", "cc", "memory" \
)
/* MOD - signed modulo */
#define PRIM_MOD() \
__asm__ __volatile__ ( \
"movq -8(%%r14), %%rax\n\t" /* Load dividend (NOS) */ \
"cqo\n\t" /* Sign-extend */ \
"idivq (%%r14)\n\t" /* rdx = remainder */ \
"movq %%rdx, -8(%%r14)\n\t" /* Store remainder to NOS slot */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
::: "rax", "rdx", "r14", "cc", "memory" \
)
/* AND - bitwise and */
#define PRIM_AND() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load TOS */ \
"andq %%rax, -8(%%r14)\n\t" /* NOS &= TOS */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
::: "rax", "r14", "cc", "memory" \
)
/* OR - bitwise or */
#define PRIM_OR() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load TOS */ \
"orq %%rax, -8(%%r14)\n\t" /* NOS |= TOS */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
::: "rax", "r14", "cc", "memory" \
)
/* XOR - bitwise xor */
#define PRIM_XOR() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load TOS */ \
"xorq %%rax, -8(%%r14)\n\t" /* NOS ^= TOS */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
::: "rax", "r14", "cc", "memory" \
)
/* INVERT - bitwise complement */
#define PRIM_INVERT() \
__asm__ __volatile__ ( \
"notq (%%r14)\n\t" /* TOS = ~TOS */ \
::: "memory" \
)
/* NEGATE - two's complement negation */
#define PRIM_NEGATE() \
__asm__ __volatile__ ( \
"negq (%%r14)\n\t" /* TOS = -TOS */ \
::: "cc", "memory" \
)
/* C@ - fetch byte from memory */
#define PRIM_C_FETCH() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load address from TOS */ \
"movq (%%r12), %%rcx\n\t" /* Load VM->memory */ \
"movzbq (%%rcx, %%rax, 1), %%rax\n\t" /* Load byte, zero-extend */ \
"movq %%rax, (%%r14)\n\t" /* Store byte value to TOS */ \
::: "rax", "rcx", "memory" \
)
/* C! - store byte to memory */
#define PRIM_C_STORE() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load address from TOS */ \
"movq -8(%%r14), %%rcx\n\t" /* Load byte value from NOS */ \
"movq (%%r12), %%rdx\n\t" /* Load VM->memory */ \
"movb %%cl, (%%rdx, %%rax, 1)\n\t" /* Store byte */ \
"subq $16, %%r14\n\t" /* DSP -= 2 */ \
::: "rax", "rcx", "rdx", "r14", "memory" \
)
/* 2DUP - duplicate top two items ( a b -- a b a b ) */
#define PRIM_2DUP() \
__asm__ __volatile__ ( \
"movq -8(%%r14), %%rax\n\t" /* Load NOS (a) */ \
"movq (%%r14), %%rcx\n\t" /* Load TOS (b) */ \
"movq %%rax, 8(%%r14)\n\t" /* Store a above TOS */ \
"movq %%rcx, 16(%%r14)\n\t" /* Store b above that */ \
"addq $16, %%r14\n\t" /* DSP += 2 */ \
::: "rax", "rcx", "r14", "memory" \
)
/* 2DROP - drop top two items */
#define PRIM_2DROP() \
__asm__ __volatile__ ( \
"subq $16, %%r14\n\t" /* DSP -= 2 */ \
::: "r14" \
)
/* 0= - true (-1) if TOS is zero, false (0) otherwise */
#define PRIM_ZERO_EQUALS() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load TOS */ \
"testq %%rax, %%rax\n\t" /* Test for zero */ \
"sete %%al\n\t" /* AL = 1 if zero */ \
"movzbq %%al, %%rax\n\t" /* Zero-extend */ \
"negq %%rax\n\t" /* -1 if true, 0 if false */ \
"movq %%rax, (%%r14)\n\t" /* Store result */ \
::: "rax", "cc", "memory" \
)
/* 0< - true (-1) if TOS is negative, false (0) otherwise */
#define PRIM_ZERO_LESS() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load TOS */ \
"sarq $63, %%rax\n\t" /* Arithmetic shift: -1 if negative, 0 otherwise */ \
"movq %%rax, (%%r14)\n\t" /* Store result */ \
::: "rax", "cc", "memory" \
)
/* = - true (-1) if top two items are equal, false (0) otherwise */
#define PRIM_EQUALS() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load TOS (b) */ \
"cmpq %%rax, -8(%%r14)\n\t" /* Compare NOS (a) with TOS (b) */ \
"sete %%al\n\t" /* AL = 1 if equal */ \
"movzbq %%al, %%rax\n\t" /* Zero-extend */ \
"negq %%rax\n\t" /* -1 if true, 0 if false */ \
"movq %%rax, -8(%%r14)\n\t" /* Store result to NOS slot */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
::: "rax", "r14", "cc", "memory" \
)
/* < - true (-1) if NOS < TOS (signed), false (0) otherwise */
#define PRIM_LESS() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rcx\n\t" /* Load TOS (b) */ \
"movq -8(%%r14), %%rax\n\t" /* Load NOS (a) */ \
"cmpq %%rcx, %%rax\n\t" /* rax - rcx: signed compare a vs b */ \
"setl %%al\n\t" /* AL = 1 if a < b */ \
"movzbq %%al, %%rax\n\t" /* Zero-extend */ \
"negq %%rax\n\t" /* -1 if true, 0 if false */ \
"movq %%rax, -8(%%r14)\n\t" /* Store result to NOS slot */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
::: "rax", "rcx", "r14", "cc", "memory" \
)
/* > - true (-1) if NOS > TOS (signed), false (0) otherwise */
#define PRIM_GREATER() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rcx\n\t" /* Load TOS (b) */ \
"movq -8(%%r14), %%rax\n\t" /* Load NOS (a) */ \
"cmpq %%rcx, %%rax\n\t" /* rax - rcx: signed compare a vs b */ \
"setg %%al\n\t" /* AL = 1 if a > b */ \
"movzbq %%al, %%rax\n\t" /* Zero-extend */ \
"negq %%rax\n\t" /* -1 if true, 0 if false */ \
"movq %%rax, -8(%%r14)\n\t" /* Store result to NOS slot */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
::: "rax", "rcx", "r14", "cc", "memory" \
)
/* EXECUTE - execute word whose execution token is on stack */
#define PRIM_EXECUTE() \
__asm__ __volatile__ ( \
"movq (%%r14), %%rax\n\t" /* Load xt from TOS */ \
"subq $8, %%r14\n\t" /* DSP-- */ \
"jmpq *%%rax\n\t" /* Jump to execution token */ \
::: "rax", "r14", "memory" \
)
#else /* !USE_DIRECT_THREADING */
/* Fallback definitions when direct threading is disabled */
#define NEXT_ASM() do {} while(0)
#define vm_setup_registers(vm) do {} while(0)
#define vm_save_registers(vm) do {} while(0)
#endif /* USE_DIRECT_THREADING */
/*
* ============================================================================
* INTEGRATION GUIDE
* ============================================================================
*
* To integrate these optimizations:
*
* 1. Enable in your build:
* -DUSE_DIRECT_THREADING=1 -DUSE_ASM_OPT=1
*
* 2. Modify your inner interpreter in vm.c:
*
* void execute_threaded_code(VM *vm) {
* vm_setup_registers(vm);
*
* // Execute words...
* // Each primitive word should use PRIM_* macros
* // and end with NEXT_ASM()
*
* vm_save_registers(vm);
* }
*
* 3. For each primitive word, replace function with macro:
*
* Instead of:
* void word_dup(VM *vm) { vm_push(vm, vm->data_stack[vm->dsp]); }
*
* Use:
* PRIM_DUP(); NEXT_ASM();
*
* 4. Benchmark before and after!
*
* ============================================================================
* PERFORMANCE NOTES
* ============================================================================
*
* Expected speedup: 2-5x for threaded code execution
*
* Why so fast?
* - No function call overhead (10+ cycles saved per word)
* - Register allocation eliminates memory traffic
* - Direct jumps instead of call/return (better branch prediction)
* - Cache-friendly: hot path stays in I-cache
*
* Trade-offs:
* - More complex code
* - Harder to debug (use regular C version for debugging)
* - Platform-specific (x86_64 only)
* - Requires more careful register management
*
* ============================================================================
* L4RE INTEGRATION NOTES
* ============================================================================
*
* All safe for L4Re because:
* - No privileged instructions
* - No system calls
* - Preserved ABI (callee-saved registers)
* - Can be used in both user and kernel contexts
*
* For StarshipOS:
* - These optimizations work in L4Re tasks
* - Safe for real-time contexts (deterministic timing)
* - No FPU usage (kernel-safe)
* - Stack usage is minimal
*
* ============================================================================
*/
#endif /* VM_INNER_INTERP_ASM_H */
+539
View File
@@ -0,0 +1,539 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef VM_INNER_INTERP_RISCV64_H
#define VM_INNER_INTERP_RISCV64_H
#include "vm.h"
/*
* RISC-V 64 DIRECT-THREADED INNER INTERPRETER
* ============================================
*
* Register Allocation (callee-saved s2–s6):
* - s2 (x18): VM pointer
* - s3 (x19): Instruction Pointer (IP)
* - s4 (x20): Data Stack Pointer (DSP) — pointer to TOS cell
* - s5 (x21): Return Stack Pointer (RSP) — pointer to top return entry
* - s6 (x22): Top-Of-Stack cache (TOS) — avoids memory round-trip for TOS
*
* Using callee-saved registers means the compiler will save/restore them
* around any C calls made while the inner interpreter is running.
*
* Stack convention (matches x86_64 and ARM64 implementations):
* - Stack grows upward; DSP points to the current TOS cell.
* - TOS is additionally cached in s6 to avoid one load per operation.
* - RSP points to the top return-address cell (not past it).
*
* NEXT macro (direct-threaded dispatch):
* ld a0, 0(s3) # a0 = *IP
* addi s3, s3, 8 # IP += 8
* jr a0 # jump to code field (jalr x0, a0, 0)
*
* Expected performance: 1.5–2.5x vs interpreter loop (fewer branch
* mispredictions, tighter dispatch path). Less than ARM64 TOS-cache gain
* because RISC-V lacks post-increment addressing for combined load+advance.
*/
#ifndef USE_DIRECT_THREADING
#define USE_DIRECT_THREADING 0
#endif
#if USE_DIRECT_THREADING
/*
* NEXT — fetch and dispatch the next word
*
* 'jr a0' is a pseudoinstruction for 'jalr x0, a0, 0' which discards
* the return address (x0 is hardwired zero).
*/
#define NEXT_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s3)\n\t" /* a0 = *IP */ \
"addi s3, s3, 8\n\t" /* IP++ */ \
"jr a0\n\t" /* jump to code field */ \
::: "a0", "memory" \
)
/*
* DOCOL — enter a colon definition
*/
static inline void vm_docol_rv64(VM *vm) {
__asm__ __volatile__(
/* Push IP onto return stack */
"addi s5, s5, 8\n\t"
"sd s3, 0(s5)\n\t"
/* Load new IP from current_executing_entry->code_field */
"ld a0, %[entry]\n\t"
"ld s3, 0(a0)\n\t"
/* NEXT */
"ld a0, 0(s3)\n\t"
"addi s3, s3, 8\n\t"
"jr a0\n\t"
:
: [entry]"m"(vm->current_executing_entry)
: "a0", "s3", "s5", "memory"
);
}
/*
* EXIT — return from a colon definition
*/
static inline void vm_exit_rv64(void) {
__asm__ __volatile__(
/* Pop IP from return stack */
"ld s3, 0(s5)\n\t"
"addi s5, s5, -8\n\t"
/* NEXT */
"ld a0, 0(s3)\n\t"
"addi s3, s3, 8\n\t"
"jr a0\n\t"
::: "a0", "s3", "s5", "memory"
);
}
/*
* Setup registers before entering the inner interpreter loop.
* Saves all callee-saved registers we're about to clobber, then
* loads VM state into s2–s6.
*/
static inline void vm_setup_registers_rv64(VM *vm) {
__asm__ __volatile__(
/* Save callee-saved regs we're about to use */
"addi sp, sp, -80\n\t"
"sd s2, 0(sp)\n\t"
"sd s3, 8(sp)\n\t"
"sd s4, 16(sp)\n\t"
"sd s5, 24(sp)\n\t"
"sd s6, 32(sp)\n\t"
"sd s7, 40(sp)\n\t"
"sd s8, 48(sp)\n\t"
"sd s9, 56(sp)\n\t"
"sd s10,64(sp)\n\t"
"sd s11,72(sp)\n\t"
/* s2 = VM pointer */
"mv s2, %[vm]\n\t"
/* s3 = IP */
"ld s3, %[ip]\n\t"
/* s4 = &data_stack[dsp] */
"lw a0, %[dsp]\n\t"
"ld a1, %[dstack]\n\t"
"slli a0, a0, 3\n\t"
"add s4, a1, a0\n\t"
/* s5 = &return_stack[rsp] */
"lw a0, %[rsp]\n\t"
"ld a1, %[rstack]\n\t"
"slli a0, a0, 3\n\t"
"add s5, a1, a0\n\t"
/* s6 = TOS (load if stack non-empty) */
"lw a0, %[dsp]\n\t"
"bltz a0, 1f\n\t"
"ld s6, 0(s4)\n\t"
"1:\n\t"
:
: [vm]"r"(vm),
[ip]"m"(vm->ip),
[dsp]"m"(vm->dsp),
[rsp]"m"(vm->rsp),
[dstack]"m"(vm->data_stack),
[rstack]"m"(vm->return_stack)
: "a0", "a1",
"s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11",
"memory"
);
}
/*
* Save registers back to VM structure and restore callee-saved regs.
*/
static inline void vm_save_registers_rv64(VM *vm) {
__asm__ __volatile__(
/* Flush TOS back to stack */
"sd s6, 0(s4)\n\t"
/* Save IP */
"sd s3, %[ip]\n\t"
/* dsp = (s4 - data_stack_base) / 8 */
"ld a0, %[dstack]\n\t"
"sub a1, s4, a0\n\t"
"srli a1, a1, 3\n\t"
"sw a1, %[dsp]\n\t"
/* rsp = (s5 - return_stack_base) / 8 */
"ld a0, %[rstack]\n\t"
"sub a1, s5, a0\n\t"
"srli a1, a1, 3\n\t"
"sw a1, %[rsp]\n\t"
/* Restore callee-saved regs */
"ld s2, 0(sp)\n\t"
"ld s3, 8(sp)\n\t"
"ld s4, 16(sp)\n\t"
"ld s5, 24(sp)\n\t"
"ld s6, 32(sp)\n\t"
"ld s7, 40(sp)\n\t"
"ld s8, 48(sp)\n\t"
"ld s9, 56(sp)\n\t"
"ld s10,64(sp)\n\t"
"ld s11,72(sp)\n\t"
"addi sp, sp, 80\n\t"
: [ip]"=m"(vm->ip),
[dsp]"=m"(vm->dsp),
[rsp]"=m"(vm->rsp)
: [dstack]"m"(vm->data_stack),
[rstack]"m"(vm->return_stack)
: "a0", "a1", "memory"
);
}
/* ============================================================================
* Primitive Word Macros
*
* TOS is cached in s6. s4 = DSP (pointer to TOS cell). s5 = RSP.
* s2 = VM pointer (used for memory-relative ops).
* a0/a1 are scratch (caller-saved, safe to clobber).
* ============================================================================
*/
/* DUP — duplicate TOS */
#define PRIM_DUP_RV64() \
__asm__ __volatile__ ( \
"addi s4, s4, 8\n\t" /* DSP++ */ \
"sd s6, 0(s4)\n\t" /* push TOS copy */ \
::: "s4", "memory" \
)
/* DROP — discard TOS */
#define PRIM_DROP_RV64() \
__asm__ __volatile__ ( \
"ld s6, -8(s4)\n\t" /* new TOS = NOS */ \
"addi s4, s4, -8\n\t" /* DSP-- */ \
::: "s4", "s6", "memory" \
)
/* SWAP — swap TOS and NOS */
#define PRIM_SWAP_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" /* a0 = NOS */ \
"sd s6, 0(s4)\n\t" /* NOS = old TOS */ \
"mv s6, a0\n\t" /* TOS = old NOS */ \
::: "a0", "s6", "memory" \
)
/* OVER — copy NOS to TOS */
#define PRIM_OVER_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" /* a0 = NOS */ \
"addi s4, s4, 8\n\t" /* DSP++ */ \
"sd s6, 0(s4)\n\t" /* push old TOS */ \
"mv s6, a0\n\t" /* TOS = NOS */ \
::: "a0", "s4", "s6", "memory" \
)
/* ROT — rotate top three: ( a b c — b c a ) */
#define PRIM_ROT_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" /* a0 = item2 (NOS) */ \
"ld a1, -8(s4)\n\t" /* a1 = item3 */ \
"sd a1, 0(s4)\n\t" /* item3 -> NOS position */ \
"sd s6, -8(s4)\n\t" /* TOS -> item3 position */ \
"mv s6, a0\n\t" /* item2 -> TOS */ \
::: "a0", "a1", "s6", "memory" \
)
/* + */
#define PRIM_PLUS_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" \
"addi s4, s4, -8\n\t" \
"add s6, s6, a0\n\t" \
::: "a0", "s4", "s6" \
)
/* - */
#define PRIM_MINUS_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" /* a0 = NOS */ \
"addi s4, s4, -8\n\t" \
"sub s6, a0, s6\n\t" /* TOS = NOS - TOS */ \
::: "a0", "s4", "s6" \
)
/* * */
#define PRIM_STAR_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" \
"addi s4, s4, -8\n\t" \
"mul s6, s6, a0\n\t" \
::: "a0", "s4", "s6" \
)
/* / — signed divide NOS/TOS */
#define PRIM_SLASH_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" /* a0 = NOS (dividend) */ \
"addi s4, s4, -8\n\t" \
"div s6, a0, s6\n\t" /* TOS = NOS / TOS */ \
::: "a0", "s4", "s6" \
)
/* MOD */
#define PRIM_MOD_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" \
"addi s4, s4, -8\n\t" \
"rem s6, a0, s6\n\t" \
::: "a0", "s4", "s6" \
)
/* AND */
#define PRIM_AND_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" \
"addi s4, s4, -8\n\t" \
"and s6, s6, a0\n\t" \
::: "a0", "s4", "s6" \
)
/* OR */
#define PRIM_OR_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" \
"addi s4, s4, -8\n\t" \
"or s6, s6, a0\n\t" \
::: "a0", "s4", "s6" \
)
/* XOR */
#define PRIM_XOR_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" \
"addi s4, s4, -8\n\t" \
"xor s6, s6, a0\n\t" \
::: "a0", "s4", "s6" \
)
/* INVERT — bitwise NOT */
#define PRIM_INVERT_RV64() \
__asm__ __volatile__ ( \
"not s6, s6\n\t" \
::: "s6" \
)
/* NEGATE — two's complement */
#define PRIM_NEGATE_RV64() \
__asm__ __volatile__ ( \
"neg s6, s6\n\t" \
::: "s6" \
)
/* @ — fetch cell from VM memory */
#define PRIM_FETCH_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s2)\n\t" /* a0 = vm->memory */ \
"add a0, a0, s6\n\t" /* a0 = &memory[TOS] */ \
"ld s6, 0(a0)\n\t" /* TOS = memory[TOS] */ \
::: "a0", "s6", "memory" \
)
/* ! — store cell to VM memory */
#define PRIM_STORE_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" /* a0 = addr (NOS) */ \
"addi s4, s4, -8\n\t" \
"ld a1, 0(s2)\n\t" /* a1 = vm->memory */ \
"add a1, a1, a0\n\t" \
"sd s6, 0(a1)\n\t" /* memory[addr] = TOS */ \
"ld s6, 0(s4)\n\t" /* load new TOS */ \
"addi s4, s4, -8\n\t" \
::: "a0", "a1", "s4", "s6", "memory" \
)
/* C@ — fetch byte */
#define PRIM_C_FETCH_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s2)\n\t" \
"add a0, a0, s6\n\t" \
"lbu s6, 0(a0)\n\t" /* zero-extend byte */ \
::: "a0", "s6", "memory" \
)
/* C! — store byte */
#define PRIM_C_STORE_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" /* a0 = addr */ \
"addi s4, s4, -8\n\t" \
"ld a1, 0(s2)\n\t" \
"add a1, a1, a0\n\t" \
"sb s6, 0(a1)\n\t" \
"ld s6, 0(s4)\n\t" \
"addi s4, s4, -8\n\t" \
::: "a0", "a1", "s4", "s6", "memory" \
)
/* >R — push TOS to return stack */
#define PRIM_TO_R_RV64() \
__asm__ __volatile__ ( \
"addi s5, s5, 8\n\t" \
"sd s6, 0(s5)\n\t" /* RS[top] = TOS */ \
"ld s6, 0(s4)\n\t" /* new TOS = NOS */ \
"addi s4, s4, -8\n\t" \
::: "s4", "s5", "s6", "memory" \
)
/* R> — pop return stack to data stack */
#define PRIM_R_FROM_RV64() \
__asm__ __volatile__ ( \
"addi s4, s4, 8\n\t" \
"sd s6, 0(s4)\n\t" /* push old TOS */ \
"ld s6, 0(s5)\n\t" /* TOS = RS top */ \
"addi s5, s5, -8\n\t" \
::: "s4", "s5", "s6", "memory" \
)
/* R@ — copy top of return stack (no pop) */
#define PRIM_R_FETCH_RV64() \
__asm__ __volatile__ ( \
"addi s4, s4, 8\n\t" \
"sd s6, 0(s4)\n\t" \
"ld s6, 0(s5)\n\t" /* TOS = RS top (no pop) */ \
::: "s4", "s6", "memory" \
)
/* 2DUP — duplicate top two */
#define PRIM_2DUP_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" /* a0 = NOS */ \
"addi s4, s4, 8\n\t" \
"sd a0, 0(s4)\n\t" /* push NOS */ \
"addi s4, s4, 8\n\t" \
"sd s6, 0(s4)\n\t" /* push TOS copy */ \
::: "a0", "s4", "memory" \
)
/* 2DROP — drop top two */
#define PRIM_2DROP_RV64() \
__asm__ __volatile__ ( \
"ld s6, -8(s4)\n\t" /* new TOS = item3 */ \
"addi s4, s4, -16\n\t" \
::: "s4", "s6", "memory" \
)
/* 0= */
#define PRIM_ZERO_EQUALS_RV64() \
__asm__ __volatile__ ( \
"seqz s6, s6\n\t" /* 1 if zero, else 0 */ \
"neg s6, s6\n\t" /* convert to -1 (true) */ \
::: "s6" \
)
/* 0< */
#define PRIM_ZERO_LESS_RV64() \
__asm__ __volatile__ ( \
"srai s6, s6, 63\n\t" /* -1 if negative, 0 if >= 0 */ \
::: "s6" \
)
/* = */
#define PRIM_EQUALS_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" \
"addi s4, s4, -8\n\t" \
"sub s6, a0, s6\n\t" \
"seqz s6, s6\n\t" \
"neg s6, s6\n\t" \
::: "a0", "s4", "s6" \
)
/* < — NOS < TOS */
#define PRIM_LESS_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" /* a0 = NOS */ \
"addi s4, s4, -8\n\t" \
"slt s6, a0, s6\n\t" /* 1 if NOS < TOS */ \
"neg s6, s6\n\t" \
::: "a0", "s4", "s6" \
)
/* > — NOS > TOS */
#define PRIM_GREATER_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s4)\n\t" \
"addi s4, s4, -8\n\t" \
"slt s6, s6, a0\n\t" /* 1 if TOS < NOS, i.e. NOS > TOS */ \
"neg s6, s6\n\t" \
::: "a0", "s4", "s6" \
)
/* BRANCH — unconditional */
#define PRIM_BRANCH_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s3)\n\t" /* offset */ \
"add s3, s3, a0\n\t" /* IP += offset */ \
::: "a0", "s3", "memory" \
)
/* 0BRANCH — branch if TOS == 0 */
#define PRIM_ZBRANCH_RV64() \
__asm__ __volatile__ ( \
"ld a0, 0(s3)\n\t" /* offset */ \
"ld s6, 0(s4)\n\t" /* new TOS */ \
"addi s4, s4, -8\n\t" \
/* if old TOS was zero: IP += offset; else IP += 8 */ \
"bnez s6, 1f\n\t" /* skip branch if nonzero */ \
"add s3, s3, a0\n\t" \
"j 2f\n\t" \
"1:\n\t" \
"addi s3, s3, 8\n\t" \
"2:\n\t" \
::: "a0", "s3", "s4", "s6", "memory" \
)
/* EXECUTE — execute word whose xt is TOS */
#define PRIM_EXECUTE_RV64() \
__asm__ __volatile__ ( \
"mv a0, s6\n\t" /* a0 = xt */ \
"ld s6, 0(s4)\n\t" /* new TOS */ \
"addi s4, s4, -8\n\t" \
"jr a0\n\t" /* jump to xt */ \
::: "a0", "s4", "s6", "memory" \
)
#else /* !USE_DIRECT_THREADING */
#define NEXT_RV64() do {} while(0)
#define vm_setup_registers_rv64(vm) do {} while(0)
#define vm_save_registers_rv64(vm) do {} while(0)
#endif /* USE_DIRECT_THREADING */
#endif /* VM_INNER_INTERP_RISCV64_H */
+62
View File
@@ -0,0 +1,62 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef WORD_REGISTRY_H
#define WORD_REGISTRY_H
#include "vm.h"
/**
* @brief Registers a single FORTH word in the virtual machine
* @param vm Pointer to the virtual machine instance
* @param name Name of the FORTH word to register
* @param func Function pointer to the word's implementation
*/
void register_word(VM *vm, const char *name, word_func_t func);
/*
* @brief Registers all standard FORTH-79 words in the virtual machine
* @param vm Pointer to the virtual machine instance
*/
void register_forth79_words(VM * vm);
#endif /* WORD_REGISTRY_H */