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
+51
View File
@@ -0,0 +1,51 @@
# src/word_source/
FORTH-79 word implementations, one file per category, registered into the
dictionary via `include/word_registry.h`. Each `.c` file here has a
matching header in `src/word_source/include/` and (for most categories) a
matching test module in `src/test_runner/modules/`.
**FORTH-79 core**
- `arithmetic_words.c`, `mixed_arithmetic_words.c` — `+ - * / MOD ABS MIN
MAX` and mixed-precision arithmetic.
- `stack_words.c` — `DUP DROP SWAP ROT OVER NIP TUCK`.
- `control_words.c` — `IF ELSE THEN DO LOOP BEGIN UNTIL WHILE`.
- `defining_words.c` — `: ; CREATE DOES> VARIABLE CONSTANT`.
- `memory_words.c` — `@ ! C@ C! MOVE FILL`.
- `return_stack_words.c` — `>R R> R@ RDROP 2>R 2R@ 2R>`.
- `double_words.c` — `2DUP 2DROP 2SWAP 2@ 2! D+ D-`.
- `logical_words.c` — `AND OR XOR NOT INVERT LSHIFT RSHIFT`.
- `io_words.c` — `EMIT KEY TYPE CR TAB SPACE ACCEPT`.
- `string_words.c` — `S" SLITERAL` and other string operations.
- `block_words.c` — `BLOCK BUFFER LOAD THRU FLUSH`.
- `format_words.c` — `.( .R .S HEX DECIMAL BASE`.
- `system_words.c` — `BYE ABORT INCLUDE STATE`.
- `dictionary_words.c` — `FIND SEARCH-WORDLIST WORDS`.
- `dictionary_manipulation_words.c` — dictionary entry manipulation words.
- `vocabulary_words.c` — `VOCABULARY DEFINITIONS FORTH-WORDLIST`.
- `editor_words.c` — block editor words.
- `defer_words.c` — `DEFER`/`IS` deferred-word mechanism, used by the
Tripod message-dispatch shim (see `tools/hermes_tripod_smoke.sh`).
**StarForth-specific extensions**
- `q48_16_words.c`, `q48_words.c` — Q48.16 fixed-point word bindings.
- `starforth_words.c` — StarForth-specific extensions.
- `acl_words.c` — word-level ACL system's FORTH-callable primitives (see
`docs/03-architecture/word-acl/`).
- `lifecycle_words_hosted.c` — hosted-build VM lifecycle words (birth/run
primitives on the hosted side; kernel-only primitives live in
`src/starkernel/capsule/mama_forth_words.c`).
- `log_words.c` — FORTH-callable logging primitives.
- `inference_words.c` — FORTH-callable bindings for the statistical
inference engine (Loops #5/#6).
- `physics_benchmark_words.c` — benchmark harness for the 7 physics
feedback loops.
- `physics_diagnostic_words.c` — physics diagnostics (`WORD-ENTROPY`).
- `physics_freeze_words.c` — `PHYSICS-FREEZE` / `PHYSICS-THAW`.
- `physics_pipelining_diagnostic_words.c` — Loop #4 pipelining
diagnostics.
- `dictionary_heat_diagnostic_words.c` — Loop #1 heat diagnostics.
See `src/word_source/include/` for the matching headers (also includes
`mama_forth_words.h`, whose `.c` implementation lives under
`src/starkernel/capsule/` since it's kernel-only).
+355
View File
@@ -0,0 +1,355 @@
/*
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.
*/
/*
* acl_words.c — C primitive words for the word-level ACL system
*
* All policy logic lives in capsules/ACL.4th. These primitives expose
* DictEntry ACL fields to FORTH: ACL-MODE@, ACL-MODE!, ACL-PINNED?,
* ACL-PIN, ACL-TTL@, ACL-TTL!, ACL-ALLOW@, ACL-ALLOW!, ACL-HEAT@,
* ACL-INHERIT, ACL-INIT-PRIMITIVES, ACL-WORD-ID.
*
* All words that take an XT expect a DictEntry* cast to cell_t on the
* stack — exactly what ' (tick) produces in StarForth.
*/
#include "include/acl_words.h"
#include "../../include/vm.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
/**
* @brief Pop an execution token (XT) from the data stack and return the
* corresponding DictEntry pointer.
*
* Sets @c vm->error and returns NULL on stack underflow or if the XT is zero.
* All ACL word implementations call this before accessing DictEntry fields.
*
* @param vm Pointer to the VM structure
* @return Pointer to the DictEntry, or NULL on error
*/
static DictEntry *pop_xt(VM *vm)
{
if (!vm || vm->dsp < 0) {
log_message(LOG_ERROR, "ACL word: stack underflow");
if (vm) vm->error = 1;
return NULL;
}
cell_t xt = vm_pop(vm);
DictEntry *entry = (DictEntry *)(uintptr_t)xt;
if (!entry) {
log_message(LOG_ERROR, "ACL word: NULL XT");
vm->error = 1;
return NULL;
}
return entry;
}
/**
* @brief Implements FORTH word @c ACL-MODE@ — fetch ACL enforcement mode
*
* Stack effect: ( xt -- mode )
* Pushes the enforcement mode of the word identified by @c xt:
* 0 = STRICT (allow/deny is permanent), 1 = TTL (expires after countdown).
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_mode_fetch(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
vm_push(vm, (cell_t)e->acl_mode);
}
/**
* @brief Implements FORTH word @c ACL-MODE! — store ACL enforcement mode
*
* Stack effect: ( mode xt -- )
* Sets the enforcement mode of the word identified by @c xt.
* No-op if the word is pinned — pinned entries are immutable.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_mode_store(VM *vm)
{
if (!vm || vm->dsp < 1) {
log_message(LOG_ERROR, "ACL-MODE!: stack underflow");
if (vm) vm->error = 1;
return;
}
DictEntry *e = pop_xt(vm);
if (!e) return;
cell_t mode = vm_pop(vm);
if (e->acl_pinned) return; /* one-way ratchet: pinned is immutable */
e->acl_mode = (uint8_t)(mode & 0xFF);
}
/**
* @brief Implements FORTH word @c ACL-PINNED? — query pin status
*
* Stack effect: ( xt -- flag )
* Pushes -1 (true) if the word is pinned, 0 (false) otherwise.
* A pinned word's ACL fields cannot be modified by any FORTH operation.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_pinned_query(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
vm_push(vm, e->acl_pinned ? -1 : 0);
}
/**
* @brief Implements FORTH word @c ACL-PIN — pin a word's ACL entry
*
* Stack effect: ( xt -- )
* Sets @c acl_pinned = 1 on the word identified by @c xt.
* This is a one-way ratchet: once set it can never be cleared, even by
* ACL-INHERIT. Use with @c ' WORD ACL-PIN in FORTH, never from C policy code.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_pin(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
e->acl_pinned = 1;
}
/**
* @brief Implements FORTH word @c ACL-TTL@ — fetch TTL countdown value
*
* Stack effect: ( xt -- n )
* Pushes the current TTL (time-to-live) countdown of the word's ACL entry.
* When TTL reaches zero in TTL mode the interpreter calls @c acl_recheck().
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_ttl_fetch(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
vm_push(vm, (cell_t)e->acl_ttl);
}
/**
* @brief Implements FORTH word @c ACL-TTL! — store TTL countdown value
*
* Stack effect: ( n xt -- )
* Sets the TTL countdown on the word's ACL entry. Negative values are
* clamped to 0; values above UINT32_MAX are clamped to UINT32_MAX.
* No-op if the word is pinned.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_ttl_store(VM *vm)
{
if (!vm || vm->dsp < 1) {
log_message(LOG_ERROR, "ACL-TTL!: stack underflow");
if (vm) vm->error = 1;
return;
}
DictEntry *e = pop_xt(vm);
if (!e) return;
cell_t n = vm_pop(vm);
if (e->acl_pinned) return;
e->acl_ttl = (uint32_t)(n < 0 ? 0 : (uint64_t)n > UINT32_MAX ? UINT32_MAX : (uint32_t)n);
}
/**
* @brief Implements FORTH word @c ACL-ALLOW@ — fetch cached allow/deny flag
*
* Stack effect: ( xt -- flag )
* Pushes the cached ACL decision for the word: -1 = allowed, 0 = denied.
* This cached value is checked on every execution in TTL mode; the cache is
* refreshed by @c acl_recheck() when TTL expires.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_allow_fetch(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
vm_push(vm, e->acl_allow ? -1 : 0);
}
/**
* @brief Implements FORTH word @c ACL-ALLOW! — store cached allow/deny flag
*
* Stack effect: ( flag xt -- )
* Sets the cached ACL decision: any non-zero @c flag = allowed, 0 = denied.
* No-op if the word is pinned.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_allow_store(VM *vm)
{
if (!vm || vm->dsp < 1) {
log_message(LOG_ERROR, "ACL-ALLOW!: stack underflow");
if (vm) vm->error = 1;
return;
}
DictEntry *e = pop_xt(vm);
if (!e) return;
cell_t flag = vm_pop(vm);
if (e->acl_pinned) return;
e->acl_allow = (flag != 0) ? 1 : 0;
}
/**
* @brief Implements FORTH word @c ACL-HEAT@ — fetch execution heat
*
* Stack effect: ( xt -- heat )
* Pushes the @c execution_heat counter for the word identified by @c xt.
* Used by @c ACL-TTL-COMPUTE in @c ACL.4th to calibrate the TTL value based
* on how frequently the word is executed.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_heat_fetch(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
vm_push(vm, (cell_t)e->execution_heat);
}
/**
* @brief Implements FORTH word @c ACL-WORD-ID — fetch stable word identifier
*
* Stack effect: ( xt -- n )
* Pushes the stable @c word_id for the entry identified by @c xt. Unlike
* dictionary positions, @c word_id is assigned at registration and never
* changes, making it safe to use as a persistent table index in @c ACL.4th.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_word_id(VM *vm)
{
DictEntry *e = pop_xt(vm);
if (!e) return;
vm_push(vm, (cell_t)e->word_id);
}
/**
* @brief Implements FORTH word @c ACL-INHERIT — inherit ACL policy from parent
*
* Stack effect: ( src dst -- )
* Copies @c acl_mode from @c src to @c dst, then clears @c dst->acl_pinned,
* resets @c acl_ttl to 0, and sets @c acl_allow to 1 (optimistic default).
* This is a C primitive because FORTH code cannot clear @c acl_pinned — pin
* is a one-way ratchet that only C can reset during inheritance.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_inherit(VM *vm)
{
if (!vm || vm->dsp < 1) {
log_message(LOG_ERROR, "ACL-INHERIT: stack underflow");
if (vm) vm->error = 1;
return;
}
DictEntry *dst = pop_xt(vm);
if (!dst) return;
DictEntry *src = pop_xt(vm);
if (!src) return;
acl_inherit_entry(src, dst);
}
/**
* @brief Implements FORTH word @c ACL-INIT-PRIMITIVES — reset ACL fields on all
* unpinned dictionary entries
*
* Stack effect: ( -- )
* Walks the entire dictionary and resets ACL fields on every entry that is not
* pinned: sets @c acl_ttl = 0 (force recheck on first execution), @c acl_allow
* = 1 (optimistic default), @c acl_mode = ACL_MODE_TTL. Called by @c ACL-BOOT
* in @c ACL.4th immediately after the ACL capsule finishes loading.
*
* @param vm Pointer to the VM structure
*/
static void forth_acl_init_primitives(VM *vm)
{
if (!vm) return;
for (DictEntry *e = vm->latest; e; e = e->link) {
if (!e->acl_pinned) {
/* Re-assert permissive defaults; ACL-RECHECK will calibrate on first exec */
e->acl_ttl = 0; /* force recheck on first execution */
e->acl_allow = 1;
e->acl_mode = ACL_MODE_TTL;
}
}
}
/**
* @brief Direct C entry point for ACL policy inheritance on the capsule birth path.
*
* Copies @c acl_mode from @c src to @c dst, clears @c acl_pinned, resets
* @c acl_ttl to 0, and sets @c acl_allow to 1. Called from capsule birth code
* (not from FORTH) — no stack manipulation is performed.
*
* @param src Source DictEntry whose @c acl_mode is inherited
* @param dst Destination DictEntry whose ACL fields are updated
*/
void acl_inherit_entry(DictEntry *src, DictEntry *dst)
{
if (!src || !dst) return;
dst->acl_mode = src->acl_mode;
dst->acl_pinned = 0; /* pin is contextual, not viral */
dst->acl_ttl = 0; /* child has no history; force recheck */
dst->acl_allow = 1; /* optimistic default */
}
/**
* @brief Register all ACL C-primitive words with the VM dictionary.
*
* Registers: field accessors (@c ACL-MODE@ / @c ACL-MODE! / @c ACL-PINNED? /
* @c ACL-TTL@ / @c ACL-TTL! / @c ACL-ALLOW@ / @c ACL-ALLOW! / @c ACL-HEAT@ /
* @c ACL-WORD-ID), and mutation words (@c ACL-PIN / @c ACL-INHERIT /
* @c ACL-INIT-PRIMITIVES). Called from @c vm_bootstrap.c during VM
* initialisation.
*
* @param vm Pointer to the VM structure
*/
void register_acl_words(VM *vm)
{
/* Field accessors — read */
register_word(vm, "ACL-MODE@", forth_acl_mode_fetch);
register_word(vm, "ACL-PINNED?", forth_acl_pinned_query);
register_word(vm, "ACL-TTL@", forth_acl_ttl_fetch);
register_word(vm, "ACL-ALLOW@", forth_acl_allow_fetch);
register_word(vm, "ACL-HEAT@", forth_acl_heat_fetch);
register_word(vm, "ACL-WORD-ID", forth_acl_word_id);
/* Field accessors — write (all no-op if pinned) */
register_word(vm, "ACL-MODE!", forth_acl_mode_store);
register_word(vm, "ACL-TTL!", forth_acl_ttl_store);
register_word(vm, "ACL-ALLOW!", forth_acl_allow_store);
/* Pin ratchet — one-way set */
register_word(vm, "ACL-PIN", forth_acl_pin);
/* Higher-level operations */
register_word(vm, "ACL-INHERIT", forth_acl_inherit);
register_word(vm, "ACL-INIT-PRIMITIVES", forth_acl_init_primitives);
}
+438
View File
@@ -0,0 +1,438 @@
/*
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.
*/
/* arithmetic_words.c - FORTH-79 Arithmetic Words */
#include "include/arithmetic_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
/* FORTH-79 Arithmetic Words:
* + ( n1 n2 -- n3 ) Add n1 and n2
* - ( n1 n2 -- n3 ) Subtract n2 from n1
* * ( n1 n2 -- n3 ) Multiply n1 and n2
* / ( n1 n2 -- n3 ) Divide n1 by n2
* MOD ( n1 n2 -- n3 ) n1 modulo n2
* /MOD ( n1 n2 -- n3 n4 ) n1/n2 remainder and quotient
* STAR-SLASH ( n1 n2 n3 -- n4 ) n1*n2/n3 with intermediate double
* STAR-SLASH-MOD ( n1 n2 n3 -- n4 n5 ) n1*n2/n3 remainder and quotient
* 1+ ( n -- n+1 ) Add 1
* 1- ( n -- n-1 ) Subtract 1
* 2+ ( n -- n+2 ) Add 2
* 2- ( n -- n-2 ) Subtract 2
* 2* ( n -- n*2 ) Multiply by 2 (left shift)
* 2/ ( n -- n/2 ) Divide by 2 (right shift)
* ABS ( n -- |n| ) Absolute value
* NEGATE ( n -- -n ) Two's complement
* MIN ( n1 n2 -- n3 ) Minimum of n1 and n2
* MAX ( n1 n2 -- n3 ) Maximum of n1 and n2
*/
/* Forward declarations */
static void arithmetic_word_star_slash(VM * vm);
static void arithmetic_word_star_slash_mod(VM * vm);
/**
* @brief Implements FORTH word "+" that adds two numbers
*
* Stack effect: ( n1 n2 -- n3 )
* Adds n1 and n2, pushing their sum n3 onto the stack.
*
* @param vm Pointer to the VM structure
*/
void arithmetic_word_plus(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "+: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = n1 + n2;
vm_push(vm, result);
log_message(LOG_DEBUG, "+: %ld + %ld = %ld", n1, n2, result);
}
/**
* @brief Implements FORTH word "-" that subtracts two numbers
*
* Stack effect: ( n1 n2 -- n3 )
* Subtracts n2 from n1, pushing their difference n3 onto the stack.
*
* @param vm Pointer to the VM structure
*/
static void arithmetic_word_minus(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "-: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = n1 - n2;
vm_push(vm, result);
log_message(LOG_DEBUG, "-: %ld - %ld = %ld", n1, n2, result);
}
/**
* @brief Implements FORTH word "*" that multiplies two numbers
*
* Stack effect: ( n1 n2 -- n3 )
* Multiplies n1 by n2, pushing their product n3 onto the stack.
*
* @param vm Pointer to the VM structure
*/
static void arithmetic_word_multiply(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "*: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = n1 * n2;
vm_push(vm, result);
log_message(LOG_DEBUG, "*: %ld * %ld = %ld", n1, n2, result);
}
/* / ( n1 n2 -- n3 ) Divide n1 by n2 */
static void word_divide(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "/: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
if (n2 == 0) {
log_message(LOG_ERROR, "/: Division by zero");
vm->error = 1;
return;
}
cell_t result = n1 / n2;
vm_push(vm, result);
log_message(LOG_DEBUG, "/: %ld / %ld = %ld", n1, n2, result);
}
/* MOD ( n1 n2 -- n3 ) n1 modulo n2 */
static void mixed_math_word_mod(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "MOD: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
if (n2 == 0) {
log_message(LOG_ERROR, "MOD: Division by zero");
vm->error = 1;
return;
}
cell_t result = n1 % n2;
vm_push(vm, result);
log_message(LOG_DEBUG, "MOD: %ld MOD %ld = %ld", n1, n2, result);
}
/* /MOD ( n1 n2 -- n3 n4 ) n1/n2 remainder and quotient */
static void word_divmod(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "/MOD: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
if (n2 == 0) {
log_message(LOG_ERROR, "/MOD: Division by zero");
vm->error = 1;
return;
}
cell_t remainder = n1 % n2;
cell_t quotient = n1 / n2;
vm_push(vm, remainder); /* remainder first */
vm_push(vm, quotient); /* quotient on top */
log_message(LOG_DEBUG, "/MOD: %ld /MOD %ld = %ld remainder %ld", n1, n2, quotient, remainder);
}
/* STAR-SLASH ( n1 n2 n3 -- n4 ) n1*n2/n3 with intermediate double */
static void arithmetic_word_star_slash(VM *vm) {
if (vm->dsp < 2) {
log_message(LOG_ERROR, "*/: Stack underflow");
vm->error = 1;
return;
}
cell_t n3 = vm_pop(vm); /* divisor */
cell_t n2 = vm_pop(vm); /* multiplier */
cell_t n1 = vm_pop(vm); /* multiplicand */
if (n3 == 0) {
log_message(LOG_ERROR, "*/: Division by zero");
vm->error = 1;
return;
}
/* Use 64-bit intermediate to avoid overflow */
int64_t intermediate = n1 * n2;
cell_t result = (intermediate / n3);
vm_push(vm, result);
log_message(LOG_DEBUG, "*/: %ld * %ld / %ld = %ld", n1, n2, n3, result);
}
/* STAR-SLASH-MOD ( n1 n2 n3 -- n4 n5 ) n1*n2/n3 remainder and quotient */
static void arithmetic_word_star_slash_mod(VM *vm) {
if (vm->dsp < 2) {
log_message(LOG_ERROR, "*/MOD: Stack underflow");
vm->error = 1;
return;
}
cell_t n3 = vm_pop(vm); /* divisor */
cell_t n2 = vm_pop(vm); /* multiplier */
cell_t n1 = vm_pop(vm); /* multiplicand */
if (n3 == 0) {
log_message(LOG_ERROR, "*/MOD: Division by zero");
vm->error = 1;
return;
}
/* Use 64-bit intermediate to avoid overflow */
int64_t intermediate = n1 * n2;
cell_t remainder = (intermediate % n3);
cell_t quotient = (intermediate / n3);
vm_push(vm, remainder); /* remainder first */
vm_push(vm, quotient); /* quotient on top */
}
/* 1+ ( n -- n+1 ) Add 1 */
static void arithmetic_word_one_plus(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "1+: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
vm_push(vm, n + 1);
log_message(LOG_DEBUG, "1+: %ld + 1 = %ld", n, (n + 1));
}
/* 1- ( n -- n-1 ) Subtract 1 */
static void arithmetic_word_one_minus(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "1-: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
vm_push(vm, n - 1);
log_message(LOG_DEBUG, "1-: %ld - 1 = %ld", n, (n - 1));
}
/* 2+ ( n -- n+2 ) Add 2 */
static void arithmetic_word_two_plus(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "2+: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
vm_push(vm, n + 2);
log_message(LOG_DEBUG, "2+: %ld + 2 = %ld", n, (n + 2));
}
/* 2- ( n -- n-2 ) Subtract 2 */
static void arithmetic_word_two_minus(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "2-: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
vm_push(vm, n - 2);
log_message(LOG_DEBUG, "2-: %ld - 2 = %ld", n, (n - 2));
}
/* 2* ( n -- n*2 ) Multiply by 2 (left shift) */
static void arithmetic_word_two_multiply(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "2*: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
vm_push(vm, n << 1);
log_message(LOG_DEBUG, "2*: %ld * 2 = %ld", n, (n << 1));
}
/* 2/ ( n -- n/2 ) Divide by 2 (right shift) */
static void arithmetic_word_two_divide(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "2/: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
vm_push(vm, n >> 1);
log_message(LOG_DEBUG, "2/: %ld / 2 = %ld", n, (n >> 1));
}
/* ABS ( n -- |n| ) Absolute value */
static void arithmetic_word_abs(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "ABS: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
cell_t result = (n < 0) ? -n : n;
vm_push(vm, result);
log_message(LOG_DEBUG, "ABS: |%ld| = %ld", n, result);
}
/* NEGATE ( n -- -n ) Two's complement */
static void arithmetic_word_negate(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "NEGATE: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
vm_push(vm, -n);
log_message(LOG_DEBUG, "NEGATE: -%ld = %ld", n, (-n));
}
/* MIN ( n1 n2 -- n3 ) Minimum of n1 and n2 */
static void arithmetic_word_min(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "MIN: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = (n1 < n2) ? n1 : n2;
vm_push(vm, result);
log_message(LOG_DEBUG, "MIN: min(%ld, %ld) = %ld", n1, n2, result);
}
/* MAX ( n1 n2 -- n3 ) Maximum of n1 and n2 */
static void arithmetic_word_max(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "MAX: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = (n1 > n2) ? n1 : n2;
vm_push(vm, result);
log_message(LOG_DEBUG, "MAX: max(%ld, %ld) = %ld", n1, n2, result);
}
/*
* @brief Registers all FORTH-79 arithmetic words with the VM
*
* This function registers all the standard FORTH-79 arithmetic operations
* including basic arithmetic, advanced math operations, increment/decrement
* operations, and comparison functions.
*
* @param vm Pointer to the VM structure where words will be registered
*/
void register_arithmetic_words(VM *vm) {
log_message(LOG_INFO, "Registering FORTH-79 arithmetic words...");
/* Basic arithmetic */
register_word(vm, "+", arithmetic_word_plus);
register_word(vm, "-", arithmetic_word_minus);
register_word(vm, "*", arithmetic_word_multiply);
register_word(vm, "/", word_divide);
register_word(vm, "MOD", mixed_math_word_mod);
register_word(vm, "/MOD", word_divmod);
/* Advanced arithmetic */
register_word(vm, "*/", arithmetic_word_star_slash);
register_word(vm, "*/MOD", arithmetic_word_star_slash_mod);
/* Increment/decrement */
register_word(vm, "1+", arithmetic_word_one_plus);
register_word(vm, "1-", arithmetic_word_one_minus);
register_word(vm, "2+", arithmetic_word_two_plus);
register_word(vm, "2-", arithmetic_word_two_minus);
register_word(vm, "2*", arithmetic_word_two_multiply);
register_word(vm, "2/", arithmetic_word_two_divide);
/* Sign and comparison */
register_word(vm, "ABS", arithmetic_word_abs);
register_word(vm, "NEGATE", arithmetic_word_negate);
register_word(vm, "MIN", arithmetic_word_min);
register_word(vm, "MAX", arithmetic_word_max);
}
+703
View File
@@ -0,0 +1,703 @@
/*
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_words.c - FORTH-79 Block Words (Layer 3: Forth Interface)
Last modified - 10/02/25, 03:55 PM ET
Author: Robert A. James (rajames) - StarshipOS Forth Project.
License: Creative Commons Zero v1.0 Universal
<http://creativecommons.org/publicdomain/zero/1.0/>
*/
#include "include/block_words.h"
#include "../../include/word_registry.h"
#include "../../include/vm.h"
#include "../../include/block_subsystem.h"
#include <string.h>
#include <stdio.h>
#ifdef __STARKERNEL__
#include "starkernel/vm/stadium_blocks.h" /* stadium_block_dispatch() -- FABRIC-2.md §B */
#endif
/* ----------------------------------------------------------------------
* Architecture:
* - Layer 1: blkio (vtable abstraction)
* - Layer 2: block_subsystem (RAM 0-1023, disk 1024+, 4KB packing)
* - Layer 3: block_words (THIS FILE - Forth interface)
*
* All block I/O now goes through block_subsystem.h API.
* Block 0 is RESERVED for volume metadata.
* ---------------------------------------------------------------------- */
static void set_scr(VM *vm, cell_t blk) {
if (!vm) return;
vm_store_cell(vm, vm->scr_addr, (cell_t) blk);
}
/* Optional utility surface (matches your header) */
/*
* @brief Initializes the block system
* @param vm Pointer to the VM instance
*/
void init_block_system(VM *vm) {
(void) vm;
/* Subsystem initialization happens in main.c via blk_subsys_init() */
}
/**
* @brief Gets a buffer for the specified block
* @param vm Pointer to the VM instance
* @param block_num Block number to retrieve
* @return Pointer to block buffer or NULL if invalid
*/
unsigned char *get_block_buffer(VM *vm, int block_num) {
if (!vm) return NULL;
if (block_num == 0) return NULL; /* Block 0 reserved */
uint8_t *buf = blk_get_buffer((uint32_t) block_num, 0); /* read-only */
if (buf) {
set_scr(vm, (cell_t) block_num);
}
return buf;
}
/**
* @brief Gets an empty buffer for the specified block
* @param vm Pointer to the VM instance
* @param block_num Block number to create
* @return Pointer to empty block buffer or NULL if invalid
*/
unsigned char *get_empty_buffer(VM *vm, int block_num) {
if (!vm) return NULL;
if (block_num == 0) return NULL; /* Block 0 reserved */
uint8_t *buf = blk_get_empty_buffer((uint32_t) block_num);
if (buf) {
set_scr(vm, (cell_t) block_num);
}
return buf;
}
/*
* @brief Marks the current block buffer as dirty
* @param vm Pointer to the VM instance
*/
void mark_buffer_dirty(VM *vm) {
if (!vm) return;
cell_t blk = vm_load_cell(vm, vm->scr_addr);
if (blk > 0) {
blk_update((uint32_t) blk);
}
}
/*
* @brief Saves all dirty buffers
* @param vm Pointer to the VM instance
*/
void save_all_buffers(VM *vm) {
(void) vm;
blk_flush(0); /* flush all */
}
/**
* @brief Empties all user block buffers
* @param vm Pointer to the VM instance
*/
void empty_all_buffers(VM *vm) {
if (!vm) return;
/* Zero blocks USER_BLOCKS_START through max available */
uint32_t total = blk_get_total_blocks();
for (uint32_t blk = USER_BLOCKS_START; blk < total; ++blk) {
uint8_t *buf = blk_get_buffer(blk, 1); /* writable */
if (buf) {
memset(buf, 0, BLOCK_SIZE);
}
}
}
/* --- Block I/O window helpers ----------------------------------------- */
/* Discard the whole VM block window if the device chain has changed since
* it was last validated (Milestone 2h). A device hot-detach followed by a
* later re-attach can reuse both the exact same LBN range
* (block_subsystem.c's chain always appends at the current tail) *and* the
* exact same blk_get_buffer() return address (confirmed live: this
* kernel's own first-fit kmalloc, src/starkernel/memory/kmalloc.c, hands
* the just-freed slot straight back to the very next same-size
* calloc()), so neither LBN nor a cached pointer is a reliable
* "still the same device" signal on its own. Any epoch change discards
* every cached slot outright -- no flush attempt, matching
* blk_subsys_detach_device()'s own reasoning: whatever device the stale
* content belonged to may already be gone by the time this runs. Called at
* the top of every function below that reads vm->blk_vm_lbn[]/
* vm->blk_vm_cbuf[] directly, not just blk_vm_find() -- blk_vm_flush_all()
* walks the same arrays without going through blk_vm_find() first. */
void blk_vm_check_epoch(VM *vm) {
uint64_t epoch = blk_subsys_epoch();
if (epoch == vm->blk_vm_epoch) return;
for (int i = 0; i < BLK_VM_SLOTS; i++) {
vm->blk_vm_lbn[i] = 0;
vm->blk_vm_cbuf[i] = NULL;
vm->blk_vm_dirty[i] = 0;
}
vm->blk_vm_epoch = epoch;
}
/* blk_vm_slot_for_addr ( addr -- slot-index|-1 ): does `addr` fall inside
* the block-window range at all? Pure address arithmetic, no VM state --
* the window's base/size are fixed compile-time constants (vm.h), so this
* doesn't need a VM* the way blk_vm_find() does (that also needs the
* per-VM blk_vm_lbn[]/cbuf[] arrays to check what's actually loaded).
* Exposed (Phase 8 v3, 2026-09-23) for MOVE/CMOVE/CMOVE>'s cross-device
* block-copy check -- they need "is this a block-window address" without
* needing to know or care whether anything's actually loaded there. */
int blk_vm_slot_for_addr(vaddr_t addr) {
if (addr < BLK_VM_WINDOW_BASE || addr >= BLK_VM_WINDOW_BASE + BLK_VM_WINDOW_SIZE)
return -1;
return (int) ((addr - BLK_VM_WINDOW_BASE) / BLOCK_SIZE);
}
/* blk_vm_is_cross_device_copy ( addr1 addr2 -- refuse? ): the actual
* Phase 8 v3 (2026-09-23) check MOVE/CMOVE/CMOVE> call before copying.
* True only when BOTH addresses are block-window addresses AND the LBNs
* currently loaded in those two slots resolve to two different physical
* devices -- the exact shape of `<src> BLOCK <dst> BUFFER ... MOVE`, a
* same-VM, cross-device raw block copy using only stock words. A copy
* where either (or both) address is ordinary VM memory -- the overwhelming
* common case, e.g. staging PAD text into a block, or reading a block out
* for display -- is never flagged: only both-window, both-different-device
* copies are refused. Callers must still do their own vm_addr_ok() bounds
* checks first; this only adds the device-boundary refusal on top. */
int blk_vm_is_cross_device_copy(VM *vm, vaddr_t addr1, vaddr_t addr2) {
int slot1 = blk_vm_slot_for_addr(addr1);
int slot2 = blk_vm_slot_for_addr(addr2);
if (slot1 < 0 || slot2 < 0) return 0;
blk_vm_check_epoch(vm);
uint32_t lbn1 = vm->blk_vm_lbn[slot1];
uint32_t lbn2 = vm->blk_vm_lbn[slot2];
if (lbn1 == 0 || lbn2 == 0) return 0; /* an empty/unassigned slot has nothing to protect */
const void *dev1 = blk_lbn_device_handle(lbn1);
const void *dev2 = blk_lbn_device_handle(lbn2);
if (!dev1 || !dev2) return 0; /* can't resolve -- not this check's job to refuse */
return dev1 != dev2;
}
/* Find the slot holding lbn; return slot index or -1 if not loaded. */
static int blk_vm_find(VM *vm, uint32_t lbn) {
blk_vm_check_epoch(vm);
for (int i = 0; i < BLK_VM_SLOTS; i++) {
if (vm->blk_vm_lbn[i] == lbn && vm->blk_vm_cbuf[i] != NULL)
return i;
}
return -1;
}
/* Evict one slot (write back if dirty), return the freed slot index. */
static int blk_vm_evict(VM *vm) {
/* Prefer a clean slot to avoid unnecessary writeback. */
for (int i = 0; i < BLK_VM_SLOTS; i++) {
int s = (vm->blk_vm_next + i) % BLK_VM_SLOTS;
if (vm->blk_vm_cbuf[s] == NULL || !vm->blk_vm_dirty[s]) {
vm->blk_vm_lbn[s] = 0;
vm->blk_vm_cbuf[s] = NULL;
vm->blk_vm_dirty[s] = 0;
vm->blk_vm_next = (s + 1) % BLK_VM_SLOTS;
return s;
}
}
/* All slots dirty: evict round-robin, sync back to C buffer first.
* Re-resolve the buffer pointer by LBN rather than trusting the
* stored one -- the block-subsystem's own devblock cache may have
* shifted (struct-copy eviction) since this slot was populated,
* which silently invalidates any raw pointer captured earlier. */
int s = vm->blk_vm_next;
vm->blk_vm_next = (vm->blk_vm_next + 1) % BLK_VM_SLOTS;
vaddr_t base = BLK_VM_WINDOW_BASE + (vaddr_t)s * BLOCK_SIZE;
uint8_t* fresh = blk_get_buffer(vm->blk_vm_lbn[s], 1);
if (fresh)
{
memcpy(fresh, vm->memory + base, BLOCK_SIZE);
blk_update(vm->blk_vm_lbn[s]);
}
vm->blk_vm_lbn[s] = 0;
vm->blk_vm_cbuf[s] = NULL;
vm->blk_vm_dirty[s] = 0;
return s;
}
/* Load lbn into a window slot (reads C buffer), return VM offset or 0 on err. */
static vaddr_t blk_vm_load(VM *vm, uint32_t lbn, int writable) {
int s = blk_vm_find(vm, lbn);
if (s < 0) {
s = -1;
for (int i = 0; i < BLK_VM_SLOTS; i++) {
if (vm->blk_vm_cbuf[i] == NULL) { s = i; break; }
}
if (s < 0) s = blk_vm_evict(vm);
uint8_t *cbuf = blk_get_buffer(lbn, writable);
if (!cbuf) return 0;
vaddr_t base = BLK_VM_WINDOW_BASE + (vaddr_t)s * BLOCK_SIZE;
memcpy(vm->memory + base, cbuf, BLOCK_SIZE);
vm->blk_vm_lbn[s] = lbn;
vm->blk_vm_cbuf[s] = cbuf;
vm->blk_vm_dirty[s] = 0;
}
return BLK_VM_WINDOW_BASE + (vaddr_t)s * BLOCK_SIZE;
}
/* Assign lbn to a slot WITHOUT reading block content (BUFFER semantics).
* Returns VM offset or 0 on error. Slot is zeroed and marked dirty. */
static vaddr_t blk_vm_assign(VM *vm, uint32_t lbn) {
int s = blk_vm_find(vm, lbn);
if (s < 0) {
s = -1;
for (int i = 0; i < BLK_VM_SLOTS; i++) {
if (vm->blk_vm_cbuf[i] == NULL) { s = i; break; }
}
if (s < 0) s = blk_vm_evict(vm);
uint8_t *cbuf = blk_get_empty_buffer(lbn);
if (!cbuf) return 0;
vaddr_t base = BLK_VM_WINDOW_BASE + (vaddr_t)s * BLOCK_SIZE;
memset(vm->memory + base, 0, BLOCK_SIZE);
vm->blk_vm_lbn[s] = lbn;
vm->blk_vm_cbuf[s] = cbuf;
}
vm->blk_vm_dirty[s] = 1;
return BLK_VM_WINDOW_BASE + (vaddr_t)s * BLOCK_SIZE;
}
/* Sync all dirty slots to their C buffers and flush the subsystem.
* Re-resolve each buffer pointer by LBN rather than trusting the stored
* one -- see blk_vm_evict for why a stored pointer can go stale. Checks
* the epoch first (Milestone 2h, see blk_vm_check_epoch()) since this
* function walks vm->blk_vm_lbn[]/vm->blk_vm_cbuf[] directly rather than
* through blk_vm_find() -- a stale-epoch dirty slot must be discarded, not
* flushed onto whatever device now owns that LBN.
*
* Non-static: this is the entire implementation behind SAVE-BUFFERS
* (block_word_save_buffers() below is a one-line wrapper) -- exposed
* (declared in block_words.h) so kernel-side code can reuse the exact
* same flush path outside the word-dispatch mechanism, e.g. sk_repl_idle()
* (see FABRIC-0.md/FABRIC-1.md Section V item 6). Cheap to call when
* nothing is dirty: every check below is a small fixed-size scan
* (BLK_VM_SLOTS here, DISK_CACHE_SLOTS per device inside blk_flush()),
* no I/O happens unless something actually needs writing. */
void blk_vm_flush_all(VM *vm) {
blk_vm_check_epoch(vm);
for (int i = 0; i < BLK_VM_SLOTS; i++) {
if (vm->blk_vm_cbuf[i] != NULL && vm->blk_vm_dirty[i]) {
vaddr_t base = BLK_VM_WINDOW_BASE + (vaddr_t)i * BLOCK_SIZE;
uint8_t* fresh = blk_get_buffer(vm->blk_vm_lbn[i], 1);
if (fresh)
{
memcpy(fresh, vm->memory + base, BLOCK_SIZE);
blk_update(vm->blk_vm_lbn[i]);
}
vm->blk_vm_dirty[i] = 0;
}
}
blk_flush(0);
}
/* --- Words ------------------------------------------------------------ */
/* BLOCK ( u -- addr ) : VM address of block u content (no dirty mark) */
void block_word_block(VM *vm) {
if (vm->dsp < 0) { vm->error = 1; return; }
cell_t blk = vm_pop(vm);
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
#ifdef __STARKERNEL__
stadium_block_dispatch(vm->stadium_vm_id, (uint32_t) blk, vm->heartbeat.tick_count);
#endif
vaddr_t vaddr = blk_vm_load(vm, (uint32_t) blk, 0);
if (!vaddr) { vm->error = 1; return; }
set_scr(vm, blk);
vm_push(vm, CELL(vaddr));
}
/* BUFFER ( u -- addr ) : VM address of block u; content unread, mark dirty */
void block_word_buffer(VM *vm) {
if (vm->dsp < 0) { vm->error = 1; return; }
cell_t blk = vm_pop(vm);
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
#ifdef __STARKERNEL__
stadium_block_dispatch(vm->stadium_vm_id, (uint32_t) blk, vm->heartbeat.tick_count);
#endif
vaddr_t vaddr = blk_vm_assign(vm, (uint32_t) blk);
if (!vaddr) { vm->error = 1; return; }
set_scr(vm, blk);
vm_push(vm, CELL(vaddr));
}
/* UPDATE ( -- ) : sync current SCR slot to C layer and mark dirty.
* Re-resolve the buffer pointer by LBN rather than trusting the stored
* one -- see blk_vm_evict for why a stored pointer can go stale. */
void block_word_update(VM *vm) {
cell_t blk = vm_load_cell(vm, vm->scr_addr);
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
#ifdef __STARKERNEL__
stadium_block_dispatch(vm->stadium_vm_id, (uint32_t) blk, vm->heartbeat.tick_count);
#endif
int s = blk_vm_find(vm, (uint32_t) blk);
if (s < 0) { vm->error = 1; return; }
vaddr_t base = BLK_VM_WINDOW_BASE + (vaddr_t)s * BLOCK_SIZE;
uint8_t* fresh = blk_get_buffer((uint32_t)blk, 1);
if (!fresh)
{
vm->error = 1;
return;
}
memcpy(fresh, vm->memory + base, BLOCK_SIZE);
vm->blk_vm_cbuf[s] = fresh;
vm->blk_vm_dirty[s] = 1;
blk_update((uint32_t) blk);
}
/* BLK-CONFIRM-FORMAT ( lbn -- ) : commit the low-level disk container
* format for the slot owning lbn. Must be called by the disk's owner
* (e.g. Artemis) only after classifying disk content as safe to touch —
* never on the path that halts for unrecognized content. Until this is
* called, all writes to that disk slot are refused by the block layer. */
void block_word_confirm_format(VM *vm) {
if (vm->dsp < 0) { vm->error = 1; return; }
cell_t blk = vm_pop(vm);
if (blk_subsys_confirm_format((uint32_t) blk) != BLK_OK) { vm->error = 1; return; }
}
/* RELOCATE-BLOCK ( home target -- ) : relocate home's content to target,
* redirecting all future access to home transparently through target
* from now on. See blk_subsys_relocate_block()'s own doc comment
* (block_subsystem.h) for the full contract -- this word performs no
* policy validation of its own (is target actually free? does the
* caller actually own it?), matching this project's "ACL owns policy,
* this is a mechanical primitive" direction. */
void block_word_relocate(VM *vm) {
if (vm->dsp < 1) { vm->error = 1; return; }
cell_t target = vm_pop(vm);
cell_t home = vm_pop(vm);
if (blk_subsys_relocate_block((uint32_t) home, (uint32_t) target) != BLK_OK) {
vm->error = 1;
}
}
/* SAVE-BUFFERS ( -- ) : sync and write all dirty blocks */
void block_word_save_buffers(VM *vm) {
blk_vm_flush_all(vm);
}
/* EMPTY-BUFFERS ( -- ) : invalidate all window slots then zero user blocks */
void block_word_empty_buffers(VM *vm) {
for (int i = 0; i < BLK_VM_SLOTS; i++) {
vm->blk_vm_lbn[i] = 0;
vm->blk_vm_cbuf[i] = NULL;
vm->blk_vm_dirty[i] = 0;
}
empty_all_buffers(vm);
}
/* FLUSH ( -- ) : save and invalidate all buffers */
void block_word_flush(VM *vm) {
blk_vm_flush_all(vm);
}
/* LOAD ( u -- ) : set SCR and (future: interpret block) */
void block_word_load(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t blk = vm_pop(vm);
if (blk == 0 || !blk_is_valid((uint32_t) blk)) {
vm->error = 1;
return;
}
uint8_t *buf = blk_get_buffer((uint32_t) blk, 0);
if (!buf) {
vm->error = 1;
return;
}
set_scr(vm, blk);
/* Interpret the block content as Forth source */
/* Block is 1024 bytes, null-terminate it for interpretation */
char block_text[1025];
memcpy(block_text, buf, 1024);
block_text[1024] = '\0';
/* Interpret the block content */
vm_interpret(vm, block_text);
}
/* LIST ( u -- ) : set SCR and (optionally) print the block */
void block_word_list(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t blk = vm_pop(vm);
if (blk == 0 || !blk_is_valid((uint32_t) blk)) {
vm->error = 1;
return;
}
uint8_t *buf = blk_get_buffer((uint32_t) blk, 0);
if (!buf) {
vm->error = 1;
return;
}
set_scr(vm, blk);
/* Print formatted block output with line numbers */
printf("\nBlock %ld\n", (long) blk);
/* FORTH blocks are typically 16 lines of 64 characters */
for (int line = 0; line < 16; line++) {
printf("%02d: ", line);
for (int col = 0; col < 64; col++) {
int idx = line * 64 + col;
char ch = (char) buf[idx];
/* Print printable characters, show spaces as-is */
if (ch >= 32 && ch < 127) {
putchar(ch);
} else if (ch == 0) {
/* Null bytes shown as spaces for readability */
putchar(' ');
} else {
/* Non-printable shown as '.' */
putchar('.');
}
}
printf("\n");
}
printf("\n");
}
/* THRU ( u1 u2 -- ) : LOAD each block from u1..u2 inclusive */
void block_word_thru(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t u2 = vm_pop(vm);
cell_t u1 = vm_pop(vm);
if (u1 > u2) {
cell_t t = u1;
u1 = u2;
u2 = t;
}
if (u1 == 0 || !blk_is_valid((uint32_t) u1) || !blk_is_valid((uint32_t) u2)) {
vm->error = 1;
return;
}
for (cell_t blk = u1; blk <= u2; ++blk) {
vm_push(vm, blk);
block_word_load(vm);
if (vm->error) return;
if (vm->abort_requested) return;
}
}
/* SCR ( -- addr ) : push VM address of SCR variable */
void block_word_scr(VM *vm) {
vm_push(vm, CELL(vm->scr_addr));
}
/* --> ( -- ) : continue interpretation on next sequential block */
void block_word_next_block(VM *vm) {
cell_t current_scr = vm_load_cell(vm, vm->scr_addr);
cell_t next_blk = current_scr + 1;
if (next_blk == 0 || !blk_is_valid((uint32_t) next_blk)) {
vm->error = 1;
return;
}
/* Get the next block's content */
uint8_t *buf = blk_get_buffer((uint32_t) next_blk, 0);
if (!buf) {
vm->error = 1;
return;
}
/* Update SCR to next block */
set_scr(vm, next_blk);
/* Interpret the next block's content line-by-line (same as LOAD) */
char block_text[1025];
memcpy(block_text, buf, 1024);
block_text[1024] = '\0';
char *p = block_text;
while (!vm->error && !vm->abort_requested && *p != '\0') {
char *nl = (char *)memchr(p, '\n', (size_t)(block_text + 1024 - p));
if (nl) {
*nl = '\0';
if (p != nl)
vm_interpret(vm, p);
p = nl + 1;
} else {
if (*p != '\0')
vm_interpret(vm, p);
break;
}
}
}
/* --- BMAPFMT block-ACL words (FABRIC-2.md §F.4/§H.6/§H.12 step 15) ----
* Raw C-primitive accessors, matching the word-level ACL system's own
* split: policy is composed in FORTH on top of these (a new capsule,
* §H.12 step 16), not here. block_num validated the same way BLOCK/
* BUFFER above already do. */
/* BLK-ACL-ALLOW@ ( block# -- allow ) */
static void block_word_acl_allow_fetch(VM *vm) {
if (vm->dsp < 0) { vm->error = 1; return; }
cell_t blk = vm_pop(vm);
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
uint8_t allow;
if (blk_acl_allow_get((uint32_t) blk, &allow) != BLK_OK) { vm->error = 1; return; }
vm_push(vm, (cell_t) allow);
}
/* BLK-ACL-ALLOW! ( allow block# -- ) */
static void block_word_acl_allow_store(VM *vm) {
if (vm->dsp < 1) { vm->error = 1; return; }
cell_t blk = vm_pop(vm);
cell_t allow = vm_pop(vm);
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
if (blk_acl_allow_set((uint32_t) blk, (uint8_t)(allow ? 1 : 0)) != BLK_OK) {
vm->error = 1;
}
}
/* BLK-ACL-TTL@ ( block# -- ttl ) */
static void block_word_acl_ttl_fetch(VM *vm) {
if (vm->dsp < 0) { vm->error = 1; return; }
cell_t blk = vm_pop(vm);
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
uint32_t ttl;
if (blk_acl_ttl_get((uint32_t) blk, &ttl) != BLK_OK) { vm->error = 1; return; }
vm_push(vm, (cell_t) ttl);
}
/* BLK-ACL-TTL! ( ttl block# -- ) */
static void block_word_acl_ttl_store(VM *vm) {
if (vm->dsp < 1) { vm->error = 1; return; }
cell_t blk = vm_pop(vm);
cell_t ttl = vm_pop(vm);
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
if (blk_acl_ttl_set((uint32_t) blk, (uint32_t) ttl) != BLK_OK) { vm->error = 1; }
}
/* BLK-OWNER@ ( block# -- fp ) : 8-byte owner fingerprint packed into one
* cell (cell_t is 8 bytes, int64_t -- see vm.h) -- raw bit reinterpretation,
* not a numeric value. Read-only from FORTH: only step 15's own list, no
* BLK-OWNER! -- setting ownership happens at a more controlled point
* (MINT/birth), in C, not exposed as a general FORTH write. */
static void block_word_owner_fetch(VM *vm) {
if (vm->dsp < 0) { vm->error = 1; return; }
cell_t blk = vm_pop(vm);
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
uint8_t fp[8];
if (blk_owner_fp_get((uint32_t) blk, fp) != BLK_OK) { vm->error = 1; return; }
cell_t packed;
memcpy(&packed, fp, sizeof(packed));
vm_push(vm, packed);
}
/* BLK-ATTACH ( dev-addr -- ok? ) : registers an already-open blkio_dev_t
* (its raw C pointer, passed as a cell -- the same "embed a pointer as a
* number in generated FORTH text" pattern VM-EXEC/the console relay
* already use) into the block subsystem's unified LBN address space via
* blk_subsys_attach_device(). This is the storage/persistence half of a
* USB drive attach -- Artemis's own domain -- split out from repl.c's
* attach loop so it can run as a message target (capsules/artemis/
* init.4th's HERA-BLK-ATTACH-REQ) instead of a direct C call, per Captain
* Bob's 2026-09-07 messaging-migration decision: Hera keeps polling and
* sig-checking, but no longer performs this step herself. dev-addr is
* trusted as a valid blkio_dev_t* from a live attach in progress -- there
* is no way to validate an arbitrary pointer from FORTH space, matching
* every other raw-pointer word this driver already exposes (e.g.
* dictionary_manipulation_words.c's >LINK/LINK>/CFA). */
static void block_word_blk_attach(VM *vm) {
if (vm->dsp < 0) { vm->error = 1; return; }
cell_t dev_addr = vm_pop(vm);
struct blkio_dev *dev = (struct blkio_dev *)(uintptr_t)dev_addr;
int rc = blk_subsys_attach_device(dev);
vm_push(vm, (rc == BLK_OK) ? 1 : 0);
}
/* --- Registration ----------------------------------------------------- */
/*
* @brief Registers all block-related FORTH words
* @param vm Pointer to the VM instance
*/
void register_block_words(VM *vm) {
register_word(vm, "BLOCK", block_word_block);
register_word(vm, "BUFFER", block_word_buffer);
register_word(vm, "UPDATE", block_word_update);
register_word(vm, "BLK-CONFIRM-FORMAT", block_word_confirm_format);
register_word(vm, "RELOCATE-BLOCK", block_word_relocate);
register_word(vm, "SAVE-BUFFERS", block_word_save_buffers);
register_word(vm, "EMPTY-BUFFERS", block_word_empty_buffers);
register_word(vm, "FLUSH", block_word_flush);
register_word(vm, "LOAD", block_word_load);
register_word(vm, "LIST", block_word_list);
register_word(vm, "THRU", block_word_thru);
register_word(vm, "SCR", block_word_scr);
register_word(vm, "-->", block_word_next_block);
register_word(vm, "BLK-ACL-ALLOW@", block_word_acl_allow_fetch);
register_word(vm, "BLK-ACL-ALLOW!", block_word_acl_allow_store);
register_word(vm, "BLK-ACL-TTL@", block_word_acl_ttl_fetch);
register_word(vm, "BLK-ACL-TTL!", block_word_acl_ttl_store);
register_word(vm, "BLK-OWNER@", block_word_owner_fetch);
register_word(vm, "BLK-ATTACH", block_word_blk_attach);
}
+976
View File
@@ -0,0 +1,976 @@
/*
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.
*/
#include "include/control_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
#include "../../include/vm.h"
#include <stddef.h>
#include <stdint.h>
/** @name Internal Runtime Functions
* Forward declarations for internal runtime helpers and control flow primitives
* @{
*/
/** Execute unconditional branch */
static void control_forth_branch(VM * vm);
/** Execute conditional branch based on top of stack */
static void control_forth_0branch(VM * vm);
/** Runtime handler for ?DO loops */
static void control_forth_runtime_qdo(VM * vm);
/** Runtime handler for DO loops */
static void control_forth_runtime_do(VM * vm);
/** Runtime handler for LOOP */
static void control_forth_runtime_loop(VM * vm);
/** Runtime handler for +LOOP */
static void control_forth_runtime_plus_loop(VM * vm);
/** Runtime handler for LEAVE */
static void control_forth_runtime_leave(VM * vm);
/** Get current loop index */
static void control_forth_I(VM * vm);
/** Get outer loop index */
static void control_forth_J(VM * vm);
/** Exit from current definition */
static void control_forth_EXIT(VM * vm);
/** @} */
/**
* @defgroup cf Control Flow Stack
* Compile-time control flow stack for tracking branch targets and loop
* structures. The types (cf_tag_t, cf_item_t, ControlFlowState) and the
* CF_STACK_MAX constant live in include/vm.h and the storage itself lives
* per-VM at vm->cf, so two VMs compiling colon definitions concurrently
* never share nesting state (see include/vm.h's ControlFlowState comment).
* @{
*/
static inline void cf_epoch_sync(VM *vm) {
if (!vm) return;
if (vm->cf.cf_last_mode == -999) {
vm->cf.cf_last_mode = vm->mode;
return;
}
if ((int)vm->mode != vm->cf.cf_last_mode) {
vm->cf.cf_sp = -1;
vm->cf.cf_last_mode = vm->mode;
log_message(LOG_DEBUG, "CF: reset (mode transition)");
}
}
static inline int cf_push_item(VM *vm, cf_tag_t tag, size_t mark) {
if (vm->cf.cf_sp + 1 >= CF_STACK_MAX) {
log_message(LOG_ERROR, "CF: overflow");
return 0;
}
++vm->cf.cf_sp;
vm->cf.cf_stack[vm->cf.cf_sp].tag = tag;
vm->cf.cf_stack[vm->cf.cf_sp].addr = mark;
return 1;
}
static inline int cf_pop_item(VM *vm, cf_item_t *out) {
if (vm->cf.cf_sp < 0) {
log_message(LOG_ERROR, "CF: underflow");
return 0;
}
if (out) {
*out = vm->cf.cf_stack[vm->cf.cf_sp];
}
--vm->cf.cf_sp;
return 1;
}
static inline int cf_peek_item(VM *vm, cf_item_t *out) {
if (vm->cf.cf_sp < 0) return 0;
if (out) {
*out = vm->cf.cf_stack[vm->cf.cf_sp];
}
return 1;
}
/** @} */
/* ===================== Low-level compile helpers ===================== */
static inline size_t emit_cell(VM *vm, cell_t value) {
if (vm->mode != MODE_COMPILE) {
vm_push(vm, value);
return (size_t) -1;
}
vm_align(vm);
cell_t *addr = (cell_t *) vm_allot(vm, sizeof(cell_t));
if (!addr) {
vm->error = 1;
log_message(LOG_ERROR, "emit_cell: out of space");
return (size_t) -1;
}
*addr = value;
return (size_t) ((uint8_t *) addr - vm->memory);
}
/**
* @defgroup runtime Runtime Branch Helpers
* Runtime support for control flow operations
* @details All functions operate on the per-step return IP (top of RS).
* All offsets are in BYTES.
* @{
*/
/**
* Runtime unconditional branch
* @param vm Virtual machine state
*/
static void control_forth_branch(VM *vm) {
if (vm->rsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "BRANCH: RSP underflow");
return;
}
cell_t *ip = (cell_t *) (uintptr_t) vm->return_stack[vm->rsp];
cell_t rel = *ip;
ip = (cell_t *) ((uint8_t *) ip + (intptr_t) rel);
vm->return_stack[vm->rsp] = (cell_t)(uintptr_t)
ip;
log_message(LOG_DEBUG, "BRANCH: +%ld bytes", (long) rel);
}
/* (0BRANCH) ( f -- ) */
static void control_forth_0branch(VM *vm) {
if (vm->rsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "0BRANCH: RSP underflow");
return;
}
if (vm->dsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "0BRANCH: DSP underflow");
return;
}
cell_t *ip = (cell_t *) (uintptr_t) vm->return_stack[vm->rsp];
cell_t flag = vm_pop(vm);
cell_t rel = *ip;
if (flag == 0) {
ip = (cell_t *) ((uint8_t *) ip + (intptr_t) rel);
log_message(LOG_DEBUG, "0BRANCH: taken +%ld", (long) rel);
} else {
ip = ip + 1;
log_message(LOG_DEBUG, "0BRANCH: not taken");
}
vm->return_stack[vm->rsp] = (cell_t)(uintptr_t)
ip;
}
/* (?DO) ( limit index -- ) */
static void control_forth_runtime_qdo(VM *vm) {
if (vm->rsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "?DO: RSP underflow");
return;
}
if (vm->dsp < 1) {
vm->error = 1;
log_message(LOG_ERROR, "?DO: DSP underflow");
return;
}
cell_t index = vm_pop(vm);
cell_t limit = vm_pop(vm);
cell_t *ip = (cell_t *) (uintptr_t) vm->return_stack[vm->rsp];
cell_t rel = *ip;
if (index == limit) {
ip = (cell_t *) ((uint8_t *) ip + (intptr_t) rel);
vm->return_stack[vm->rsp] = (cell_t)(uintptr_t)
ip;
log_message(LOG_DEBUG, "?DO: empty -> +%ld", (long) rel);
return;
}
/* enter loop at body (skip rel) and insert (limit,index) under IP */
ip = ip + 1;
if (vm->rsp + 2 >= STACK_SIZE) {
vm->error = 1;
log_message(LOG_ERROR, "?DO: RSTACK overflow");
return;
}
vm->return_stack[vm->rsp + 2] = (cell_t)(uintptr_t)
ip;
vm->return_stack[vm->rsp + 1] = index;
vm->return_stack[vm->rsp + 0] = limit;
vm->rsp += 2;
log_message(LOG_DEBUG, "?DO: enter (index=%ld limit=%ld)", (long) index, (long) limit);
}
/* (DO) ( limit index -- ) */
static void control_forth_runtime_do(VM *vm) {
if (vm->rsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "DO: RSP underflow");
return;
}
if (vm->dsp < 1) {
vm->error = 1;
log_message(LOG_ERROR, "DO: DSP underflow");
return;
}
cell_t index = vm_pop(vm);
cell_t limit = vm_pop(vm);
if (vm->rsp + 2 >= STACK_SIZE) {
vm->error = 1;
log_message(LOG_ERROR, "DO: RSTACK overflow");
return;
}
cell_t ip_cell = vm->return_stack[vm->rsp];
vm->return_stack[vm->rsp + 2] = ip_cell; /* move IP up */
vm->return_stack[vm->rsp + 1] = index;
vm->return_stack[vm->rsp + 0] = limit;
vm->rsp += 2;
log_message(LOG_DEBUG, "DO: enter (index=%ld limit=%ld)", (long) index, (long) limit);
}
/* (LOOP) */
static void control_forth_runtime_loop(VM *vm) {
if (vm->rsp < 2) {
vm->error = 1;
log_message(LOG_ERROR, "LOOP: missing loop frame");
return;
}
cell_t *ip = (cell_t *) (uintptr_t) vm->return_stack[vm->rsp];
cell_t *idxp = &vm->return_stack[vm->rsp - 1];
cell_t *limp = &vm->return_stack[vm->rsp - 2];
(*idxp) += 1;
cell_t back = *ip; /* BYTES */
if (*idxp < *limp) {
ip = (cell_t *) ((uint8_t *) ip + (intptr_t) back);
vm->return_stack[vm->rsp] = (cell_t)(uintptr_t)
ip;
log_message(LOG_DEBUG, "LOOP: continue (index=%ld)", (long) *idxp);
} else {
vm->rsp -= 2; /* drop INDEX,LIMIT */
ip = ip + 1; /* skip back-offset */
vm->return_stack[vm->rsp] = (cell_t)(uintptr_t)
ip;
log_message(LOG_DEBUG, "LOOP: exit");
}
}
/* (+LOOP) ( n -- ) */
static void control_forth_runtime_plus_loop(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "+LOOP: DSP underflow");
return;
}
if (vm->rsp < 2) {
vm->error = 1;
log_message(LOG_ERROR, "+LOOP: missing loop frame");
return;
}
cell_t n = vm_pop(vm);
cell_t *ip = (cell_t *) (uintptr_t) vm->return_stack[vm->rsp];
cell_t *idxp = &vm->return_stack[vm->rsp - 1];
cell_t *limp = &vm->return_stack[vm->rsp - 2];
cell_t newv = (*idxp) + n;
*idxp = newv;
cell_t back = *ip; /* BYTES */
int cont = (n >= 0) ? (newv < *limp) : (newv >= *limp);
if (cont) {
ip = (cell_t *) ((uint8_t *) ip + (intptr_t) back);
vm->return_stack[vm->rsp] = (cell_t)(uintptr_t)
ip;
log_message(LOG_DEBUG, "+LOOP: continue (index=%ld)", (long) newv);
} else {
vm->rsp -= 2;
ip = ip + 1;
vm->return_stack[vm->rsp] = (cell_t)(uintptr_t)
ip;
log_message(LOG_DEBUG, "+LOOP: exit");
}
}
/* (LEAVE) ( -- ) — force loop to exit at next LOOP/+LOOP */
static void control_forth_runtime_leave(VM *vm) {
if (vm->rsp < 2) {
vm->error = 1;
log_message(LOG_ERROR, "LEAVE: outside loop");
return;
}
vm->return_stack[vm->rsp - 1] = vm->return_stack[vm->rsp - 2]; /* index = limit */
log_message(LOG_DEBUG, "LEAVE: flagged exit");
}
/* UNLOOP ( -- ) — discard loop parameters from return stack */
static void control_forth_UNLOOP(VM *vm) {
if (vm->rsp < 2) {
vm->error = 1;
log_message(LOG_ERROR, "UNLOOP: outside loop (return stack underflow)");
return;
}
/* Return stack layout: ..., limit (rsp-2), index (rsp-1), ip (rsp) */
/* Move IP from rsp down to rsp-2, then decrement rsp by 2 */
vm->return_stack[vm->rsp - 2] = vm->return_stack[vm->rsp];
vm->rsp -= 2;
log_message(LOG_DEBUG, "UNLOOP: removed loop parameters (limit, index)");
}
/* I ( -- i ) */
static void control_forth_I(VM *vm) {
if (vm->rsp < 2) {
vm->error = 1;
log_message(LOG_ERROR, "I: outside DO loop");
return;
}
vm_push(vm, vm->return_stack[vm->rsp - 1]); /* INDEX */
}
/* J ( -- j ) — next-outer loop index */
static void control_forth_J(VM *vm) {
if (vm->rsp < 4) {
vm->error = 1;
log_message(LOG_ERROR, "J: needs nested DO loops");
return;
}
vm_push(vm, vm->return_stack[vm->rsp - 3]); /* outer INDEX */
}
/* EXIT — one-shot return from current colon definition (guarded) */
static void control_forth_EXIT(VM *vm) {
if (vm->rsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "EXIT: interpret-time use is invalid");
return;
}
vm->exit_colon = 1;
log_message(LOG_DEBUG, "EXIT: return from colon");
}
/* ===================== Compile-time words ===================== */
static void control_forth_if(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "IF: compile-only");
return;
}
vm_compile_call(vm, control_forth_0branch);
size_t lit = emit_cell(vm, 0);
if (!cf_push_item(vm, CF_IF, lit)) {
vm->error = 1;
return;
}
log_message(LOG_DEBUG, "IF: placeholder @ %zu", lit);
}
static void control_forth_else(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "ELSE: compile-only");
return;
}
cf_item_t it;
if (!cf_peek_item(vm, &it) || it.tag != CF_IF) {
vm->error = 1;
log_message(LOG_ERROR, "ELSE: missing IF");
return;
}
(void) cf_pop_item(vm, &it);
vm_compile_call(vm, control_forth_branch);
size_t new_lit = emit_cell(vm, 0);
cell_t off = (cell_t)((size_t) vm->here - it.addr);
*(cell_t *) (vm->memory + it.addr) = off;
if (!cf_push_item(vm, CF_ELSE, new_lit)) {
vm->error = 1;
return;
}
log_message(LOG_DEBUG, "ELSE: patched IF @ %zu -> +%ld; new lit @ %zu", it.addr, (long) off, new_lit);
}
static void control_forth_then(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "THEN: compile-only");
return;
}
cf_item_t it;
if (!cf_pop_item(vm, &it) || (it.tag != CF_IF && it.tag != CF_ELSE)) {
vm->error = 1;
log_message(LOG_ERROR, "THEN: unmatched");
return;
}
cell_t off = (cell_t)((size_t) vm->here - it.addr);
*(cell_t *) (vm->memory + it.addr) = off;
log_message(LOG_DEBUG, "THEN: patched lit @ %zu -> +%ld", it.addr, (long) off);
}
static void control_forth_begin(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "BEGIN: compile-only");
return;
}
if (!cf_push_item(vm, CF_BEGIN, vm->here)) {
vm->error = 1;
return;
}
log_message(LOG_DEBUG, "BEGIN: mark @ %zu", vm->here);
}
static void control_forth_until(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "UNTIL: compile-only");
return;
}
cf_item_t begin;
if (!cf_pop_item(vm, &begin) || begin.tag != CF_BEGIN) {
vm->error = 1;
log_message(LOG_ERROR, "UNTIL: missing BEGIN");
return;
}
vm_compile_call(vm, control_forth_0branch);
cell_t back = (cell_t)((cell_t) begin.addr - (cell_t) vm->here);
(void) emit_cell(vm, back);
log_message(LOG_DEBUG, "UNTIL: back -> %zu (%ld bytes)", begin.addr, (long) back);
}
static void control_forth_again(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "AGAIN: compile-only");
return;
}
cf_item_t begin;
if (!cf_pop_item(vm, &begin) || begin.tag != CF_BEGIN) {
vm->error = 1;
log_message(LOG_ERROR, "AGAIN: missing BEGIN");
return;
}
vm_compile_call(vm, control_forth_branch);
cell_t back = (cell_t)((cell_t) begin.addr - (cell_t) vm->here);
(void) emit_cell(vm, back);
log_message(LOG_DEBUG, "AGAIN: back -> %zu (%ld bytes)", begin.addr, (long) back);
}
static void control_forth_while(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "WHILE: compile-only");
return;
}
cf_item_t b;
if (!cf_peek_item(vm, &b) || b.tag != CF_BEGIN) {
vm->error = 1;
log_message(LOG_ERROR, "WHILE: needs BEGIN");
return;
}
vm_compile_call(vm, control_forth_0branch);
size_t lit = emit_cell(vm, 0);
if (!cf_push_item(vm, CF_WHILE, lit)) {
vm->error = 1;
return;
}
log_message(LOG_DEBUG, "WHILE: placeholder @ %zu", lit);
}
static void control_forth_repeat(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "REPEAT: compile-only");
return;
}
cf_item_t w, b;
if (!cf_pop_item(vm, &w) || w.tag != CF_WHILE) {
vm->error = 1;
log_message(LOG_ERROR, "REPEAT: missing WHILE");
return;
}
if (!cf_pop_item(vm, &b) || b.tag != CF_BEGIN) {
vm->error = 1;
log_message(LOG_ERROR, "REPEAT: missing BEGIN");
return;
}
vm_compile_call(vm, control_forth_branch);
cell_t back = (cell_t)((cell_t) b.addr - (cell_t) vm->here);
(void) emit_cell(vm, back);
cell_t fwd = (cell_t)((size_t) vm->here - w.addr);
*(cell_t *) (vm->memory + w.addr) = fwd;
log_message(LOG_DEBUG, "REPEAT: WHILE @ %zu -> +%ld; back=%ld to %zu", w.addr, (long) fwd, (long) back, b.addr);
}
/* ?DO ( limit index -- ) compile */
static void control_forth_qdo(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "?DO: compile-only");
return;
}
vm_compile_call(vm, control_forth_runtime_qdo);
size_t fwd_lit = emit_cell(vm, 0);
if (!cf_push_item(vm, CF_DO, vm->here)) {
vm->error = 1;
return;
} /* back target for LOOP */
if (!cf_push_item(vm, CF_WHILE, fwd_lit)) {
vm->error = 1;
return;
} /* forward to loop-end */
vm->cf.leave_mark_stack[++vm->cf.leave_mark_sp] = vm->cf.leave_sp;
log_message(LOG_DEBUG, "?DO: fwd lit @ %zu; back mark=%d", fwd_lit, vm->cf.leave_mark_stack[vm->cf.leave_mark_sp]);
}
/* DO ( limit index -- ) compile */
static void control_forth_do(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "DO: compile-only");
return;
}
vm_compile_call(vm, control_forth_runtime_do);
if (!cf_push_item(vm, CF_DO, vm->here)) {
vm->error = 1;
return;
}
vm->cf.leave_mark_stack[++vm->cf.leave_mark_sp] = vm->cf.leave_sp;
log_message(LOG_DEBUG, "DO: mark @ %zu; leave_mark=%d", vm->here, vm->cf.leave_mark_stack[vm->cf.leave_mark_sp]);
}
/* LEAVE — compile runtime LEAVE plus BRANCH <placeholder>, collect patch site */
static void control_forth_leave(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "LEAVE: compile-only");
return;
}
/* verify inside DO */
int seen_do = 0;
for (int i = vm->cf.cf_sp; i >= 0; --i) {
if (vm->cf.cf_stack[i].tag == CF_DO) {
seen_do = 1;
break;
}
}
if (!seen_do || vm->cf.leave_mark_sp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "LEAVE: needs DO");
return;
}
vm_compile_call(vm, control_forth_runtime_leave);
vm_compile_call(vm, control_forth_branch);
size_t lit = emit_cell(vm, 0);
if (vm->cf.leave_sp + 1 >= CF_STACK_MAX) {
vm->error = 1;
log_message(LOG_ERROR, "LEAVE: too many sites");
return;
}
vm->cf.leave_addrs[++vm->cf.leave_sp] = lit;
log_message(LOG_DEBUG, "LEAVE: site lit @ %zu (leave_sp=%d)", lit, vm->cf.leave_sp);
}
/* LOOP — compile runtime LOOP + backoffset; patch ?DO fwd and LEAVE sites */
static void control_forth_loop(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "LOOP: compile-only");
return;
}
/* Optional ?DO forward */
cf_item_t maybe_qdo;
int have_qdo = 0;
cf_item_t top;
if (cf_peek_item(vm, &top) && top.tag == CF_WHILE) {
(void) cf_pop_item(vm, &maybe_qdo);
have_qdo = 1;
}
/* Required DO back mark */
cf_item_t do_mark;
if (!cf_pop_item(vm, &do_mark) || do_mark.tag != CF_DO) {
vm->error = 1;
log_message(LOG_ERROR, "LOOP: missing DO");
return;
}
vm_compile_call(vm, control_forth_runtime_loop);
cell_t back = (cell_t)((cell_t) do_mark.addr - (cell_t) vm->here);
(void) emit_cell(vm, back);
if (have_qdo) {
cell_t fwd = (cell_t)((size_t) vm->here - maybe_qdo.addr);
*(cell_t *) (vm->memory + maybe_qdo.addr) = fwd;
log_message(LOG_DEBUG, "LOOP: patched ?DO @ %zu -> +%ld", maybe_qdo.addr, (long) fwd);
}
if (vm->cf.leave_mark_sp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "LOOP: LEAVE mark underflow");
return;
}
int mark = vm->cf.leave_mark_stack[vm->cf.leave_mark_sp--];
for (int i = vm->cf.leave_sp; i > mark; --i) {
size_t addr = vm->cf.leave_addrs[i];
cell_t fwd = (cell_t)((size_t) vm->here - addr);
*(cell_t *) (vm->memory + addr) = fwd;
log_message(LOG_DEBUG, "LEAVE: patched @ %zu -> +%ld", addr, (long) fwd);
}
vm->cf.leave_sp = mark;
log_message(LOG_DEBUG, "LOOP: back -> %zu (%ld bytes)", do_mark.addr, (long) back);
}
/* +LOOP — same as LOOP but with runtime +LOOP */
static void control_forth_plus_loop(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "+LOOP: compile-only");
return;
}
cf_item_t maybe_qdo;
int have_qdo = 0;
cf_item_t top;
if (cf_peek_item(vm, &top) && top.tag == CF_WHILE) {
(void) cf_pop_item(vm, &maybe_qdo);
have_qdo = 1;
}
cf_item_t do_mark;
if (!cf_pop_item(vm, &do_mark) || do_mark.tag != CF_DO) {
vm->error = 1;
log_message(LOG_ERROR, "+LOOP: missing DO");
return;
}
vm_compile_call(vm, control_forth_runtime_plus_loop);
cell_t back = (cell_t)((cell_t) do_mark.addr - (cell_t) vm->here);
(void) emit_cell(vm, back);
if (have_qdo) {
cell_t fwd = (cell_t)((size_t) vm->here - maybe_qdo.addr);
*(cell_t *) (vm->memory + maybe_qdo.addr) = fwd;
log_message(LOG_DEBUG, "+LOOP: patched ?DO @ %zu -> +%ld", maybe_qdo.addr, (long) fwd);
}
if (vm->cf.leave_mark_sp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "+LOOP: LEAVE mark underflow");
return;
}
int mark = vm->cf.leave_mark_stack[vm->cf.leave_mark_sp--];
for (int i = vm->cf.leave_sp; i > mark; --i) {
size_t addr = vm->cf.leave_addrs[i];
cell_t fwd = (cell_t)((size_t) vm->here - addr);
*(cell_t *) (vm->memory + addr) = fwd;
log_message(LOG_DEBUG, "LEAVE: patched @ %zu -> +%ld", addr, (long) fwd);
}
vm->cf.leave_sp = mark;
log_message(LOG_DEBUG, "+LOOP: back -> %zu (%ld bytes)", do_mark.addr, (long) back);
}
/* ===================== CASE/OF/ENDOF/ENDCASE ===================== */
/* Standard FORTH CASE statement:
* n CASE
* val1 OF code1 ENDOF
* val2 OF code2 ENDOF
* default-code
* ENDCASE
*
* Compile-time behavior:
* - CASE: Push CF_CASE marker, record endof_sp for ENDCASE patching
* - OF: Compile OVER = 0BRANCH <endof> DROP, push CF_OF with branch addr
* - ENDOF: Patch OF's forward branch, compile BRANCH <endcase>, save for ENDCASE
* - ENDCASE: Compile DROP, patch all ENDOF branches to here
*/
/* CASE ( n -- n ) compile-time: mark start of case statement */
static void control_forth_case(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "CASE: compile-only");
return;
}
if (!cf_push_item(vm, CF_CASE, 0)) {
vm->error = 1;
return;
}
/* Record current endof_sp so ENDCASE knows which branches to patch */
if (vm->cf.endof_mark_sp + 1 >= CF_STACK_MAX) {
vm->error = 1;
log_message(LOG_ERROR, "CASE: nesting overflow");
return;
}
vm->cf.endof_mark_stack[++vm->cf.endof_mark_sp] = vm->cf.endof_sp;
log_message(LOG_DEBUG, "CASE: mark (endof_mark=%d)", vm->cf.endof_mark_stack[vm->cf.endof_mark_sp]);
}
/* OF ( n1 n2 -- | n1 ) compile-time: compare and branch */
static void control_forth_of(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "OF: compile-only");
return;
}
/* Verify inside CASE */
int seen_case = 0;
for (int i = vm->cf.cf_sp; i >= 0; --i) {
if (vm->cf.cf_stack[i].tag == CF_CASE) {
seen_case = 1;
break;
}
}
if (!seen_case) {
vm->error = 1;
log_message(LOG_ERROR, "OF: needs CASE");
return;
}
/* Compile: OVER = 0BRANCH <endof> DROP */
/* We need to compile calls to OVER and = */
DictEntry *over_entry = vm_find_word(vm, "OVER", 4);
DictEntry *eq_entry = vm_find_word(vm, "=", 1);
DictEntry *drop_entry = vm_find_word(vm, "DROP", 4);
if (!over_entry || !eq_entry || !drop_entry) {
vm->error = 1;
log_message(LOG_ERROR, "OF: missing OVER, =, or DROP");
return;
}
vm_compile_call(vm, over_entry->func);
vm_compile_call(vm, eq_entry->func);
vm_compile_call(vm, control_forth_0branch);
size_t of_branch = emit_cell(vm, 0); /* Placeholder for ENDOF */
vm_compile_call(vm, drop_entry->func);
if (!cf_push_item(vm, CF_OF, of_branch)) {
vm->error = 1;
return;
}
log_message(LOG_DEBUG, "OF: branch placeholder @ %zu", of_branch);
}
/* ENDOF ( -- ) compile-time: end OF clause, jump to ENDCASE */
static void control_forth_endof(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "ENDOF: compile-only");
return;
}
/* Pop CF_OF and patch its forward branch */
cf_item_t of_item;
if (!cf_pop_item(vm, &of_item) || of_item.tag != CF_OF) {
vm->error = 1;
log_message(LOG_ERROR, "ENDOF: missing OF");
return;
}
/* Compile BRANCH to ENDCASE (placeholder) */
vm_compile_call(vm, control_forth_branch);
size_t endcase_branch = emit_cell(vm, 0);
/* Patch OF's 0BRANCH to jump here (after the BRANCH) */
cell_t off = (cell_t)((size_t) vm->here - of_item.addr);
*(cell_t *) (vm->memory + of_item.addr) = off;
/* Save ENDOF's branch for ENDCASE patching */
if (vm->cf.endof_sp + 1 >= CF_STACK_MAX) {
vm->error = 1;
log_message(LOG_ERROR, "ENDOF: too many clauses");
return;
}
vm->cf.endof_addrs[++vm->cf.endof_sp] = endcase_branch;
log_message(LOG_DEBUG, "ENDOF: patched OF @ %zu -> +%ld; endcase branch @ %zu",
of_item.addr, (long) off, endcase_branch);
}
/* ENDCASE ( n -- ) compile-time: end case, drop selector, patch all ENDOFs */
static void control_forth_endcase(VM *vm) {
cf_epoch_sync(vm);
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "ENDCASE: compile-only");
return;
}
/* Pop CF_CASE marker */
cf_item_t case_item;
if (!cf_pop_item(vm, &case_item) || case_item.tag != CF_CASE) {
vm->error = 1;
log_message(LOG_ERROR, "ENDCASE: missing CASE");
return;
}
/* Compile DROP to discard the selector */
DictEntry *drop_entry = vm_find_word(vm, "DROP", 4);
if (!drop_entry) {
vm->error = 1;
log_message(LOG_ERROR, "ENDCASE: missing DROP");
return;
}
vm_compile_call(vm, drop_entry->func);
/* Patch all ENDOF branches to here */
if (vm->cf.endof_mark_sp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "ENDCASE: mark underflow");
return;
}
int mark = vm->cf.endof_mark_stack[vm->cf.endof_mark_sp--];
for (int i = vm->cf.endof_sp; i > mark; --i) {
size_t addr = vm->cf.endof_addrs[i];
cell_t fwd = (cell_t)((size_t) vm->here - addr);
*(cell_t *) (vm->memory + addr) = fwd;
log_message(LOG_DEBUG, "ENDCASE: patched ENDOF @ %zu -> +%ld", addr, (long) fwd);
}
vm->cf.endof_sp = mark;
log_message(LOG_DEBUG, "ENDCASE: complete");
}
/* ===================== Registration ===================== */
void register_control_words(VM *vm) {
if (!vm) return;
/* Internal branches & runtimes */
register_word(vm, "(BRANCH)", control_forth_branch);
register_word(vm, "(0BRANCH)", control_forth_0branch);
register_word(vm, "(?DO)", control_forth_runtime_qdo);
register_word(vm, "(DO)", control_forth_runtime_do);
register_word(vm, "(LOOP)", control_forth_runtime_loop);
register_word(vm, "(+LOOP)", control_forth_runtime_plus_loop);
register_word(vm, "(LEAVE)", control_forth_runtime_leave);
/* IF/ELSE/THEN */
register_word(vm, "IF", control_forth_if);
vm_make_immediate(vm);
register_word(vm, "ELSE", control_forth_else);
vm_make_immediate(vm);
register_word(vm, "THEN", control_forth_then);
vm_make_immediate(vm);
/* BEGIN/WHILE/REPEAT/AGAIN/UNTIL */
register_word(vm, "BEGIN", control_forth_begin);
vm_make_immediate(vm);
register_word(vm, "WHILE", control_forth_while);
vm_make_immediate(vm);
register_word(vm, "REPEAT", control_forth_repeat);
vm_make_immediate(vm);
register_word(vm, "AGAIN", control_forth_again);
vm_make_immediate(vm);
register_word(vm, "UNTIL", control_forth_until);
vm_make_immediate(vm);
/* DO/?DO/LOOP/+LOOP/LEAVE */
register_word(vm, "?DO", control_forth_qdo);
vm_make_immediate(vm);
register_word(vm, "DO", control_forth_do);
vm_make_immediate(vm);
register_word(vm, "LOOP", control_forth_loop);
vm_make_immediate(vm);
register_word(vm, "+LOOP", control_forth_plus_loop);
vm_make_immediate(vm);
register_word(vm, "LEAVE", control_forth_leave);
vm_make_immediate(vm);
/* Loop indices, UNLOOP & EXIT */
register_word(vm, "I", control_forth_I);
register_word(vm, "J", control_forth_J);
register_word(vm, "UNLOOP", control_forth_UNLOOP);
register_word(vm, "EXIT", control_forth_EXIT);
/* CASE/OF/ENDOF/ENDCASE */
register_word(vm, "CASE", control_forth_case);
vm_make_immediate(vm);
register_word(vm, "OF", control_forth_of);
vm_make_immediate(vm);
register_word(vm, "ENDOF", control_forth_endof);
vm_make_immediate(vm);
register_word(vm, "ENDCASE", control_forth_endcase);
vm_make_immediate(vm);
}
+208
View File
@@ -0,0 +1,208 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/*
* DEFER / IS / DEFER@ primitives — late-binding deferred words.
*
* DEFER name ( -- ) Create a deferred word, data field = 0 (unset).
* IS name ( xt -- ) Store xt into the data field of a DEFER word.
* DEFER@ name ( -- xt ) Fetch the current action xt of a DEFER word.
*
* When a deferred word executes, defer_runtime reads its data field
* (a raw DictEntry*) and calls that entry's func directly. This is
* essential because the inner interpreter stores DictEntry* pointers
* in compiled bodies at compile time — redefining a colon word after
* compilation does not update callers. DEFER/IS gives late binding
* without any name-lookup overhead at execution time.
*
* IS enforces that the named word was created by DEFER (func ==
* defer_runtime). Attempting IS on any other word sets vm->error.
*/
#include "include/defer_words.h"
#include "../../include/vm.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
#include "../../include/dictionary_management.h"
#include <stdint.h>
/* ── defer_runtime ─────────────────────────────────────────────────────
* The func stored in every DEFER-created word.
* Data field = 0 means unset → error. Non-zero = DictEntry* to call.
*/
static void defer_runtime(VM *vm)
{
if (!vm || !vm->current_executing_entry) { if (vm) vm->error = 1; return; }
DictEntry *self = vm->current_executing_entry;
cell_t *df = vm_dictionary_get_data_field(self);
if (!df || *df == 0)
{
log_message(LOG_ERROR, "DEFER runtime: no target set for '%.*s'",
(int)self->name_len, self->name);
vm->error = 1;
return;
}
DictEntry *target = (DictEntry *)(uintptr_t)(*df);
if (!target || !target->func)
{
log_message(LOG_ERROR, "DEFER runtime: invalid target for '%.*s'",
(int)self->name_len, self->name);
vm->error = 1;
return;
}
vm->current_executing_entry = target;
target->func(vm);
}
/* ── DEFER ─────────────────────────────────────────────────────────────
* DEFER name ( -- )
* Creates a new dictionary entry with defer_runtime as its func.
* Data field initialised to 0 (unset). Calling the word before IS
* has been used will set vm->error.
*/
static void word_defer(VM *vm)
{
char name[WORD_NAME_MAX + 1];
int len = vm_parse_word(vm, name, sizeof(name));
if (len <= 0)
{
log_message(LOG_ERROR, "DEFER: missing name");
vm->error = 1;
return;
}
DictEntry *de = vm_create_word(vm, name, (size_t)len, defer_runtime);
if (!de)
{
log_message(LOG_ERROR, "DEFER: vm_create_word failed for '%.*s'", len, name);
vm->error = 1;
return;
}
cell_t *df = vm_dictionary_get_data_field(de);
if (!df)
{
log_message(LOG_ERROR, "DEFER: no data field for '%.*s'", len, name);
vm->error = 1;
return;
}
*df = 0; /* unset — defer_runtime will error if called before IS */
log_message(LOG_DEBUG, "DEFER: created '%.*s' (unset)", len, name);
}
/* ── IS ────────────────────────────────────────────────────────────────
* IS name ( xt -- )
* Pops an execution token (DictEntry*) from the stack, parses the next
* word from input, and stores the xt into that word's data field.
* The named word MUST have been created by DEFER; any other word causes
* an error. This prevents corrupting non-DEFER words' memory.
*/
static void word_is(VM *vm)
{
if (vm->dsp < 0)
{
log_message(LOG_ERROR, "IS: stack underflow");
vm->error = 1;
return;
}
cell_t xt = vm_pop(vm);
char name[WORD_NAME_MAX + 1];
int len = vm_parse_word(vm, name, sizeof(name));
if (len <= 0)
{
log_message(LOG_ERROR, "IS: missing name");
vm->error = 1;
return;
}
DictEntry *de = vm_find_word(vm, name, (size_t)len);
if (!de)
{
log_message(LOG_ERROR, "IS: word '%.*s' not found", len, name);
vm->error = 1;
return;
}
if (de->func != defer_runtime)
{
log_message(LOG_ERROR, "IS: '%.*s' was not created by DEFER", len, name);
vm->error = 1;
return;
}
cell_t *df = vm_dictionary_get_data_field(de);
if (!df)
{
log_message(LOG_ERROR, "IS: no data field for '%.*s'", len, name);
vm->error = 1;
return;
}
*df = xt;
log_message(LOG_DEBUG, "IS: stored xt into '%.*s'", len, name);
}
/* ── DEFER@ ────────────────────────────────────────────────────────────
* DEFER@ name ( -- xt )
* Parses the next word from input and pushes its current action xt.
* The named word must have been created by DEFER.
*/
static void word_defer_fetch(VM *vm)
{
char name[WORD_NAME_MAX + 1];
int len = vm_parse_word(vm, name, sizeof(name));
if (len <= 0)
{
log_message(LOG_ERROR, "DEFER@: missing name");
vm->error = 1;
return;
}
DictEntry *de = vm_find_word(vm, name, (size_t)len);
if (!de)
{
log_message(LOG_ERROR, "DEFER@: word '%.*s' not found", len, name);
vm->error = 1;
return;
}
if (de->func != defer_runtime)
{
log_message(LOG_ERROR, "DEFER@: '%.*s' was not created by DEFER", len, name);
vm->error = 1;
return;
}
cell_t *df = vm_dictionary_get_data_field(de);
if (!df)
{
log_message(LOG_ERROR, "DEFER@: no data field for '%.*s'", len, name);
vm->error = 1;
return;
}
vm_push(vm, *df);
log_message(LOG_DEBUG, "DEFER@: fetched xt from '%.*s'", len, name);
}
/* ── Registration ──────────────────────────────────────────────────────*/
void register_defer_words(VM *vm)
{
register_word(vm, "DEFER", word_defer);
register_word(vm, "IS", word_is);
register_word(vm, "DEFER@", word_defer_fetch);
}
+793
View File
@@ -0,0 +1,793 @@
/*
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.
*/
#include "include/defining_words.h"
#include "../../include/vm.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
#include "../../include/profiler.h"
#include "../../include/physics_metadata.h"
#include "../../include/platform_time.h"
#include "../../include/physics_pipelining_metrics.h"
#include <string.h>
#include <stdlib.h> /* malloc/free */
/* ───── Forward declarations so registration sees these symbols ───── */
static void defining_runtime_create(VM * vm);
static void defining_runtime_constant(VM * vm);
static void defining_runtime_variable(VM * vm);
static void defining_runtime_lit(VM * vm);
static void defining_runtime_dodoes(VM * vm);
static void defining_runtime_does_rt(VM * vm);
static void defining_word_colon(VM * vm);
static void defining_word_semicolon(VM * vm);
static void defining_word_create(VM * vm);
static void defining_word_constant(VM * vm);
static void defining_word_variable(VM * vm);
static void defining_word_left_bracket(VM * vm);
static void defining_word_right_bracket(VM * vm);
static void defining_word_state(VM * vm);
static void defining_word_compile(VM * vm);
static void defining_word_bracket_compile(VM * vm);
static void defining_word_literal(VM * vm);
static void defining_word_does(VM * vm);
static void defining_word_immediate(VM * vm);
static void dictionary_word_forget(VM * vm);
static void dictionary_word_fence(VM * vm);
/* ───────────────────────────── Runtimes ───────────────────────────── */
/* CONSTANT runtime: ( -- n ) Push stored value from header DF cell */
/**
* @brief Runtime behavior for CONSTANT words
*
* Pushes the constant value stored in the word's data field onto the stack.
* Stack effect: ( -- n )
*
* @param vm Pointer to the VM context
*/
static void defining_runtime_constant(VM *vm) {
if (!vm) return;
DictEntry *entry = vm->current_executing_entry;
if (!entry) {
vm->error = 1;
return;
}
cell_t *df = vm_dictionary_get_data_field(entry);
if (!df) {
vm->error = 1;
return;
}
vm_push(vm, *df);
log_message(LOG_DEBUG, "CONSTANT runtime: pushed %ld for %.*s",
(long) *df, entry->name_len, entry->name);
}
/* VARIABLE runtime: ( -- addr ) Push VM offset stored in header DF cell */
/**
* @brief Runtime behavior for VARIABLE words
*
* Pushes the variable's address (VM offset) stored in the word's data field.
* Stack effect: ( -- addr )
*
* @param vm Pointer to the VM context
*/
static void defining_runtime_variable(VM *vm) {
if (!vm) return;
DictEntry *entry = vm->current_executing_entry;
if (!entry) {
vm->error = 1;
return;
}
cell_t *df = vm_dictionary_get_data_field(entry);
if (!df) {
vm->error = 1;
return;
}
vm_push(vm, *df);
log_message(LOG_DEBUG, "VARIABLE runtime: pushed VM addr %ld for %.*s",
(long) *df, entry->name_len, entry->name);
}
/* CREATE runtime: ( -- addr ) Push DFA (VM byte offset) captured at CREATE */
/**
* @brief Runtime behavior for words defined by CREATE
*
* Pushes the word's data field address (DFA) captured at CREATE time.
* Stack effect: ( -- addr )
*
* @param vm Pointer to the VM context
*/
static void defining_runtime_create(VM *vm) {
if (!vm) return;
DictEntry *entry = vm->current_executing_entry;
if (!entry) {
vm->error = 1;
return;
}
cell_t *df = vm_dictionary_get_data_field(entry);
if (!df) {
vm->error = 1;
return;
}
vaddr_t dfa = (vaddr_t)(uint64_t)(*df);
if (!vm_addr_ok(vm, dfa, sizeof(cell_t))) {
vm->error = 1;
return;
}
vm_push(vm, CELL(dfa));
log_message(LOG_DEBUG, "CREATE runtime: pushed DFA %ld for %.*s",
(long) dfa, entry->name_len, entry->name);
}
/* LIT runtime: ( -- n ) — fetch next cell from threaded code via R-stack IP */
/**
* @brief Runtime behavior for literal values in threaded code
*
* Fetches the next cell from threaded code via IP and pushes it.
* Stack effect: ( -- n )
*
* @param vm Pointer to the VM context
*/
static void defining_runtime_lit(VM *vm) {
if (!vm) return;
if (vm->rsp < 0) {
vm->error = 1;
return;
}
cell_t *rip = (cell_t *) (uintptr_t) vm->return_stack[vm->rsp]; /* peek IP */
if (!rip) {
vm->error = 1;
return;
}
cell_t value = *rip; /* read literal cell */
vm_push(vm, value);
rip++; /* advance IP to after literal */
vm->return_stack[vm->rsp] = (cell_t)(uintptr_t)
rip; /* write-back */
log_message(LOG_DEBUG, "LIT: pushed %ld", (long) value);
}
/* DODOES runtime: when a created word runs, push PFA+8 (user data addr) then execute DOES>-body
PFA layout: [body_vaddr][user_data...]
We push the address of user_data, then execute body_vaddr code. */
/**
* @brief Runtime behavior for words defined with DOES>
*
* Pushes the address of user data area then executes the DOES> body code.
* PFA layout: [body_vaddr][user_data...]
*
* @param vm Pointer to the VM context
*/
static void defining_runtime_dodoes(VM *vm) {
if (!vm) return;
DictEntry *self = vm->current_executing_entry;
if (!self) {
vm->error = 1;
log_message(LOG_ERROR, "DODOES: no current entry");
return;
}
cell_t *df = vm_dictionary_get_data_field(self);
if (!df) {
vm->error = 1;
return;
}
vaddr_t pfa = (vaddr_t)(uint64_t)(*df);
/* PFA[0] holds vaddr of DOES>-body */
if (!vm_addr_ok(vm, pfa, sizeof(cell_t))) {
vm->error = 1;
log_message(LOG_ERROR, "DODOES: invalid PFA address %zu", (size_t) pfa);
return;
}
vaddr_t body_vaddr = (vaddr_t)(uint64_t)(*(cell_t *) vm_ptr(vm, pfa));
log_message(LOG_DEBUG, "DODOES: body_vaddr=%zu (loaded from PFA[0] at %zu)", (size_t) body_vaddr, (size_t) pfa);
if (!vm_addr_ok(vm, body_vaddr, sizeof(cell_t))) {
vm->error = 1;
log_message(LOG_ERROR, "DODOES: invalid body_vaddr %zu", (size_t) body_vaddr);
return;
}
/* Push address of user data (PFA + sizeof(cell_t)) per DOES> semantics */
vaddr_t user_data_addr = pfa + sizeof(cell_t);
vm_push(vm, (cell_t) user_data_addr);
/* Execute like a colon word until EXIT pops the frame */
int base_rsp = vm->rsp;
cell_t *ip = (cell_t *) vm_ptr(vm, body_vaddr);
vm->rsp++;
if (vm->rsp >= STACK_SIZE) {
vm->error = 1;
vm->rsp = base_rsp;
return;
}
vm->return_stack[vm->rsp] = (cell_t)(uintptr_t)
ip;
while (!vm->error && !vm->abort_requested && !vm->exit_colon && vm->rsp >= base_rsp + 1) {
cell_t *cur_ip = (cell_t *) (uintptr_t) vm->return_stack[vm->rsp];
DictEntry *entry = (DictEntry *) (uintptr_t)(*cur_ip++);
vm->return_stack[vm->rsp] = (cell_t)(uintptr_t)
cur_ip;
if (!entry || !entry->func) {
vm->error = 1;
break;
}
vm->current_executing_entry = entry;
physics_execution_heat_increment(entry);
profiler_word_count(entry);
profiler_word_enter(entry);
entry->func(vm);
physics_metadata_touch(entry, entry->execution_heat, sf_monotonic_ns());
profiler_word_exit(entry);
}
/* Handle EXIT from the DOES> body */
if (vm->exit_colon) {
vm->rsp = base_rsp;
vm->exit_colon = 0;
}
/* ABORT already reset rsp (reset_vm_state(), called synchronously by
* system_word_abort() before this loop even notices the flag) --
* restoring it to base_rsp here would partially undo that reset.
* Only clamp back up to base_rsp on a normal/EXIT completion. */
if (!vm->abort_requested && vm->rsp < base_rsp) vm->rsp = base_rsp;
}
/* does_rt: runs inside the defining word at DOES> time.
Converts the just-created child so its runtime is DODOES and
records the DOES>-body start address at the child's PFA[0].
NOTE: PFA already contains user data from , or other compile-time words.
We need to build a new PFA structure: [body_vaddr][old_user_data...]
and update the child's DF to point to this new PFA. */
/**
* @brief Helper routine executed at DOES> time
*
* Converts the just-created child word to use DODOES runtime behavior
* and records the DOES> body address in the child's PFA.
*
* @param vm Pointer to the VM context
*/
static void defining_runtime_does_rt(VM *vm) {
if (!vm) return;
DictEntry *child = vm->latest;
if (!child) {
vm->error = 1;
log_message(LOG_ERROR, "DOES>: no child to patch");
return;
}
/* IP (top R-stack) points at the next entry (EXIT we compile below); body starts after that */
if (vm->rsp < 0) {
vm->error = 1;
return;
}
cell_t *ip = (cell_t *) (uintptr_t) vm->return_stack[vm->rsp]; /* IP is a real pointer, not vaddr */
cell_t *body_ip = ip + 1; /* first entry AFTER EXIT */
vaddr_t body_vaddr = (vaddr_t)((uint8_t *) body_ip - vm->memory);
cell_t *df = vm_dictionary_get_data_field(child);
if (!df) {
vm->error = 1;
return;
}
vaddr_t old_pfa = (vaddr_t)(uint64_t)(*df);
/* Calculate how much user data was allocated between old PFA and current HERE */
vaddr_t current_here = (vaddr_t) vm->here;
size_t user_data_size = (old_pfa < current_here) ? (current_here - old_pfa) : 0;
/* Allocate new PFA: [body_vaddr][user_data...] */
vm_align(vm);
vaddr_t new_pfa = (vaddr_t) vm->here;
/* Allocate space for body_vaddr pointer */
cell_t *body_ptr = (cell_t *) vm_allot(vm, sizeof(cell_t));
if (!body_ptr) {
vm->error = 1;
return;
}
*body_ptr = (cell_t) body_vaddr;
/* Copy user data if any was allocated */
if (user_data_size > 0) {
void *user_data_dest = vm_allot(vm, user_data_size);
if (!user_data_dest) {
vm->error = 1;
return;
}
if (vm_addr_ok(vm, old_pfa, user_data_size)) {
memcpy(user_data_dest, vm_ptr(vm, old_pfa), user_data_size);
}
}
/* Update child's DF to point to new PFA */
*df = (cell_t) new_pfa;
child->func = defining_runtime_dodoes;
}
/* ───────────────────────────── Defining words ─────────────────────── */
/* CREATE ( "name" -- ) — 79: does NOT allocate data; captures cell-aligned HERE as DFA */
static void defining_word_create(VM *vm) {
if (!vm) return;
char namebuf[WORD_NAME_MAX + 1];
int nlen = vm_parse_word(vm, namebuf, sizeof(namebuf));
if (nlen <= 0) {
vm->error = 1;
return;
}
DictEntry *entry = vm_create_word(vm, namebuf, (size_t) nlen, defining_runtime_create);
if (!entry) {
vm->error = 1;
return;
}
vm_align(vm); /* align HERE for cell data */
vaddr_t dfa = (vaddr_t) vm->here;
cell_t *df = vm_dictionary_get_data_field(entry);
if (!df) {
vm->error = 1;
return;
}
*df = (cell_t)(int64_t)
dfa;
log_message(LOG_DEBUG, "CREATE: '%.*s' DFA=%ld (HERE=%ld)",
nlen, namebuf, (long) dfa, (long) vm->here);
}
/* : ( "name" -- ) begin colon definition — IMMEDIATE */
static void defining_word_colon(VM *vm) {
if (!vm) return;
/* 79: nested colon definitions are illegal */
if (vm->mode == MODE_COMPILE) {
log_message(LOG_ERROR, "Nested ':' inside a definition is not allowed");
vm->error = 1;
return;
}
char namebuf[WORD_NAME_MAX + 1];
int nlen = vm_parse_word(vm, namebuf, sizeof(namebuf));
if (nlen <= 0) {
vm->error = 1;
return;
}
vm_enter_compile_mode(vm, namebuf, (size_t) nlen);
log_message(LOG_DEBUG, ": Started definition of '%.*s'", nlen, namebuf);
}
/* ; ( -- ) end colon definition — IMMEDIATE (compiles EXIT in vm_exit_compile_mode) */
static void defining_word_semicolon(VM *vm) {
if (!vm) return;
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
return;
}
vm_exit_compile_mode(vm);
}
/* CONSTANT ( n "name" -- ) */
static void defining_word_constant(VM *vm) {
if (!vm) return;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t value = vm_pop(vm);
char namebuf[WORD_NAME_MAX + 1];
int nlen = vm_parse_word(vm, namebuf, sizeof(namebuf));
if (nlen <= 0) {
vm->error = 1;
return;
}
DictEntry *entry = vm_create_word(vm, namebuf, (size_t) nlen, defining_runtime_constant);
if (!entry) {
vm->error = 1;
return;
}
cell_t *df = vm_dictionary_get_data_field(entry);
if (!df) {
vm->error = 1;
return;
}
*df = value;
log_message(LOG_DEBUG, "CONSTANT: '%.*s' = %ld", nlen, namebuf, (long) value);
}
/* VARIABLE ( "name" -- ) allocate one cell in VM and store its addr in header DF */
static void defining_word_variable(VM *vm) {
if (!vm) return;
char namebuf[WORD_NAME_MAX + 1];
int nlen = vm_parse_word(vm, namebuf, sizeof(namebuf));
if (nlen <= 0) {
vm->error = 1;
return;
}
vm_align(vm);
vaddr_t addr = (vaddr_t) vm->here;
void *p = vm_allot(vm, sizeof(cell_t));
if (!p) {
vm->error = 1;
return;
}
*(cell_t *) p = 0;
DictEntry *entry = vm_create_word(vm, namebuf, (size_t) nlen, defining_runtime_variable);
if (!entry) {
vm->error = 1;
return;
}
cell_t *df = vm_dictionary_get_data_field(entry);
if (!df) {
vm->error = 1;
return;
}
*df = (cell_t)(int64_t)
addr;
log_message(LOG_DEBUG, "VARIABLE: '%.*s' at VM addr %ld", nlen, namebuf, (long) addr);
}
/* [ ( -- ) enter interpret state — IMMEDIATE */
static void defining_word_left_bracket(VM *vm) {
if (!vm) return;
/* Forth-79 requires STATE to be 0 for interpret and non-zero (typically -1) for compile */
vm_store_cell(vm, vm->state_addr, 0);
vm->mode = MODE_INTERPRET;
log_message(LOG_DEBUG, "[: interpret mode");
}
/* ] ( -- ) enter compile state — IMMEDIATE */
static void defining_word_right_bracket(VM *vm) {
if (!vm) return;
vm_store_cell(vm, vm->state_addr, (cell_t) - 1);
vm->mode = MODE_COMPILE;
log_message(LOG_DEBUG, "]: compile mode");
}
/* STATE ( -- addr ) push VM addr of STATE cell */
static void defining_word_state(VM *vm) {
if (!vm) return;
vm_push(vm, (cell_t) vm->state_addr);
}
/* COMPILE ( "word" -- ) — IMMEDIATE (legacy) */
static void defining_word_compile(VM *vm) {
if (!vm) return;
char namebuf[WORD_NAME_MAX + 1];
int nlen = vm_parse_word(vm, namebuf, sizeof(namebuf));
if (nlen <= 0) {
vm->error = 1;
return;
}
DictEntry *entry = vm_find_word(vm, namebuf, (size_t) nlen);
if (!entry) {
vm->error = 1;
return;
}
vm_compile_word(vm, entry);
log_message(LOG_DEBUG, "COMPILE: compiled '%.*s'", nlen, namebuf);
}
/* [COMPILE] ( "word" -- ) — IMMEDIATE: compile even if the word is IMMEDIATE */
static void defining_word_bracket_compile(VM *vm) {
defining_word_compile(vm);
}
/* LITERAL ( n -- ) — IMMEDIATE: compile a literal */
static void defining_word_literal(VM *vm) {
if (!vm) return;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t v = vm_pop(vm);
vm_compile_literal(vm, v);
log_message(LOG_DEBUG, "LITERAL: compiled %ld", (long) v);
}
/* FORGET ( "name" -- ) remove target and newer words; rewind HERE to target DFA */
static void dictionary_word_forget(VM *vm) {
if (!vm) return;
char namebuf[WORD_NAME_MAX + 1];
int nlen = vm_parse_word(vm, namebuf, sizeof(namebuf));
if (nlen <= 0) {
vm->error = 1;
return;
}
int forget_failed = 0;
sf_mutex_lock(&vm->dict_lock);
/* Find target */
DictEntry *e = vm->latest;
DictEntry *target = NULL;
while (e) {
if (!(e->flags & WORD_HIDDEN) &&
e->name_len == (uint8_t) nlen &&
memcmp(e->name, namebuf, (size_t) nlen) == 0) {
target = e;
break;
}
e = e->link;
}
if (!target) {
vm->error = 1;
forget_failed = 1;
goto forget_exit;
}
/* Ensure target is NEWER than the fence */
int allowed = 0;
for (e = vm->latest; e; e = e->link) {
if (e == target) {
allowed = 1;
break;
}
if (e == vm->dict_fence_latest) break;
}
if (!allowed) {
vm->error = 1;
forget_failed = 1;
goto forget_exit;
}
/* Compute new HERE from target’s DFA (bounded by fence HERE) */
vaddr_t new_here = (vaddr_t) vm->here;
cell_t *df = vm_dictionary_get_data_field(target);
if (df) {
vaddr_t dfa = (vaddr_t)(uint64_t)(*df);
if (vm_addr_ok(vm, dfa, 0)) {
vaddr_t fence_here = (vaddr_t) vm->dict_fence_here;
new_here = (dfa < fence_here) ? fence_here : dfa;
}
} else {
new_here = (vaddr_t) vm->dict_fence_here;
}
/* Save target's link before freeing (needed for relinking) */
DictEntry *target_next = target->link;
/* Free headers from latest down to and including target, stopping before fence */
e = vm->latest;
while (e && e != vm->dict_fence_latest) {
DictEntry *next = e->link;
int is_target = (e == target);
vm_dictionary_untrack_entry(vm, e);
/* Free transition metrics before freeing entry */
if (e->transition_metrics) {
transition_metrics_cleanup(e->transition_metrics);
free(e->transition_metrics);
e->transition_metrics = NULL;
}
free(e);
if (is_target) break;
e = next;
}
/* Relink: FORGET removes target and everything newer (closer to
* vm->latest) -- the free loop above just freed every entry from
* vm->latest through target inclusive, which by definition includes
* any entry between them. There is nothing left to "skip past" --
* target_next (target's own surviving link) is always the correct
* new head, whether or not target was vm->latest itself. (Previously
* this branched on whether an entry existed between vm->latest and
* target, and relinked through it -- but that entry was always inside
* the just-freed range, making it a use-after-free on every FORGET of
* a non-latest word.) */
vm->latest = target_next;
vm->here = (size_t) new_here;
log_message(LOG_DEBUG, "FORGET: forgot '%.*s'; HERE=%ld (fence HERE=%ld)",
nlen, namebuf, (long) vm->here, (long) vm->dict_fence_here);
forget_exit:
sf_mutex_unlock(&vm->dict_lock);
if (forget_failed)
return;
}
/* FENCE ( -- ) raise the FORGET boundary to the current dictionary top.
* dict_fence_latest/dict_fence_here already exist and are already honored
* by FORGET (set once at bootstrap, protecting the base wordset); this is
* the first word exposing that state so callers can advance it further --
* e.g. an SDK capsule calling FENCE after loading protects its own
* definitions the same way the base wordset is already protected. */
static void dictionary_word_fence(VM *vm) {
if (!vm) return;
sf_mutex_lock(&vm->dict_lock);
vm->dict_fence_latest = vm->latest;
vm->dict_fence_here = vm->here;
sf_mutex_unlock(&vm->dict_lock);
log_message(LOG_DEBUG, "FENCE: raised to HERE=%ld", (long) vm->dict_fence_here);
}
/* DOES> — IMMEDIATE: finalize the defining word’s create-part and compile DOES>-body */
static void defining_word_does(VM *vm) {
if (!vm) return;
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "DOES>: compile-only");
return;
}
/* Compile helper (void fn; check vm->error after) */
vm_compile_call(vm, defining_runtime_does_rt);
if (vm->error) {
log_message(LOG_ERROR, "DOES>: could not compile does_rt");
return;
}
/* Compile EXIT so the defining word returns immediately after patching */
DictEntry *exitw = vm_find_word(vm, "EXIT", 4);
if (!exitw) {
vm->error = 1;
log_message(LOG_ERROR, "DOES>: EXIT not found");
return;
}
vm_compile_word(vm, exitw);
if (vm->error) return;
/* Continue compiling: the following words form the DOES>-body executed by DODOES later. */
}
/* IMMEDIATE ( -- ) mark latest word immediate — IMMEDIATE itself */
static void defining_word_immediate(VM *vm) {
if (!vm) return;
if (!vm->latest) {
vm->error = 1;
return;
}
vm->latest->flags |= WORD_IMMEDIATE;
log_message(LOG_DEBUG, "IMMEDIATE: marked '%.*s' immediate",
(int) vm->latest->name_len, vm->latest->name);
}
/* ───────────────────────────── DEFER / IS ─────────────────────────── */
/* DEFER/IS/DEFER@ are NOT defined here: defer_words.c registers the same
* three names later in boot order (word_registry.c Module 27 vs this
* file's Module 17), and it has no __STARKERNEL__ guard, so it shadows
* this file's versions in both hosted and kernel builds. FORTH's
* newest-first dictionary lookup means defer_words.c's versions (a
* self-contained implementation, unrelated to this file's former
* defining_runtime_defer) are the only ones ever reachable. See
* proof/StarForth_Defer_Words.thy and proof/FINDINGS.md §3. */
/* ───────────────────────────── Registration ───────────────────────── */
/**
* @brief Register all defining words in the dictionary
*
* Registers core defining words like :, ;, CREATE, CONSTANT, VARIABLE,
* and their support words like IMMEDIATE, STATE, etc.
*
* @param vm Pointer to the VM context
*/
void register_defining_words(VM *vm) {
/* Core colon pair — both IMMEDIATE */
register_word(vm, ":", defining_word_colon);
vm_make_immediate(vm);
register_word(vm, ";", defining_word_semicolon);
vm_make_immediate(vm);
/* CREATE / VARIABLE / CONSTANT */
register_word(vm, "CREATE", defining_word_create);
register_word(vm, "VARIABLE", defining_word_variable);
register_word(vm, "CONSTANT", defining_word_constant);
/* IMMEDIATE (make IMMEDIATE itself immediate) */
register_word(vm, "IMMEDIATE", defining_word_immediate);
vm_make_immediate(vm);
/* STATE and mode switchers */
register_word(vm, "STATE", defining_word_state);
register_word(vm, "[", defining_word_left_bracket);
vm_make_immediate(vm);
register_word(vm, "]", defining_word_right_bracket);
vm_make_immediate(vm);
/* Dictionary management */
register_word(vm, "FORGET", dictionary_word_forget);
register_word(vm, "FENCE", dictionary_word_fence);
/* Compile helpers — immediate */
register_word(vm, "COMPILE", defining_word_compile);
vm_make_immediate(vm);
register_word(vm, "[COMPILE]", defining_word_bracket_compile);
vm_make_immediate(vm);
/* LIT runtime + LITERAL (immediate) */
register_word(vm, "LIT", defining_runtime_lit);
register_word(vm, "LITERAL", defining_word_literal);
vm_make_immediate(vm);
/* DOES> plumbing (lowercase internal helper visible for tests/trace) */
register_word(vm, "does_rt", defining_runtime_does_rt); /* internal helper */
register_word(vm, "DOES>", defining_word_does);
vm_make_immediate(vm);
}
@@ -0,0 +1,287 @@
/*
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_diagnostic_words.c - FORTH Interface for Dictionary Heat Optimization
Introspection words to observe and control heat-aware dictionary lookup behavior.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "vm.h"
#include "word_registry.h"
#include "platform_time.h"
#include "dictionary_heat_optimization.h"
/**
* @brief HEAT-PERCENTILES ( -- 25th 50th 75th )
*
* Pushes the three heat percentile thresholds currently used by the
* heat-aware dictionary lookup strategy: @c heat_threshold_25th,
* @c heat_threshold_50th, and @c heat_threshold_75th. These are updated
* by @c dict_update_heat_percentiles() during bucket reorganisation.
*
* Stack effect: ( -- 25th 50th 75th ) TOS = 75th
*
* @param vm Active VM; sets @c vm->error = 1 on stack overflow
*/
void forth_HEAT_PERCENTILES(VM *vm) {
if (vm->error) return;
if (vm->dsp + 3 > STACK_SIZE) {
vm->error = 1;
return;
}
vm->data_stack[vm->dsp++] = vm->heat_threshold_25th;
vm->data_stack[vm->dsp++] = vm->heat_threshold_50th;
vm->data_stack[vm->dsp++] = vm->heat_threshold_75th;
}
/**
* @brief LOOKUP-STRATEGY@ ( -- strategy )
*
* Pushes the current dictionary lookup strategy: 0 = naive (newest-first
* linear scan), 1 = heat-aware (hot-bucket-first).
*
* Stack effect: ( -- strategy )
*
* @param vm Active VM; sets @c vm->error = 1 on stack overflow
*/
void forth_LOOKUP_STRATEGY_FETCH(VM *vm) {
if (vm->error) return;
if (vm->dsp >= STACK_SIZE) {
vm->error = 1;
return;
}
vm->data_stack[vm->dsp++] = (cell_t)vm->lookup_strategy;
}
/**
* @brief LOOKUP-STRATEGY! ( strategy -- )
*
* Sets @c vm->lookup_strategy to @c strategy. Only values 0 (naive) and
* 1 (heat-aware) are accepted; out-of-range values are silently discarded.
*
* Stack effect: ( strategy -- )
*
* @param vm Active VM; sets @c vm->error = 1 on stack underflow
*/
void forth_LOOKUP_STRATEGY_STORE(VM *vm) {
if (vm->error) return;
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t strategy = vm->data_stack[--vm->dsp];
if (strategy == 0 || strategy == 1) {
vm->lookup_strategy = (int)strategy;
}
}
/**
* @brief REORG-BUCKETS ( -- )
*
* Immediately triggers @c dict_reorganize_buckets_by_heat() followed by
* @c dict_update_heat_percentiles() to re-sort the dictionary's lookup
* buckets by current execution heat and refresh the percentile thresholds.
* Normally this happens automatically on a heartbeat cycle; this word forces
* it on-demand for testing or after a large batch of @c HEAT! assignments.
*
* Stack effect: ( -- )
*
* @param vm Active VM; no-op if @c vm->error is set
*/
void forth_REORG_BUCKETS(VM *vm) {
if (vm->error) return;
dict_reorganize_buckets_by_heat(vm);
dict_update_heat_percentiles(vm);
}
/**
* @brief SHOW-HEAT-OPTIMIZATION ( -- )
*
* Prints a summary of the current dictionary heat optimization state to
* @c stdout: the active lookup strategy (naive or heat-aware), the three
* percentile threshold values, and the resulting hot/warm/cool heat zones.
* Intended for interactive diagnostics.
*
* Stack effect: ( -- )
*
* @param vm Active VM; no-op if @c vm->error is set
*/
void forth_SHOW_HEAT_OPTIMIZATION(VM *vm) {
if (vm->error) return;
printf("\n=== Dictionary Heat Optimization Status ===\n");
printf("Lookup Strategy: ");
if (vm->lookup_strategy == 0) {
printf("NAIVE (newest-first search)\n");
} else if (vm->lookup_strategy == 1) {
printf("HEAT-AWARE (hot words first)\n");
} else {
printf("UNKNOWN (%d)\n", vm->lookup_strategy);
}
printf("Heat Percentile Thresholds:\n");
printf(" 25th percentile: %ld\n", vm->heat_threshold_25th);
printf(" 50th percentile: %ld\n", vm->heat_threshold_50th);
printf(" 75th percentile: %ld\n", vm->heat_threshold_75th);
printf("\nHeat Zones:\n");
printf(" TOP 25%% (hot) : >= %ld heat\n", vm->heat_threshold_75th);
printf(" MIDDLE 50%% (warm) : %ld - %ld heat\n",
vm->heat_threshold_25th, vm->heat_threshold_75th);
printf(" BOTTOM 25%% (cool) : < %ld heat\n", vm->heat_threshold_25th);
printf("\n");
}
/**
* @brief COMPARE-LOOKUPS ( iterations -- )
*
* Micro-benchmark that measures naive vs heat-aware dictionary lookup
* performance over @c iterations passes through six common test words
* (DUP, DROP, SWAP, @, !, EMIT). Prints elapsed time in milliseconds and
* the percentage speedup (or slowdown) for heat-aware vs naive. Restores
* the original @c vm->lookup_strategy on completion. Intended for
* interactive diagnostics; not used in DoE mode.
*
* Stack effect: ( iterations -- )
*
* @param vm Active VM; sets @c vm->error = 1 on underflow or non-positive @c iterations
*/
void forth_COMPARE_LOOKUPS(VM *vm) {
if (vm->error) return;
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t iterations = vm->data_stack[--vm->dsp];
if (iterations <= 0) {
printf("Need positive iteration count\n");
return;
}
printf("\n=== Dictionary Lookup Comparison ===\n");
printf("Iterations: %ld\n", iterations);
/* Common test words */
const char *test_words[] = {"DUP", "DROP", "SWAP", "@", "!", "EMIT", NULL};
/* Strategy 1: Naive (current strategy) */
printf("\nNaive (newest-first) lookup:\n");
int orig_strategy = vm->lookup_strategy;
vm->lookup_strategy = 0;
uint64_t start_ns = sf_monotonic_ns();
for (cell_t i = 0; i < iterations; i++) {
for (int j = 0; test_words[j]; j++) {
vm_find_word(vm, test_words[j], strlen(test_words[j]));
}
}
uint64_t naive_ns = sf_monotonic_ns() - start_ns;
/* Strategy 2: Heat-aware */
printf("Heat-aware lookup:\n");
dict_reorganize_buckets_by_heat(vm);
vm->lookup_strategy = 1;
start_ns = sf_monotonic_ns();
for (cell_t i = 0; i < iterations; i++) {
for (int j = 0; test_words[j]; j++) {
vm_find_word(vm, test_words[j], strlen(test_words[j]));
}
}
uint64_t heat_ns = sf_monotonic_ns() - start_ns;
/* Restore original strategy */
vm->lookup_strategy = orig_strategy;
/* Report results_run_01_2025_12_08 */
printf("\n=== Benchmark Results ===\n");
printf("Naive time: %.3f ms\n", (double)naive_ns / 1000000.0);
printf("Heat-aware time: %.3f ms\n", (double)heat_ns / 1000000.0);
if (heat_ns > 0) {
double speedup = (double)naive_ns / (double)heat_ns;
if (speedup > 1.0) {
printf("Speedup: %.2f%% faster\n", (speedup - 1.0) * 100.0);
} else {
printf("Slowdown: %.2f%% slower\n", (1.0 - speedup) * 100.0);
}
}
printf("\n");
}
/**
* @brief Register all dictionary heat diagnostic words with the VM dictionary.
*
* Registers: @c HEAT-PERCENTILES, @c LOOKUP-STRATEGY@, @c LOOKUP-STRATEGY!,
* @c REORG-BUCKETS, @c SHOW-HEAT-OPTIMIZATION, @c COMPARE-LOOKUPS.
* Called during VM bootstrap.
*
* @param vm Active VM to register words into
*/
void register_dictionary_heat_diagnostic_words(VM *vm) {
register_word(vm, "HEAT-PERCENTILES", forth_HEAT_PERCENTILES);
register_word(vm, "LOOKUP-STRATEGY@", forth_LOOKUP_STRATEGY_FETCH);
register_word(vm, "LOOKUP-STRATEGY!", forth_LOOKUP_STRATEGY_STORE);
register_word(vm, "REORG-BUCKETS", forth_REORG_BUCKETS);
register_word(vm, "SHOW-HEAT-OPTIMIZATION", forth_SHOW_HEAT_OPTIMIZATION);
register_word(vm, "COMPARE-LOOKUPS", forth_COMPARE_LOOKUPS);
}
@@ -0,0 +1,469 @@
/*
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.
*/
/* dictionary_manipulation_words.c - FORTH-79 Dictionary Manipulation Words */
#include "include/dictionary_manipulation_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
/* Global state for dictionary manipulation */
static cell_t state_variable = 0; /* STATE - compilation state */
/* Helper function to calculate aligned address */
static uintptr_t align_address(uintptr_t addr) {
size_t alignment = sizeof(cell_t);
return (addr + alignment - 1) & ~(alignment - 1);
}
/* Helper function to get name field from execution token */
static char *get_name_field(DictEntry *entry) {
if (entry == NULL) {
return NULL;
}
return (char *) entry->name;
}
/* Helper function to get body address from execution token */
static void *get_body_address(DictEntry *entry) {
if (entry == NULL) {
return NULL;
}
/* Body follows the header structure */
uintptr_t body_addr = (uintptr_t) entry + sizeof(DictEntry) + entry->name_len;
return (void *) align_address(body_addr);
}
/* Helper function to find dictionary entry from name field */
static DictEntry *find_entry_from_name(VM *vm, char *name_field) {
DictEntry *entry;
entry = vm->latest;
while (entry != NULL) {
if ((char *) entry->name == name_field) {
return entry;
}
entry = entry->link;
}
return NULL;
}
/* Helper function to traverse name field */
static char *traverse_name_field(char *name_addr, int direction) {
uint8_t *ptr;
uint8_t name_len;
if (name_addr == NULL) {
return NULL;
}
/* FORTH-79 names start with length byte */
ptr = (uint8_t *) name_addr;
if (direction > 0) {
/* Forward traversal - skip over name */
name_len = *ptr & 0x1F; /* Mask off flags */
return (char *) (ptr + name_len + 1);
} else {
/* Backward traversal - find start of name */
/* This is more complex - we need to scan backwards */
/* For simplicity, assume we're at the start already */
return name_addr;
}
}
/**
* @brief FORTH word SMUDGE - Toggle smudge bit of latest word
* @param vm Pointer to VM instance
* @stack ( -- )
* @note Compile-only word
*/
static void dictionary_m_word_smudge(VM *vm) {
// compile-only: error if used while interpreting
if (vm->mode != MODE_COMPILE) {
// correct
vm->error = 1;
return;
}
// existing body that toggles/sets the smudge/hidden bit on the *latest* word
// (leave your current logic exactly as-is)
}
/* >BODY ( xt -- addr ) Convert execution token to body */
void dictionary_m_word_to_body(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, ">BODY: Stack underflow");
vm->error = 1;
return;
}
cell_t xt = vm_pop(vm);
log_message(LOG_DEBUG, ">BODY: POP: %ld (dsp=%d)", (long) xt, vm->dsp);
DictEntry *entry = (DictEntry *) (intptr_t) xt;
if (!entry) {
log_message(LOG_ERROR, ">BODY: Invalid execution token (NULL)");
vm->error = 1;
return;
}
// Use the VM's dictionary function to get the data field
cell_t *data_field = vm_dictionary_get_data_field(entry);
if (!data_field) {
log_message(LOG_ERROR, ">BODY: Unable to get data field address for entry %p", (void *) entry);
vm->error = 1;
return;
}
cell_t body_addr = (cell_t)(intptr_t)data_field;
log_message(LOG_DEBUG, ">BODY: xt=%p -> body=%p", (void *) entry, (void *) data_field);
vm_push(vm, body_addr);
log_message(LOG_DEBUG, ">BODY: PUSH: %ld (dsp=%d)", (long) body_addr, vm->dsp);
}
/* >NAME ( xt -- addr ) Convert execution token to name */
void dictionary_m_word_to_name(VM *vm) {
cell_t xt;
DictEntry *entry;
char *name_field;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
xt = vm_pop(vm);
entry = (DictEntry *) (uintptr_t) xt;
name_field = get_name_field(entry);
if (name_field == NULL) {
vm->error = 1;
return;
}
vm_push(vm, (cell_t)(uintptr_t)name_field);
}
/* NAME> ( addr -- xt ) Convert name to execution token */
void dictionary_m_word_name_to(VM *vm) {
cell_t addr;
char *name_field;
DictEntry *entry;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
addr = vm_pop(vm);
name_field = (char *) (uintptr_t) addr;
entry = find_entry_from_name(vm, name_field);
if (entry == NULL) {
vm->error = 1;
return;
}
vm_push(vm, (cell_t)(uintptr_t)entry);
}
/* >LINK ( addr -- addr ) Get link field address */
void dictionary_m_word_to_link(VM *vm) {
cell_t addr;
DictEntry *entry;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
addr = vm_pop(vm);
entry = (DictEntry *) (uintptr_t) addr;
if (entry == NULL) {
vm->error = 1;
return;
}
/* Link field is part of the DictEntry structure */
vm_push(vm, (cell_t)(uintptr_t) & entry->link);
}
/* LINK> ( addr -- addr ) Get next word from link */
void dictionary_m_word_link_from(VM *vm) {
cell_t addr;
DictEntry **link_field;
DictEntry *next_entry;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
addr = vm_pop(vm);
link_field = (DictEntry **) (uintptr_t) addr;
if (link_field == NULL) {
vm->error = 1;
return;
}
next_entry = *link_field;
vm_push(vm, (cell_t)(uintptr_t)next_entry);
}
/* CFA ( addr -- xt ) Get code field address */
void dictionary_m_word_cfa(VM *vm) {
cell_t addr;
DictEntry *entry;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
addr = vm_pop(vm);
entry = (DictEntry *) (uintptr_t) addr;
if (entry == NULL) {
vm->error = 1;
return;
}
/* In our implementation, the execution token is the entry itself */
vm_push(vm, (cell_t)(uintptr_t)entry);
}
/* LFA ( addr -- addr ) Get link field address */
void dictionary_m_word_lfa(VM *vm) {
dictionary_m_word_to_link(vm); /* Same as >LINK */
}
/* NFA ( addr -- addr ) Get name field address */
void dictionary_m_word_nfa(VM *vm) {
cell_t addr;
DictEntry *entry;
char *name_field;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
addr = vm_pop(vm);
entry = (DictEntry *) (uintptr_t) addr;
name_field = get_name_field(entry);
if (name_field == NULL) {
vm->error = 1;
return;
}
vm_push(vm, (cell_t)(uintptr_t)name_field);
}
/* PFA ( addr -- addr ) Get parameter field address */
void dictionary_m_word_pfa(VM *vm) {
cell_t addr;
DictEntry *entry;
void *body_addr;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
addr = vm_pop(vm);
entry = (DictEntry *) (uintptr_t) addr;
body_addr = get_body_address(entry);
if (body_addr == NULL) {
vm->error = 1;
return;
}
vm_push(vm, (cell_t)(uintptr_t)body_addr);
}
/* TRAVERSE ( addr n -- addr ) Move through name field */
void dictionary_m_word_traverse(VM *vm) {
cell_t n, addr;
char *name_addr;
char *result_addr;
if (vm->dsp < 1) {
vm->error = 1;
return;
}
n = vm_pop(vm);
addr = vm_pop(vm);
name_addr = (char *) (uintptr_t) addr;
result_addr = traverse_name_field(name_addr, (int) n);
if (result_addr == NULL) {
vm->error = 1;
return;
}
vm_push(vm, (cell_t)(uintptr_t)result_addr);
}
/* INTERPRET ( -- ) Text interpreter */
void dictionary_m_word_interpret(VM *vm) {
/* Simply call the VM's built-in interpreter - it handles everything */
/* The VM already has proper input management, word parsing, and execution */
/* In FORTH-79, INTERPRET processes the current input stream */
/* Since vm_interpret handles input parsing and execution, we just ensure */
/* we're in the right mode and let the VM do the work */
/* Set interpretation mode */
vm->mode = MODE_INTERPRET;
state_variable = 0;
/* The actual interpretation happens through vm_interpret() calls */
/* This word exists mainly for completeness and mode setting */
}
static void dictionary_m_word_find(VM *vm) {
char namebuf[128];
int nlen = vm_parse_word(vm, namebuf, sizeof namebuf);
if (nlen <= 0) {
vm->error = 1; // real input underflow
return;
}
DictEntry *e = vm_find_word(vm, namebuf, (size_t) nlen);
if (e) {
vm_push(vm, (cell_t)(uintptr_t)e); // compilation address; swap to CFA if you prefer later
} else {
vm_push(vm, 0); // miss is NOT an error
}
}
/* ' ( -- xt ) FORTH-79 tick: parse next word, push its execution token.
* Non-immediate — in compile mode the interpreter compiles a call to ',
* so this function only ever runs in interpret mode. */
static void dictionary_m_word_tick(VM *vm) {
char namebuf[128];
int nlen = vm_parse_word(vm, namebuf, sizeof namebuf);
if (nlen <= 0) {
log_message(LOG_ERROR, "': unable to parse word");
vm->error = 1;
return;
}
DictEntry *e = vm_find_word(vm, namebuf, (size_t) nlen);
if (!e) {
log_message(LOG_ERROR, "': word '%.*s' not found", nlen, namebuf);
vm->error = 1;
return;
}
log_message(LOG_DEBUG, "': found '%.*s' xt=%p", nlen, namebuf, (void *) e);
vm_push(vm, (cell_t)(uintptr_t)e);
}
// in src/word_source/dictionary_manipulation_words.c
static void dictionary_m_word_hidden(VM *vm) {
// compile-only guard (matches your other compile-only words)
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
return;
}
// get the latest entry (use whatever you use elsewhere; many places access vm->latest)
DictEntry *e = vm->latest;
if (!e) {
vm->error = 1;
return;
}
// Set the hidden/smudge flag. Prefer a named flag if you have it.
// If your codebase uses WORD_HIDDEN, use that.
// If it uses the same bit as SMUDGE, use that (often called WORD_SMUDGED).
#ifdef WORD_HIDDEN
e->flags |= WORD_HIDDEN;
#else
// Fallback: if you only have a toggling SMUDGE path, set conditionally using it.
if (!(e->flags & WORD_SMUDGED)) {
// call your existing SMUDGE implementation to flip it on once
dictionary_word_smudge(vm);
// ensure we didn't accidentally set an error
if (vm->error) return;
}
#endif
}
/*
* @brief Register all dictionary manipulation words with the VM
* @param vm Pointer to VM instance
* @details Registers standard FORTH-79 dictionary manipulation words
*/
void register_dictionary_manipulation_words(VM *vm) {
/* Register all dictionary manipulation words */
/* [, ], STATE are NOT registered here: defining_words.c registers the
* same three names later in boot order (word_registry.c Module 17 vs
* this file's Module 13), and FORTH's newest-first dictionary lookup
* means defining_words.c's versions are the only ones ever reachable.
* The versions formerly here also wrote a dead file-scope static
* instead of vm->state_addr -- see proof/StarForth_Defining_Words.thy
* and proof/FINDINGS.md §1. */
register_word(vm, "SMUDGE", dictionary_m_word_smudge);
register_word(vm, "HIDDEN", dictionary_m_word_hidden);
register_word(vm, ">BODY", dictionary_m_word_to_body);
register_word(vm, ">NAME", dictionary_m_word_to_name);
register_word(vm, "NAME>", dictionary_m_word_name_to);
register_word(vm, ">LINK", dictionary_m_word_to_link);
register_word(vm, "LINK>", dictionary_m_word_link_from);
register_word(vm, "CFA", dictionary_m_word_cfa);
register_word(vm, "LFA", dictionary_m_word_lfa);
register_word(vm, "NFA", dictionary_m_word_nfa);
register_word(vm, "PFA", dictionary_m_word_pfa);
register_word(vm, "TRAVERSE", dictionary_m_word_traverse);
register_word(vm, "INTERPRET", dictionary_m_word_interpret);
register_word(vm, "FIND", dictionary_m_word_find);
register_word(vm, "'", dictionary_m_word_tick);
}
+224
View File
@@ -0,0 +1,224 @@
/*
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.
*/
#include "include/dictionary_words.h"
#include "../../include/word_registry.h"
#include "vm.h"
#include "../../include/log.h"
/* === SAFE DICTIONARY WORDS IMPLEMENTATION (addresses are VM offsets) === */
/**
* @brief HERE ( -- addr ) Returns the dictionary pointer
* @param vm Pointer to the VM structure
*/
void dictionary_word_here(VM *vm) {
vm_push(vm, vm->here);
}
static void dictionary_word_align(VM *vm) {
vm_align(vm);
}
/**
* @brief ALLOT ( n -- ) Allocate n bytes in dictionary
* @param vm Pointer to the VM structure
* @details Allocates n bytes in the dictionary. n can be negative to deallocate space
*/
void dictionary_word_allot(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
cell_t new_here = vm->here + n;
/* Dictionary growth is bounded to DICTIONARY_MEMORY_SIZE, not the full
* VM_MEMORY_SIZE: blocks >= USER_BLOCKS_START live in the same flat
* vm->memory array (see vm_get_block_addr), so letting `here` grow past
* the dictionary region would silently corrupt live block/user data. */
if (new_here < 0 || new_here > (cell_t) DICTIONARY_MEMORY_SIZE) {
vm->error = 1;
return;
}
vm->here = new_here;
}
/**
* @brief , ( n -- ) Compile cell into dictionary
* @param vm Pointer to the VM structure
* @details Compiles a single cell value into the dictionary at HERE
*/
void dictionary_word_comma(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
vm_align(vm);
if (vm->here + (cell_t) sizeof(cell_t) > DICTIONARY_MEMORY_SIZE) {
vm->error = 1;
return;
}
vaddr_t addr = VM_ADDR(vm->here);
if (!vm_addr_ok(vm, addr, sizeof(cell_t))) {
vm->error = 1;
return;
}
vm_store_cell(vm, addr, n);
vm->here += sizeof(cell_t);
}
/**
* @brief C, ( c -- ) Compile byte into dictionary
* @param vm Pointer to the VM structure
* @details Compiles a single byte value into the dictionary at HERE
*/
void dictionary_word_c_comma(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t c = vm_pop(vm);
if (vm->here + 1 > DICTIONARY_MEMORY_SIZE) {
vm->error = 1;
return;
}
vaddr_t a = VM_ADDR(vm->here);
if (!vm_addr_ok(vm, a, 1)) {
vm->error = 1;
return;
}
vm_store_u8(vm, a, (uint8_t)(c & 0xFF));
vm->here += 1;
}
/**
* @brief 2, ( d -- ) Compile double-cell into dictionary
* @param vm Pointer to the VM structure
* @details Compiles a double cell value into the dictionary at HERE, low cell first
*/
void dictionary_word_2comma(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t high = vm_pop(vm);
cell_t low = vm_pop(vm);
vm_align(vm);
if (vm->here + (cell_t)(2 * sizeof(cell_t)) > DICTIONARY_MEMORY_SIZE) {
vm->error = 1;
return;
}
vaddr_t a = VM_ADDR(vm->here);
if (!vm_addr_ok(vm, a, 2 * sizeof(cell_t))) {
vm->error = 1;
return;
}
vm_store_cell(vm, a, low);
vm_store_cell(vm, a + sizeof(cell_t), high);
vm->here += 2 * sizeof(cell_t);
}
/**
* @brief PAD ( -- addr ) Get scratch pad buffer address
* @param vm Pointer to the VM structure
* @details Returns VM address of 512-byte temporary text buffer at top of memory
*/
void dictionary_word_pad(VM *vm) {
size_t pad_offset = VM_MEMORY_SIZE - 512;
vm_push(vm, (cell_t) pad_offset);
}
/**
* @brief SP@ ( -- sp ) Get current stack pointer
* @param vm Pointer to the VM structure
* @details Returns the current stack-pointer index (top is 0)
*/
void dictionary_word_sp_fetch(VM *vm) {
/* No stack args required */
vm_push(vm, (cell_t) vm->dsp);
}
/**
* @brief SP! ( sp -- ) Set stack pointer
* @param vm Pointer to the VM structure
* @details Restores the stack-pointer, but never allows growing the stack
*/
void dictionary_word_sp_store(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
} /* need one arg */
cell_t new_sp = vm_pop(vm);
/* Valid range: -1 (empty stack) up to current dsp (can shrink, not grow) */
if (new_sp < -1 || new_sp > vm->dsp) {
vm->error = 1;
return;
}
vm->dsp = new_sp;
/* We don’t need to scrub values above dsp; they’re considered garbage/unused. */
}
/**
* @brief LATEST ( -- addr ) Get latest definition address
* @param vm Pointer to the VM structure
* @details Returns VM address near most recent compiled definition (end of dictionary)
*/
void dictionary_word_latest(VM *vm) {
vm_push(vm, vm->here);
}
/* Registration */
void register_dictionary_words(VM *vm) {
register_word(vm, "HERE", dictionary_word_here);
register_word(vm, "ALIGN", dictionary_word_align);
register_word(vm, "ALLOT", dictionary_word_allot);
register_word(vm, ",", dictionary_word_comma);
register_word(vm, "C,", dictionary_word_c_comma);
register_word(vm, "2,", dictionary_word_2comma);
register_word(vm, "PAD", dictionary_word_pad);
register_word(vm, "SP!", dictionary_word_sp_store);
register_word(vm, "SP@", dictionary_word_sp_fetch);
register_word(vm, "LATEST", dictionary_word_latest);
}
+485
View File
@@ -0,0 +1,485 @@
/*
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.
*/
/* double_words.c - FORTH-79 Double Number Words */
#include "include/double_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
/**
* @brief Convert single number to double ( n -- d )
*
* Converts a single-precision number to double-precision format.
* Result is pushed as ( dlow dhigh ) with high cell on top.
*
* @param vm Pointer to the virtual machine state
*/
void double_word_s_to_d(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
cell_t dhigh = (n < 0) ? -1 : 0;
vm_push(vm, n); // push low
vm_push(vm, dhigh); // push high
}
/* D+ ( d1 d2 -- d3 ) */
void double_word_d_plus(VM *vm) {
if (vm->dsp < 3) {
vm->error = 1;
return;
}
// Stack layout: d1low d1high d2low d2high (top)
// Pop from top: d2high, d2low, d1high, d1low
cell_t d2high = vm_pop(vm);
cell_t d2low = vm_pop(vm);
cell_t d1high = vm_pop(vm);
cell_t d1low = vm_pop(vm);
// Use unsigned arithmetic for carry detection. ucell_t (vm.h), not plain
// unsigned long -- unsigned long is only 32-bit on some bare-metal
// aarch64 cross-compile targets, silently truncating this addition and
// losing the sign-extension D. depends on (FABRIC-3.md §XII.4).
ucell_t ud1low = (ucell_t) d1low;
ucell_t ud2low = (ucell_t) d2low;
ucell_t uresult_low = ud1low + ud2low;
cell_t result_low = (cell_t) uresult_low;
cell_t carry = (uresult_low < ud1low) ? 1 : 0;
cell_t result_high = d1high + d2high + carry;
// Push result: low first, then high (so high is on top)
vm_push(vm, result_low);
vm_push(vm, result_high);
}
/* D- ( d1 d2 -- d3 ) */
void double_word_d_minus(VM *vm) {
if (vm->dsp < 3) {
vm->error = 1;
return;
}
// Stack layout: d1low d1high d2low d2high (top)
// Pop from top: d2high, d2low, d1high, d1low
cell_t d2high = vm_pop(vm);
cell_t d2low = vm_pop(vm);
cell_t d1high = vm_pop(vm);
cell_t d1low = vm_pop(vm);
// Use unsigned arithmetic for borrow detection. ucell_t, not plain
// unsigned long -- see double_word_d_plus()'s matching comment.
ucell_t ud1low = (ucell_t) d1low;
ucell_t ud2low = (ucell_t) d2low;
ucell_t uresult_low = ud1low - ud2low;
cell_t result_low = (cell_t) uresult_low;
cell_t borrow = (ud1low < ud2low) ? 1 : 0;
cell_t result_high = d1high - d2high - borrow;
// Push result: low first, then high (so high is on top)
vm_push(vm, result_low);
vm_push(vm, result_high);
}
/* DNEGATE ( d1 -- d2 ) */
void double_word_dnegate(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
// Stack layout: dlow dhigh (top)
cell_t dhigh = vm_pop(vm);
cell_t dlow = vm_pop(vm);
// Negate: ~d + 1. ucell_t, not plain unsigned long -- see
// double_word_d_plus()'s matching comment.
ucell_t new_low = ~(ucell_t) dlow + 1;
cell_t new_dhigh = ~dhigh + (new_low == 0 ? 1 : 0);
vm_push(vm, (cell_t) new_low);
vm_push(vm, new_dhigh);
}
/* DABS ( d1 -- d2 ) */
void double_word_dabs(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
// High cell is on top of stack (dsp points to top)
cell_t dhigh = vm->data_stack[vm->dsp];
if (dhigh < 0) {
double_word_dnegate(vm);
}
}
/**
* @brief Compare two double numbers
*
* @param d1h High cell of first double number
* @param d1l Low cell of first double number
* @param d2h High cell of second double number
* @param d2l Low cell of second double number
* @return int -1 if d1<d2, 0 if d1=d2, 1 if d1>d2
*/
static int d_compare(cell_t d1h, cell_t d1l, cell_t d2h, cell_t d2l) {
if (d1h < d2h) return -1;
if (d1h > d2h) return 1;
// ucell_t, not plain unsigned long -- see double_word_d_plus()'s
// matching comment (unsigned long is only 32-bit on some bare-metal
// aarch64 cross-compile targets).
ucell_t ul1 = (ucell_t) d1l;
ucell_t ul2 = (ucell_t) d2l;
return (ul1 > ul2) - (ul1 < ul2);
}
/* DMAX ( d1 d2 -- d3 ) */
void double_word_dmax(VM *vm) {
if (vm->dsp < 3) {
vm->error = 1;
return;
}
cell_t d2high = vm_pop(vm);
cell_t d2low = vm_pop(vm);
cell_t d1high = vm_pop(vm);
cell_t d1low = vm_pop(vm);
if (d_compare(d1high, d1low, d2high, d2low) >= 0) {
vm_push(vm, d1low);
vm_push(vm, d1high);
} else {
vm_push(vm, d2low);
vm_push(vm, d2high);
}
}
/* DMIN ( d1 d2 -- d3 ) */
void double_word_dmin(VM *vm) {
if (vm->dsp < 3) {
vm->error = 1;
return;
}
cell_t d2high = vm_pop(vm);
cell_t d2low = vm_pop(vm);
cell_t d1high = vm_pop(vm);
cell_t d1low = vm_pop(vm);
if (d_compare(d1high, d1low, d2high, d2low) <= 0) {
vm_push(vm, d1low);
vm_push(vm, d1high);
} else {
vm_push(vm, d2low);
vm_push(vm, d2high);
}
}
/* D< ( d1 d2 -- flag ) */
void double_word_d_less(VM *vm) {
if (vm->dsp < 3) {
vm->error = 1;
return;
}
cell_t d2high = vm_pop(vm);
cell_t d2low = vm_pop(vm);
cell_t d1high = vm_pop(vm);
cell_t d1low = vm_pop(vm);
int cmp = d_compare(d1high, d1low, d2high, d2low);
vm_push(vm, (cmp < 0) ? -1 : 0);
}
/* D= ( d1 d2 -- flag ) */
void double_word_d_equals(VM *vm) {
if (vm->dsp < 3) {
vm->error = 1;
return;
}
cell_t d2high = vm_pop(vm);
cell_t d2low = vm_pop(vm);
cell_t d1high = vm_pop(vm);
cell_t d1low = vm_pop(vm);
int cmp = d_compare(d1high, d1low, d2high, d2low);
vm_push(vm, (cmp == 0) ? -1 : 0);
}
/* 2DROP ( d -- ) */
void double_word_2drop(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
vm_pop(vm);
vm_pop(vm);
}
/* 2DUP ( d -- d d ) */
void double_word_2dup(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
// Stack: dlow dhigh (top)
cell_t dhigh = vm->data_stack[vm->dsp];
cell_t dlow = vm->data_stack[vm->dsp - 1];
vm_push(vm, dlow);
vm_push(vm, dhigh);
}
/* 2SWAP ( d1 d2 -- d2 d1 ) */
void double_word_2swap(VM *vm) {
if (vm->dsp < 3) {
vm->error = 1;
return;
}
cell_t d2high = vm_pop(vm);
cell_t d2low = vm_pop(vm);
cell_t d1high = vm_pop(vm);
cell_t d1low = vm_pop(vm);
vm_push(vm, d2low);
vm_push(vm, d2high);
vm_push(vm, d1low);
vm_push(vm, d1high);
}
/* 2OVER ( d1 d2 -- d1 d2 d1 ) */
void double_word_2over(VM *vm) {
if (vm->dsp < 3) {
vm->error = 1;
return;
}
// Stack: d1low d1high d2low d2high (top)
// Indices: dsp-3 dsp-2 dsp-1 dsp
cell_t d1low = vm->data_stack[vm->dsp - 3];
cell_t d1high = vm->data_stack[vm->dsp - 2];
vm_push(vm, d1low);
vm_push(vm, d1high);
}
/* 2ROT ( d1 d2 d3 -- d2 d3 d1 ) */
void double_word_2rot(VM *vm) {
if (vm->dsp < 5) {
vm->error = 1;
return;
}
cell_t d3high = vm_pop(vm);
cell_t d3low = vm_pop(vm);
cell_t d2high = vm_pop(vm);
cell_t d2low = vm_pop(vm);
cell_t d1high = vm_pop(vm);
cell_t d1low = vm_pop(vm);
vm_push(vm, d2low);
vm_push(vm, d2high);
vm_push(vm, d3low);
vm_push(vm, d3high);
vm_push(vm, d1low);
vm_push(vm, d1high);
}
/* 2>R ( d -- ) ( R: -- d ) */
void double_word_2to_r(VM *vm) {
if (vm->dsp < 1) { vm->error = 1; return; }
cell_t dhigh = vm_pop(vm);
cell_t dlow = vm_pop(vm);
if (vm->ecw_nesting > 0) {
/* Inside execute_colon_word: RS[rsp]=resume_ip; insert dlow,dhigh below it. */
if (vm->rsp + 2 >= STACK_SIZE) { vm->error = 1; return; }
cell_t resume_ip = vm->return_stack[vm->rsp];
vm->return_stack[vm->rsp] = dlow;
vm->return_stack[vm->rsp + 1] = dhigh;
vm->return_stack[vm->rsp + 2] = resume_ip;
vm->rsp += 2;
} else {
if (vm->rsp + 2 >= STACK_SIZE) { vm->error = 1; return; }
vm->return_stack[++vm->rsp] = dlow;
vm->return_stack[++vm->rsp] = dhigh;
}
}
/* 2R> ( -- d ) ( R: d -- ) */
void double_word_2r_from(VM *vm) {
if (vm->ecw_nesting > 0) {
/* Inside execute_colon_word: RS[rsp]=resume_ip, [rsp-1]=dhigh, [rsp-2]=dlow. */
if (vm->rsp < 2) { vm->error = 1; return; }
cell_t resume_ip = vm->return_stack[vm->rsp];
cell_t dhigh = vm->return_stack[vm->rsp - 1];
cell_t dlow = vm->return_stack[vm->rsp - 2];
vm->return_stack[vm->rsp - 2] = resume_ip;
vm->rsp -= 2;
vm_push(vm, dlow);
vm_push(vm, dhigh);
} else {
if (vm->rsp < 1) { vm->error = 1; return; }
cell_t dhigh = vm->return_stack[vm->rsp--];
cell_t dlow = vm->return_stack[vm->rsp--];
vm_push(vm, dlow);
vm_push(vm, dhigh);
}
}
/* 2R@ ( -- d ) ( R: d -- d ) */
void double_word_2r_fetch(VM *vm) {
if (vm->ecw_nesting > 0) {
/* Inside execute_colon_word: RS[rsp]=resume_ip, [rsp-1]=dhigh, [rsp-2]=dlow. */
if (vm->rsp < 2) { vm->error = 1; return; }
vm_push(vm, vm->return_stack[vm->rsp - 2]);
vm_push(vm, vm->return_stack[vm->rsp - 1]);
} else {
if (vm->rsp < 1) { vm->error = 1; return; }
vm_push(vm, vm->return_stack[vm->rsp - 1]);
vm_push(vm, vm->return_stack[vm->rsp]);
}
}
/* D0= ( d -- flag ) */
void double_word_d_zero_equals(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t dhigh = vm_pop(vm);
cell_t dlow = vm_pop(vm);
vm_push(vm, (dhigh == 0 && dlow == 0) ? -1 : 0);
}
/* D0< ( d -- flag ) */
void double_word_d_zero_less(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t dhigh = vm_pop(vm);
vm_pop(vm); // dlow (unused, but must pop)
vm_push(vm, (dhigh < 0) ? -1 : 0);
}
/* D2* ( d1 -- d2 ) */
void double_word_d_two_star(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t dhigh = vm_pop(vm);
cell_t dlow = vm_pop(vm);
// Left shift: new_low = dlow << 1, new_high = (dhigh << 1) | (carry from dlow)
unsigned long long udlow = (unsigned long long)(uint64_t) dlow;
cell_t new_dlow = (cell_t)(udlow << 1);
cell_t new_dhigh = (dhigh << 1) | ((udlow >> 63) & 1);
vm_push(vm, new_dlow);
vm_push(vm, new_dhigh);
}
/* D2/ ( d1 -- d2 ) */
void double_word_d_two_slash(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t dhigh = vm_pop(vm);
cell_t dlow = vm_pop(vm);
// Arithmetic right shift: preserve sign in high cell
cell_t new_dhigh = dhigh >> 1;
unsigned long long udlow = (unsigned long long)(uint64_t) dlow;
cell_t new_dlow = (cell_t)((udlow >> 1) | ((dhigh & 1) ? (1ULL << 63) : 0));
vm_push(vm, new_dlow);
vm_push(vm, new_dhigh);
}
/**
* @brief Register all double number words
*
* Registers all double-precision number operations with the virtual machine.
*
* @param vm Pointer to the virtual machine state
*
* @note This function must be called during VM initialization.
*/
void register_double_words(VM *vm) {
register_word(vm, "S>D", double_word_s_to_d);
register_word(vm, "D+", double_word_d_plus);
register_word(vm, "D-", double_word_d_minus);
register_word(vm, "DNEGATE", double_word_dnegate);
register_word(vm, "DABS", double_word_dabs);
register_word(vm, "DMAX", double_word_dmax);
register_word(vm, "DMIN", double_word_dmin);
register_word(vm, "D<", double_word_d_less);
register_word(vm, "D=", double_word_d_equals);
register_word(vm, "2DROP", double_word_2drop);
register_word(vm, "2DUP", double_word_2dup);
register_word(vm, "2SWAP", double_word_2swap);
register_word(vm, "2OVER", double_word_2over);
register_word(vm, "2ROT", double_word_2rot);
register_word(vm, "2>R", double_word_2to_r);
register_word(vm, "2R>", double_word_2r_from);
register_word(vm, "2R@", double_word_2r_fetch);
register_word(vm, "D0=", double_word_d_zero_equals);
register_word(vm, "D0<", double_word_d_zero_less);
register_word(vm, "D2*", double_word_d_two_star);
register_word(vm, "D2/", double_word_d_two_slash);
}
+344
View File
@@ -0,0 +1,344 @@
/*
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.
*/
#include "include/editor_words.h" /* register_editor_words prototype */
#include "include/block_words.h" /* mark_buffer_dirty(), MAX_BLOCKS, BLOCK_SIZE */
#include "../../include/vm.h" /* VM, vm_load_cell, vm_store_cell, vm_addr_ok, vm_ptr */
#include "../../include/word_registry.h" /* register_word */
#include "../../include/log.h" /* log_message */
#include <stdio.h> /* fputs, fflush, puts, printf */
#include <string.h> /* memset, memcpy, strlen, isdigit, isspace */
#include <ctype.h> /* isdigit, isspace */
/* -------- helpers -------- */
/** @brief Get current screen number from VM
* @param vm Pointer to VM instance
* @return Current screen number
*/
static inline cell_t current_scr(VM *vm) {
return vm_load_cell(vm, vm->scr_addr);
}
/** @brief Get pointer to specific line in a screen
* @param vm Pointer to VM instance
* @param scr Screen number (1-based)
* @param line Line number (0-15)
* @param out Output parameter for line pointer
* @return 1 if successful, 0 if parameters invalid
*/
static int line_ptr(VM *vm, cell_t scr, cell_t line, unsigned char **out) {
if (!vm || !out) return 0;
if (scr < 1 || scr >= (cell_t) MAX_BLOCKS) return 0;
if (line < 0 || line > 15) return 0;
/* Use block subsystem to get block buffer */
extern uint8_t *blk_get_buffer(uint32_t block_num, int writable);
extern int blk_is_valid(uint32_t block_num);
if (!blk_is_valid((uint32_t) scr)) return 0;
uint8_t *block = blk_get_buffer((uint32_t) scr, 1); /* writable for editing */
if (!block) return 0;
*out = block + (line * 64);
return 1;
}
/** @brief Print 64-character line with character filtering
* @param p Pointer to 64-byte line buffer
*/
static void print_line_64(const unsigned char *p) {
#ifdef STARFORTH_ANSI
/* No cursor moves unless asked; just render content cleanly. */
#endif
for (int i = 0; i < 64; ++i) {
unsigned char c = p[i];
if (c == 0) { putchar(' '); } else if (c >= 32 && c <= 126) { putchar((int) c); } else {
putchar('.');
}
}
putchar('\n');
}
/* -------- words -------- */
/* L ( u -- ) : print line u of current SCR (0..15). Errors on empty/range/invalid SCR. */
static void editor_word_l(VM *vm) {
if (!vm) return;
if (vm->dsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "L: stack underflow (needs line# 0..15)");
return;
}
cell_t line = vm_pop(vm);
if (line < 0 || line > 15) {
vm->error = 1;
log_message(LOG_ERROR, "L: line out of range (0..15)");
return;
}
cell_t scr = current_scr(vm);
if (scr < 1 || scr >= (cell_t) MAX_BLOCKS) {
vm->error = 1;
log_message(LOG_ERROR, "L: SCR out of range (SCR=%ld)", (long) scr);
return;
}
unsigned char *lp = NULL;
if (!line_ptr(vm, scr, line, &lp)) {
vm->error = 1;
log_message(LOG_ERROR, "L: address invalid for SCR=%ld line=%ld", (long) scr, (long) line);
return;
}
print_line_64(lp);
}
/* S ( c-addr len u -- ) : set line u from buffer (pads with spaces to 64, truncates if longer). */
static void editor_word_s(VM *vm) {
if (!vm) return;
/* Pop in the order that lets us validate line range before address checks (useful in tests). */
if (vm->dsp < 2) {
vm->error = 1;
log_message(LOG_ERROR, "S: stack underflow (needs c-addr len line#)");
return;
}
cell_t line = vm_pop(vm);
if (line < 0 || line > 15) {
vm->error = 1;
log_message(LOG_ERROR, "S: line out of range (0..15)");
/* Drain remaining args to keep stack sane */
(void) vm_pop(vm);
(void) vm_pop(vm);
return;
}
cell_t len = vm_pop(vm);
cell_t addr = vm_pop(vm);
if (len < 0) len = 0; /* defensive: negative length treated as 0 */
cell_t scr = current_scr(vm);
if (scr < 1 || scr >= (cell_t) MAX_BLOCKS) {
vm->error = 1;
log_message(LOG_ERROR, "S: SCR out of range (SCR=%ld)", (long) scr);
return;
}
unsigned char *dst = NULL;
if (!line_ptr(vm, scr, line, &dst)) {
vm->error = 1;
log_message(LOG_ERROR, "S: destination address invalid");
return;
}
/* Source pointer */
if (!vm_addr_ok(vm, (vaddr_t) addr, (size_t) len)) {
vm->error = 1;
log_message(LOG_ERROR, "S: source range invalid (addr=%ld len=%ld)",
(long) addr, (long) len);
return;
}
const unsigned char *src = (const unsigned char *) vm_ptr(vm, (vaddr_t) addr);
/* Write: pad spaces, then copy up to 64 */
memset(dst, ' ', 64);
size_t n = (len > 64) ? 64 : (size_t) len;
if (n > 0) memcpy(dst, src, n);
mark_buffer_dirty(vm);
}
/* SHOW ( -- ) : print the whole 16x64 screen with simple line numbers. */
static void editor_word_show(VM *vm) {
if (!vm) return;
cell_t scr = current_scr(vm);
if (scr < 1 || scr >= (cell_t) MAX_BLOCKS) {
vm->error = 1;
log_message(LOG_ERROR, "SHOW: SCR out of range (SCR=%ld)", (long) scr);
return;
}
#ifdef STARFORTH_ANSI
/* Clear + home (optional; compile-time opt-in) */
fputs("\x1b[2J\x1b[H", stdout);
printf("\x1b[1mScreen %ld\x1b[0m:\n", (long) scr);
#else
printf("Screen %ld:\n", (long) scr);
#endif
for (cell_t line = 0; line < 16; ++line) {
unsigned char *lp = NULL;
if (!line_ptr(vm, scr, line, &lp)) {
vm->error = 1;
return;
}
#ifdef STARFORTH_ANSI
printf("\x1b[90m%2ld:\x1b[0m ", (long) line);
#else
printf("%2ld: ", (long) line);
#endif
print_line_64(lp);
}
}
/* EDIT ( u -- ) : tiny line-editor shell (stdin/stdout). No raw mode, no curses. */
/* EDIT ( u -- ) : tiny line-editor shell (stdin/stdout). No raw mode, no curses. */
static void editor_word_edit(VM *vm) {
if (!vm) return;
if (vm->dsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "EDIT: stack underflow (needs block#)");
return;
}
cell_t blk = vm_pop(vm);
if (blk < 1 || blk >= (cell_t) MAX_BLOCKS) {
vm->error = 1;
log_message(LOG_ERROR, "EDIT: block out of range");
return;
}
vm_store_cell(vm, vm->scr_addr, blk);
editor_word_show(vm);
if (vm->error) return;
char buf[1024];
for (;;) {
printf("SCR %ld> ", (long) vm_load_cell(vm, vm->scr_addr));
fflush(stdout);
if (!fgets(buf, sizeof buf, stdin)) break;
/* Trim trailing newline */
size_t L = strlen(buf);
if (L && buf[L - 1] == '\n') buf[L - 1] = '\0';
if (buf[0] == 'q' || buf[0] == 'Q') break;
else if (buf[0] == 'h' || buf[0] == 'H') {
puts("Commands: p=print, l <n>=line, s <n> <text>=set, n=next, b=prev, w=write, q=quit");
} else if (buf[0] == 'p' || buf[0] == 'P') {
editor_word_show(vm);
} else if (buf[0] == 'l' || buf[0] == 'L') {
long ln = -1;
/* Simple number parsing to avoid sscanf */
const char *p = buf + 1;
while (*p == ' ' || *p == '\t') p++;
if (isdigit(*p)) {
ln = 0;
while (isdigit(*p)) {
ln = ln * 10 + (*p - '0');
p++;
}
vm_push(vm, (cell_t) ln);
editor_word_l(vm);
} else {
puts("usage: l <0..15>");
}
} else if (buf[0] == 's' || buf[0] == 'S') {
long ln = -1;
const char *p = buf + 1;
while (*p && isspace((unsigned char) *p)) ++p;
/* Parse number manually */
if (isdigit(*p)) {
ln = 0;
while (isdigit(*p)) {
ln = ln * 10 + (*p - '0');
p++;
}
/* Move past any remaining spaces */
while (*p && isspace((unsigned char) *p)) ++p;
/* p is now the text to write */
size_t tlen = strlen(p);
cell_t scr = vm_load_cell(vm, vm->scr_addr);
unsigned char *dst = NULL;
if (ln < 0 || ln > 15 || scr < 1 || scr >= (cell_t) MAX_BLOCKS ||
!line_ptr(vm, scr, (cell_t) ln, &dst)) {
puts("error: bad SCR/line");
} else {
memset(dst, ' ', 64);
size_t n = (tlen > 64) ? 64 : tlen;
if (n) memcpy(dst, p, n);
/* ✅ Use helpers declared in block_words.h (no implicit decl) */
mark_buffer_dirty(vm);
puts("ok");
}
} else {
puts("usage: s <0..15> <text>");
}
} else if (buf[0] == 'n' || buf[0] == 'N') {
cell_t s = vm_load_cell(vm, vm->scr_addr);
if (s + 1 < (cell_t) MAX_BLOCKS) vm_store_cell(vm, vm->scr_addr, s + 1);
editor_word_show(vm);
} else if (buf[0] == 'b' || buf[0] == 'B') {
cell_t s = vm_load_cell(vm, vm->scr_addr);
if (s - 1 >= 1) vm_store_cell(vm, vm->scr_addr, s - 1);
editor_word_show(vm);
} else if (buf[0] == 'w' || buf[0] == 'W') {
/* ✅ No more implicit decls: use helpers */
mark_buffer_dirty(vm);
save_all_buffers(vm);
puts("saved");
} else {
puts("h for help");
}
if (vm->error) {
puts("error");
vm->error = 0; /* keep shell alive */
}
}
}
/* -------- registration -------- */
void register_editor_words(VM *vm) {
if (!vm) return;
register_word(vm, "L", editor_word_l); /* non-79 */
register_word(vm, "S", editor_word_s); /* non-79 */
register_word(vm, "SHOW", editor_word_show); /* non-79 */
register_word(vm, "EDIT", editor_word_edit); /* non-79 */
}
+480
View File
@@ -0,0 +1,480 @@
/*
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.
*/
#include "include/format_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
#include "../../include/vm.h"
#include <stdint.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
/* Pictured-number hold buffer lives in each VM's own memory (hold_addr / hold_pos).
* These macros provide the same interface as the old static globals but are per-VM,
* so child VMs never alias Hera's buffer. */
#define PN_CAP 64
#define PN_BUF(vm) ((unsigned char *)((vm)->memory + (vm)->hold_addr))
#define CELL_BITS ((int)(sizeof(cell_t) * CHAR_BIT))
#define CHUNK_BITS 16
#define CHUNK_MASK ((uint64_t)(UINT64_C(1) << CHUNK_BITS) - 1)
/** @brief Returns current numeric conversion base, defaulting to 10 if invalid */
static unsigned current_base(VM *vm) {
unsigned b = (unsigned) vm->base;
return (b < 2 || b > 36) ? 10u : b;
}
/** @brief Converts value 0-35 to character '0'-'9' or 'A'-'Z' */
static char digit_for(unsigned v) {
return (v < 10) ? (char) ('0' + v) : (char) ('A' + (v - 10));
}
/* Divide unsigned double-cell (dhigh:dlow) by base (2..36), portable C99 */
/** @brief Divides unsigned double-cell number by base using portable C99 code */
static void div_ud_by_base(cell_t dhigh_in, cell_t dlow_in, unsigned base,
cell_t *qhigh_out, cell_t *qlow_out, unsigned *rem_out) {
uint64_t base_ul = (uint64_t) base;
uint64_t r = 0;
uint64_t qh = 0, ql = 0;
/* High word */
{
uint64_t hi = (uint64_t)(ucell_t) dhigh_in;
int parts = CELL_BITS / CHUNK_BITS;
for (int i = parts - 1; i >= 0; --i) {
uint64_t chunk = (hi >> (i * CHUNK_BITS)) & CHUNK_MASK;
uint64_t val = (r << CHUNK_BITS) + chunk;
uint64_t qchunk = val / base_ul;
r = val % base_ul;
qh = (qh << CHUNK_BITS) | (qchunk & CHUNK_MASK);
}
}
/* Low word */
{
uint64_t lo = (uint64_t)(ucell_t) dlow_in;
int parts = CELL_BITS / CHUNK_BITS;
for (int i = parts - 1; i >= 0; --i) {
uint64_t chunk = (lo >> (i * CHUNK_BITS)) & CHUNK_MASK;
uint64_t val = (r << CHUNK_BITS) + chunk;
uint64_t qchunk = val / base_ul;
r = val % base_ul;
ql = (ql << CHUNK_BITS) | (qchunk & CHUNK_MASK);
}
}
*qhigh_out = (cell_t) qh;
*qlow_out = (cell_t) ql;
*rem_out = (unsigned) r;
}
/* Render one cell in current base (no printf %x/%o so input==output base) */
/** @brief Prints number with optional width field and sign handling */
static void print_number_formatted(VM *vm, cell_t n, int width, int is_unsigned) {
char buf[80];
int i = 0;
unsigned base = current_base(vm);
if (!is_unsigned && n == 0) {
buf[i++] = '0';
} else if (is_unsigned) {
uint64_t u = (uint64_t)(ucell_t) n;
if (u == 0) buf[i++] = '0';
while (u != 0) {
uint64_t rem = u % (uint64_t) base;
u /= (uint64_t) base;
buf[i++] = digit_for((unsigned) rem);
}
} else {
int neg = (n < 0);
uint64_t u = (uint64_t)(ucell_t) n;
if (neg) u = (uint64_t) 0 - u;
if (u == 0) buf[i++] = '0';
while (u != 0) {
uint64_t rem = u % (uint64_t) base;
u /= (uint64_t) base;
buf[i++] = digit_for((unsigned) rem);
}
if (neg) buf[i++] = '-';
}
int len = i;
if (width > 0 && len < width) for (int s = 0; s < width - len; ++s) putchar(' ');
while (i--) putchar(buf[i]);
}
/* ===== Words ===== */
/* BASE ( -- addr ) — returns VM address (offset) of the BASE variable */
void format_word_base(VM *vm) {
if (!vm) { return; }
/* vm->base_addr is a VM address (offset into vm->memory), not a host pointer */
vm_push(vm, CELL(vm->base_addr));
}
/* DECIMAL ( -- ) — set BASE=10 */
void format_word_decimal(VM *vm) {
if (!vm) { return; }
vm_set_base(vm, 10);
}
/* HEX ( -- ) — set BASE=16 */
void format_word_hex(VM *vm) {
if (!vm) { return; }
vm_set_base(vm, 16);
}
/* OCTAL ( -- ) — set BASE=8 */
void format_word_octal(VM *vm) {
if (!vm) { return; }
vm_set_base(vm, 8);
}
/* <# ( -- ) */
void format_word_begin_conversion(VM *vm) {
vm->hold_pos = 0;
memset(PN_BUF(vm), 0, PN_CAP);
}
/* HOLD ( c -- ) */
void format_word_hold(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "HOLD: data stack underflow");
return;
}
cell_t c = vm_pop(vm);
/* Must be a single byte 0..255 */
if (c < 0 || c > 255) {
vm->error = 1;
log_message(LOG_ERROR, "HOLD: byte out of range (%ld)", (long) c);
return;
}
/* keep 1 byte of headroom (null safety) */
if (vm->hold_pos >= PN_CAP - 1) {
vm->error = 1;
log_message(LOG_ERROR, "HOLD: conversion buffer full (pos=%d cap=%d)", vm->hold_pos, PN_CAP);
return;
}
/* Prepend the byte */
memmove(&PN_BUF(vm)[1], &PN_BUF(vm)[0], (size_t) vm->hold_pos);
PN_BUF(vm)[0] = (unsigned char) c;
vm->hold_pos++;
}
/* SIGN ( n -- ) */
void format_word_sign(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
if (n < 0) {
vm_push(vm, '-');
format_word_hold(vm);
}
}
/* # ( ud1 | n -- ud2 )
Tolerant form:
- If only one cell is present, treat it as a signed single and convert its magnitude.
- SIGN (if used) is responsible for inserting '-' afterwards. */
void format_word_hash(VM *vm) {
cell_t dlow, dhigh;
/* If we don't have a full double, but have one cell, promote it. */
if (vm->dsp < 1) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
unsigned long u = (n < 0) ? (unsigned long) (-(cell_t) n) : (unsigned long) n;
dhigh = 0;
dlow = (cell_t) u;
} else {
/* Normal path: consume unsigned double (dlow dhigh) */
dlow = vm_pop(vm);
dhigh = vm_pop(vm);
}
unsigned base = current_base(vm);
cell_t qh, ql;
unsigned rem;
div_ud_by_base(dhigh, dlow, base, &qh, &ql, &rem);
/* Emit rightmost digit via HOLD */
vm_push(vm, (cell_t) digit_for(rem));
format_word_hold(vm);
if (vm->error) return;
/* Push quotient back as (dhigh dlow) so #S can loop */
vm_push(vm, qh);
vm_push(vm, ql);
}
/* #S ( ud | n -- 0 0 ) — tolerant:
If only one cell is present, treat it as a signed single and convert its magnitude.
SIGN (if used) is responsible for inserting '-' later. */
void format_word_hash_s(VM *vm) {
/* If we don't have a full double, but have one cell, promote it. */
if (vm->dsp < 1) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
/* Promote single to (dhigh=0, dlow=abs(n)) so # works on magnitude */
cell_t n = vm_pop(vm);
unsigned long u = (n < 0) ? (unsigned long) (-(cell_t) n) : (unsigned long) n;
/* Stack order for format_word_hash: top must be dlow, under it dhigh */
vm_push(vm, (cell_t) 0); /* dhigh */
vm_push(vm, (cell_t) u); /* dlow */
}
for (;;) {
/* Always convert at least once so 0 produces "0". */
format_word_hash(vm);
if (vm->error) return;
/* Peek the quotient (dhigh dlow) that '#' just pushed back */
cell_t dlow = vm->data_stack[vm->dsp];
cell_t dhigh = vm->data_stack[vm->dsp - 1];
if (dhigh == 0 && dlow == 0) break;
}
}
/* #> ( [ud] -- addr u ) — tolerant: pops ud iff present, else just returns buffer */
void format_word_end_conversion(VM *vm) {
/* If there’s at least a double on the stack, drop it (dlow dhigh). */
if (vm->dsp >= 1) {
(void) vm_pop(vm);
(void) vm_pop(vm);
}
/* Push VM address (offset) of the hold buffer and its fill count */
vm_push(vm, CELL(vm->hold_addr));
vm_push(vm, (cell_t) vm->hold_pos);
}
/* . ( n -- ) */
void format_word_dot(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
print_number_formatted(vm, n, 0, 0);
putchar(' ');
}
/* .R ( n width -- ) */
void format_word_dot_r(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t width = vm_pop(vm);
cell_t n = vm_pop(vm);
print_number_formatted(vm, n, (int) width, 0);
putchar(' ');
}
/* U. ( u -- ) */
void format_word_u_dot(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t u = vm_pop(vm);
print_number_formatted(vm, u, 0, 1);
putchar(' ');
}
/* U.R ( u width -- ) */
void format_word_u_dot_r(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t width = vm_pop(vm);
cell_t u = vm_pop(vm);
print_number_formatted(vm, u, (int) width, 1);
putchar(' ');
}
/* D. ( d -- ) */
void format_word_d_dot(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t dlow = vm_pop(vm);
cell_t dhigh = vm_pop(vm);
if ((dhigh == 0) || (dhigh == -1 && dlow < 0)) {
print_number_formatted(vm, dlow, 0, 0);
} else {
fputs("DOUBLE-OVERFLOW", stdout);
}
putchar(' ');
}
/* D.R ( d width -- ) */
void format_word_d_dot_r(VM *vm) {
if (vm->dsp < 2) {
vm->error = 1;
return;
}
cell_t width = vm_pop(vm);
cell_t dlow = vm_pop(vm);
cell_t dhigh = vm_pop(vm);
if ((dhigh == 0) || (dhigh == -1 && dlow < 0)) {
print_number_formatted(vm, dlow, (int) width, 0);
} else {
int len = (int) strlen("DOUBLE-OVERFLOW");
for (int i = 0; i < ((int) width > len ? (int) width - len : 0); ++i) putchar(' ');
fputs("DOUBLE-OVERFLOW", stdout);
}
putchar(' ');
}
/* .S ( -- ) */
void format_word_dot_s(VM *vm) {
printf("<%d> ", vm->dsp + 1);
for (int i = 0; i <= vm->dsp; i++) {
print_number_formatted(vm, vm->data_stack[i], 0, 0);
putchar(' ');
}
putchar('\n');
}
/* ? ( addr -- ) */
void format_word_question(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
vaddr_t addr = VM_ADDR(vm_pop(vm));
if (!vm_addr_ok(vm, addr, sizeof(cell_t))) {
vm->error = 1;
return;
}
print_number_formatted(vm, vm_load_cell(vm, addr), 0, 0);
putchar(' ');
}
/* DUMP ( addr u -- ) */
void format_word_dump(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t u = vm_pop(vm);
vaddr_t addr = VM_ADDR(vm_pop(vm));
if (u < 0) {
vm->error = 1;
return;
}
size_t n = (size_t) u;
if (!vm_addr_ok(vm, addr, n)) {
vm->error = 1;
return;
}
const uint8_t *ptr = vm_ptr(vm, addr);
int addr_width = (int) (sizeof(vaddr_t) * 2);
for (size_t i = 0; i < n; i += 16) {
unsigned long long a = (unsigned long long) (addr + i);
printf("%0*llX: ", addr_width, a);
size_t j = 0;
for (; j < 16 && i + j < n; j++) printf("%02X ", ptr[i + j]);
for (; j < 16; j++) printf(" ");
fputs(" |", stdout);
for (j = 0; j < 16 && i + j < n; j++) {
char c = (char) ptr[i + j];
putchar((c >= 32 && c <= 126) ? c : '.');
}
fputs("|\n", stdout);
}
}
/* Registration */
/**
* @brief Registers all formatting and numeric conversion words with the VM.
*
* This function registers the standard FORTH formatting words including:
* - Number output words (., .R, U., U.R, D., D.R, .S, ?)
* - Picture number formatting (<#, #, #S, #>, HOLD, SIGN)
* - Base conversion (BASE, DECIMAL, HEX, OCTAL)
* - Memory dump (DUMP)
*
* @param vm Pointer to the virtual machine instance
*/
void register_format_words(VM *vm) {
register_word(vm, ".", format_word_dot);
register_word(vm, ".R", format_word_dot_r);
register_word(vm, "U.", format_word_u_dot);
register_word(vm, "U.R", format_word_u_dot_r);
register_word(vm, "D.", format_word_d_dot);
register_word(vm, "D.R", format_word_d_dot_r);
register_word(vm, ".S", format_word_dot_s);
register_word(vm, "?", format_word_question);
register_word(vm, "DUMP", format_word_dump);
register_word(vm, "<#", format_word_begin_conversion);
register_word(vm, "#", format_word_hash);
register_word(vm, "#S", format_word_hash_s);
register_word(vm, "#>", format_word_end_conversion);
register_word(vm, "HOLD", format_word_hold);
register_word(vm, "SIGN", format_word_sign);
register_word(vm, "BASE", format_word_base);
register_word(vm, "DECIMAL", format_word_decimal);
register_word(vm, "HEX", format_word_hex);
register_word(vm, "OCTAL", format_word_octal);
}
+65
View File
@@ -0,0 +1,65 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/* framebuffer_words.c — raw framebuffer hardware-boundary FORTH words
* (FABRIC-0.md item 4.3.3). Kernel-only; no-op on hosted builds. */
#include "include/framebuffer_words.h"
#include "../../include/log.h"
#include "../../include/word_registry.h"
#ifdef __STARKERNEL__
#include "starkernel/framebuffer.h"
#endif
/* PLOT ( x y color -- ) : raw raster pixel write, top-left origin, Y-down.
* No Cartesian awareness -- that is capsules/fabric.4th's job. */
static void fbw_plot(VM *vm)
{
if (vm->dsp < 2) {
log_message(LOG_ERROR, "PLOT: Stack underflow");
vm->error = 1;
return;
}
cell_t color = vm_pop(vm);
cell_t y = vm_pop(vm);
cell_t x = vm_pop(vm);
#ifdef __STARKERNEL__
fb_put_pixel((uint32_t)x, (uint32_t)y, (uint32_t)color);
#else
(void)x; (void)y; (void)color;
#endif
}
/* FB-WIDTH ( -- n ) */
static void fbw_width(VM *vm)
{
#ifdef __STARKERNEL__
vm_push(vm, (cell_t)fb_width());
#else
vm_push(vm, 0);
#endif
}
/* FB-HEIGHT ( -- n ) */
static void fbw_height(VM *vm)
{
#ifdef __STARKERNEL__
vm_push(vm, (cell_t)fb_height());
#else
vm_push(vm, 0);
#endif
}
void register_framebuffer_words(VM *vm)
{
register_word(vm, "PLOT", fbw_plot);
register_word(vm, "FB-WIDTH", fbw_width);
register_word(vm, "FB-HEIGHT", fbw_height);
}
+11
View File
@@ -0,0 +1,11 @@
# src/word_source/include/
Public headers for `src/word_source/`, one per `.c` file there (see
`src/word_source/README.md` for the category breakdown), plus
`control_words.h` and `mama_forth_words.h`:
- `control_words.h` — declarations for `control_words.c`'s `IF`/`THEN`/
`ELSE`/`BEGIN`/`DO`/`LOOP` family.
- `mama_forth_words.h` — declares the Mama VM word bindings implemented in
`src/starkernel/capsule/mama_forth_words.c` (kernel-only; the header
lives here for consistency with the rest of the word-category headers).
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include "vm.h"
/* Register all ACL C primitive words into the VM dictionary */
void register_acl_words(VM *vm);
/* Direct C entry point for birth inheritance (no stack manipulation needed) */
void acl_inherit_entry(DictEntry *src, DictEntry *dst);
@@ -0,0 +1,90 @@
/*
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 ARITHMETIC_WORDS_H
#define ARITHMETIC_WORDS_H
#include "vm.h"
/* FORTH-79 Arithmetic Words:
* + ( n1 n2 -- n3 ) Add n1 and n2
* - ( n1 n2 -- n3 ) Subtract n2 from n1
* * ( n1 n2 -- n3 ) Multiply n1 and n2
* / ( n1 n2 -- n3 ) Divide n1 by n2
* MOD ( n1 n2 -- n3 ) Remainder of n1/n2
* /MOD ( n1 n2 -- n3 n4 ) n3=remainder, n4=quotient of n1/n2
* NEGATE ( n1 -- n2 ) Negate n1
* ABS ( n1 -- n2 ) Absolute value of n1
* MIN ( n1 n2 -- n3 ) Minimum of n1 and n2
* MAX ( n1 n2 -- n3 ) Maximum of n1 and n2
* 1+ ( n1 -- n2 ) Add 1 to n1
* 1- ( n1 -- n2 ) Subtract 1 from n1
* 2* ( n1 -- n2 ) Multiply n1 by 2 (left shift)
* 2/ ( n1 -- n2 ) Divide n1 by 2 (right shift)
* STAR-SLASH ( n1 n2 n3 -- n4 ) Multiply n1*n2, divide by n3
*/
/**
* @brief Registers all FORTH-79 arithmetic words with the virtual machine
*
* Registers the following words:
* - + (add)
* - - (subtract)
* - * (multiply)
* - / (divide)
* - MOD (modulo)
* - /MOD (divide with remainder)
* - NEGATE
* - ABS (absolute)
* - MIN
* - MAX
* - 1+ (increment)
* - 1- (decrement)
* - 2* (multiply by 2)
* - 2/ (divide by 2)
* - STAR-SLASH (multiply then divide)
*
* @param vm Pointer to the Forth virtual machine instance
*/
void register_arithmetic_words(VM * vm);
#endif /* ARITHMETIC_WORDS_H */
+196
View File
@@ -0,0 +1,196 @@
/*
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 block_words.h
* @brief FORTH-79 Block and Buffer System Implementation
*
* This file implements the standard FORTH-79 block and buffer words:
* @li BLOCK ( u -- addr ) Get block buffer address
* @li BUFFER ( u -- addr ) Get empty buffer for block
* @li UPDATE ( -- ) Mark current block as modified
* @li SAVE-BUFFERS ( -- ) Save all modified blocks
* @li EMPTY-BUFFERS ( -- ) Mark all buffers as empty
* @li FLUSH ( -- ) Save buffers and empty them
* @li LIST ( u -- ) List block contents
* @li LOAD ( u -- ) Load and interpret block
* @li SCR ( -- addr ) Variable: screen/block being listed
* @li THRU ( u1 u2 -- ) Load blocks from u1 to u2
* @li --> or → ( -- ) Continue to next block
*/
#ifndef BLOCK_WORDS_H
#define BLOCK_WORDS_H
#include "vm.h"
/** @name Block System Configuration
* @{
*/
/** @brief Size of each block in bytes (1KB) */
#define BLOCK_SIZE 1024
/** @brief Maximum number of blocks (1024 blocks from 1MB memory) */
#define MAX_BLOCKS (VM_MEMORY_SIZE / BLOCK_SIZE)
/** @brief Number of block buffers in memory */
#define BUFFER_COUNT 4
/** @} */
/**
* @brief Block buffer types for different storage mechanisms
*/
typedef enum {
BUFFER_TYPE_MEMORY, /**< Direct memory access (current implementation) */
BUFFER_TYPE_HARDWARE, /**< Future: Hardware register blocks */
BUFFER_TYPE_STORAGE, /**< Future: Persistent storage blocks */
BUFFER_TYPE_NETWORK /**< Future: Network packet buffers */
} buffer_type_t;
/**
* @brief Block buffer structure maintaining buffer state
*/
typedef struct {
unsigned char data[BLOCK_SIZE]; /**< Block content buffer */
int block_number; /**< Currently loaded block number (-1 = empty) */
int dirty; /**< Modified flag for write-back */
int accessed; /**< LRU counter for buffer management */
buffer_type_t type; /**< Buffer type for storage mechanism */
uintptr_t hw_address; /**< Future: Hardware backing address */
} BlockBuffer;
/**
* @brief Registers all block and buffer words with the virtual machine
* @param vm Pointer to the Forth virtual machine instance
*/
void register_block_words(VM * vm);
/**
* @brief Initializes the block system
* @param vm Pointer to the Forth virtual machine instance
*/
void init_block_system(VM * vm);
/*
* @brief Gets a buffer containing the specified block
* @param vm Pointer to the Forth virtual machine instance
* @param block_num Block number to retrieve
* @return Pointer to the block buffer data
*/
unsigned char *get_block_buffer(VM *vm, int block_num);
/*
* @brief Gets an empty buffer for the specified block
* @param vm Pointer to the Forth virtual machine instance
* @param block_num Block number to allocate
* @return Pointer to the empty buffer data
*/
unsigned char *get_empty_buffer(VM *vm, int block_num);
/**
* @brief Marks the current buffer as modified
* @param vm Pointer to the Forth virtual machine instance
*/
void mark_buffer_dirty(VM * vm);
/*
* @brief Saves all modified buffers to storage
* @param vm Pointer to the Forth virtual machine instance
*/
void save_all_buffers(VM * vm);
/**
* @brief Sync every dirty VM block-window slot to the C-layer block cache,
* then flush the whole block subsystem to physical media --
* the complete SAVE-BUFFERS implementation (unlike
* save_all_buffers() above, which only calls blk_flush() and so
* misses any BUFFER-modified-but-not-yet-UPDATE'd content still
* sitting only in vm->memory's window). Cheap to call when nothing
* is dirty -- every check inside is a small fixed-size scan, no
* I/O happens unless something actually needs writing. Safe to
* call from outside word dispatch (e.g. a periodic idle-tick
* auto-flush); vm must be non-NULL.
* @param vm Pointer to the Forth virtual machine instance
*/
void blk_vm_flush_all(VM * vm);
/*
* @brief Marks all buffers as empty
* @param vm Pointer to the Forth virtual machine instance
*/
void empty_all_buffers(VM * vm);
/**
* @brief Discards vm->blk_vm_lbn[]/cbuf[]/dirty[] if the device chain has
* changed since last validated (see this function's own doc comment in
* block_words.c for why). Exposed (Phase 8 v3, 2026-09-23) so callers
* outside this file that read vm->blk_vm_lbn[] directly (MOVE/CMOVE's
* cross-device block-copy check, memory_words.c/string_words.c) go through
* the same invalidation contract blk_vm_find() already uses internally,
* rather than a second, divergent copy of this logic.
* @param vm Pointer to the Forth virtual machine instance
*/
void blk_vm_check_epoch(VM *vm);
/**
* @brief Does `addr` fall inside the block I/O window? Pure address
* arithmetic against the window's fixed compile-time base/size -- no VM
* state needed. Exposed (Phase 8 v3, 2026-09-23) for MOVE/CMOVE/CMOVE>'s
* cross-device block-copy check.
* @param addr VM offset to test
* @return slot index (0..BLK_VM_SLOTS-1) if inside the window, -1 otherwise
*/
int blk_vm_slot_for_addr(vaddr_t addr);
/**
* @brief True if addr1/addr2 are both block-window addresses currently
* loaded from two different physical devices (Phase 8 v3, 2026-09-23) --
* the check MOVE/CMOVE/CMOVE> call before copying to refuse a same-VM,
* cross-device raw block copy. See this function's own doc comment in
* block_words.c for exactly what is and isn't flagged.
* @param vm VM performing the copy
* @param addr1 first VM offset (source or destination, order doesn't matter)
* @param addr2 second VM offset
* @return 1 if this copy should be refused, 0 otherwise
*/
int blk_vm_is_cross_device_copy(VM *vm, vaddr_t addr1, vaddr_t addr2);
#endif /* BLOCK_WORDS_H */
+127
View File
@@ -0,0 +1,127 @@
/*
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 CONTROL_WORDS_H
#define CONTROL_WORDS_H
#include "vm.h"
/**
* @defgroup control_words FORTH-79 Control Flow Words
* @{
* @brief Standard FORTH-79 control flow words implementation
*
* @def IF ( flag -- )
* Conditional execution based on flag
*
* @def THEN ( -- )
* End of IF conditional block
*
* @def ELSE ( -- )
* Alternative execution path
*
* @def BEGIN ( -- )
* Start of indefinite loop
*
* @def UNTIL ( flag -- )
* End loop if flag is true
*
* @def AGAIN ( -- )
* End infinite loop
*
* @def WHILE ( flag -- )
* Test condition in BEGIN loop
*
* @def REPEAT ( -- )
* End of WHILE loop
*
* @def DO ( limit start -- )
* Start definite loop
*
* @def LOOP ( -- )
* End DO loop, increment by 1
*
* @def +LOOP ( n -- )
* End DO loop, increment by n
*
* @def I ( -- n )
* Get current loop index
*
* @def J ( -- n )
* Get outer loop index
*
* @def LEAVE ( -- )
* Exit current loop
*
* @def UNLOOP ( -- )
* Discard loop parameters
*
* @def EXIT ( -- )
* Exit current word
*
* @def ABORT ( -- )
* Abort execution
*
* @def QUIT ( -- )
* Return to interpreter
*
* @def EXECUTE ( xt -- )
* Execute word at address
*
* @def BRANCH ( -- )
* Unconditional branch
*
* @def 0BRANCH ( flag -- )
* Branch if flag is zero
*
* @def (LIT) ( -- n )
* Push inline literal
* @}
*/
/**
* @brief Registers all control flow words with the Forth virtual machine
* @param vm Pointer to the Forth virtual machine instance
*/
void register_control_words(VM * vm);
#endif /* CONTROL_WORDS_H */
+17
View File
@@ -0,0 +1,17 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
#ifndef DEFER_WORDS_H
#define DEFER_WORDS_H
#include "../../../include/vm.h"
void register_defer_words(VM *vm);
#endif /* DEFER_WORDS_H */
@@ -0,0 +1,77 @@
/*
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 DEFINING_WORDS_H
#define DEFINING_WORDS_H
#include "vm.h"
/**
* @defgroup DefiningWords FORTH-79 Defining Words
* @{
* @brief Standard FORTH-79 defining words
*
* @details These words are used for creating new dictionary entries and defining words:
* - : ( -- ) Start colon definition
* - ; ( -- ) End colon definition
* - CREATE ( -- ) Create dictionary entry
* - DOES> ( -- ) Define runtime behavior
* - <BUILDS ( -- ) Create word with <BUILDS/DOES>
* - CONSTANT ( n -- ) Define constant
* - VARIABLE ( -- ) Define variable
* - 2CONSTANT ( d -- ) Define double constant
* - 2VARIABLE ( -- ) Define double variable
* - USER ( n -- ) Define user variable
* - IMMEDIATE ( -- ) Make most recent word immediate
* - [COMPILE] ( -- ) Force compilation of immediate word
* - COMPILE ( -- ) Compile inline code
* - FORGET ( -- ) Remove word and all after it
* @}
*/
/*
* @brief Registers all FORTH-79 defining words with the virtual machine
* @param vm Pointer to the virtual machine instance
*/
void register_defining_words(VM * vm);
#endif /* DEFINING_WORDS_H */
@@ -0,0 +1,60 @@
/*
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.
*/
/*
* dictionary_heat_diagnostic_words.h
*
* FORTH interface for dictionary heat optimization diagnostics
*/
#ifndef DICTIONARY_HEAT_DIAGNOSTIC_WORDS_H
#define DICTIONARY_HEAT_DIAGNOSTIC_WORDS_H
#include "vm.h"
/**
* Register all dictionary heat diagnostic words with the VM
*/
void register_dictionary_heat_diagnostic_words(VM *vm);
#endif /* DICTIONARY_HEAT_DIAGNOSTIC_WORDS_H */
@@ -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.
*/
#ifndef DICTIONARY_MANIPULATION_WORDS_H
#define DICTIONARY_MANIPULATION_WORDS_H
#include "vm.h"
/**
* @defgroup dictionary_words FORTH-79 Dictionary Manipulation Words
* @{
* @brief Standard FORTH-79 words for dictionary manipulation
*
* @par [ ( -- )
* Enter interpretation mode. Switches the system to interpret incoming words.
*
* @par ] ( -- )
* Enter compilation mode. Switches the system to compile incoming words.
*
* @par STATE ( -- addr )
* Variable containing current compilation state. Returns address of STATE variable.
*
* @par SMUDGE ( -- )
* Toggle smudge bit of latest defined word. Used during word definition.
*
* @par >BODY ( xt -- addr )
* Convert execution token to parameter field address.
*
* @par >NAME ( xt -- addr )
* Convert execution token to name field address.
*
* @par NAME> ( addr -- xt )
* Convert name field address to execution token.
*
* @par >LINK ( addr -- addr )
* Get link field address from name field address.
*
* @par LINK> ( addr -- addr )
* Get next word's address from link field.
*
* @par CFA ( addr -- xt )
* Get code field address from name field address.
*
* @par LFA ( addr -- addr )
* Get link field address from name field address.
*
* @par NFA ( addr -- addr )
* Get name field address from any field address.
*
* @par PFA ( addr -- addr )
* Get parameter field address from name field address.
*
* @par TRAVERSE ( addr n -- addr )
* Move through name field by n characters.
*
* @par INTERPRET ( -- )
* Text interpreter - process input stream.
* @}
*/
/**
* @brief Register all dictionary manipulation words with the virtual machine
*
* @param vm Pointer to the virtual machine instance
*
* This function registers all FORTH-79 standard dictionary manipulation words
* with the provided virtual machine instance. These words are essential for
* dictionary management and word definition functionality.
*/
void register_dictionary_manipulation_words(VM * vm);
#endif /* DICTIONARY_MANIPULATION_WORDS_H */
@@ -0,0 +1,73 @@
/*
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_WORDS_H
#define DICTIONARY_WORDS_H
#include "vm.h"
/**
* @defgroup dictionary FORTH-79 Dictionary & Compilation Words
* @{
*
* @brief Dictionary and compilation words as defined in FORTH-79 Standard
*
* @word HERE ( -- addr ) Returns address of next free dictionary space
* @word ALLOT ( n -- ) Allocates n bytes in dictionary
* @word , ( n -- ) Compiles n into dictionary
* @word C, ( c -- ) Compiles character into dictionary
* @word 2, ( d -- ) Compiles double into dictionary
* @word PAD ( -- addr ) Returns address of temporary text buffer
* @word SP! ( addr -- ) Sets data stack pointer
* @word SP@ ( -- addr ) Gets data stack pointer
* @word LATEST ( -- addr ) Returns address of most recent definition
* @}
*/
/**
* @brief Registers all dictionary and compilation words with the Forth VM
*
* @param vm Pointer to the Forth virtual machine instance
*/
void register_dictionary_words(VM * vm);
#endif /* DICTIONARY_WORDS_H */
+78
View File
@@ -0,0 +1,78 @@
/*
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 DOUBLE_WORDS_H
#define DOUBLE_WORDS_H
#include "vm.h"
/* FORTH-79 Double Number Words:
* S>D ( n -- d ) Convert single to double
* D+ ( d1 d2 -- d3 ) Add double numbers
* D- ( d1 d2 -- d3 ) Subtract double numbers
* DNEGATE ( d1 -- d2 ) Negate double number
* DABS ( d1 -- d2 ) Absolute value of double
* DMAX ( d1 d2 -- d3 ) Maximum of two doubles
* DMIN ( d1 d2 -- d3 ) Minimum of two doubles
* D< ( d1 d2 -- flag ) Compare doubles: d1 < d2
* D= ( d1 d2 -- flag ) Compare doubles: d1 = d2
* 2DROP ( d -- ) Drop double from stack
* 2DUP ( d -- d d ) Duplicate double
* 2SWAP ( d1 d2 -- d2 d1 ) Swap two doubles
* 2OVER ( d1 d2 -- d1 d2 d1 ) Copy second double to top
* 2ROT ( d1 d2 d3 -- d2 d3 d1 ) Rotate three doubles
* 2>R ( d -- ) ( R: -- d ) Move double to return stack
* 2R> ( -- d ) ( R: d -- ) Move double from return stack
* 2R@ ( -- d ) ( R: d -- d ) Copy double from return stack
* D0= ( d -- flag ) Test if double is zero
* D0< ( d -- flag ) Test if double is negative
* D2* ( d1 -- d2 ) Multiply double by 2
* D2/ ( d1 -- d2 ) Divide double by 2
*/
/*
* @brief Registers all double precision number manipulation words with the Forth virtual machine
* @param vm Pointer to the Forth virtual machine instance
*/
void register_double_words(VM * vm);
#endif /* DOUBLE_WORDS_H */
+125
View File
@@ -0,0 +1,125 @@
/*
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 EDITOR_WORDS_H
#define EDITOR_WORDS_H
#include "vm.h"
/**
* @defgroup editor_words FORTH-79 Line Editor Words
* @{
*
* @brief Line editor commands implementation
*
* @fn void L ( -- )
* @brief List current screen
*
* @fn void T ( n -- )
* @brief Type line n of current screen
* @param[in] n Line number to type
*
* @fn void E ( n -- )
* @brief Edit line n of current screen
* @param[in] n Line number to edit
*
* @fn void S ( n addr -- )
* @brief Replace line n with string at addr
* @param[in] n Line number to replace
* @param[in] addr Address of replacement string
*
* @fn void I ( n -- )
* @brief Insert before line n
* @param[in] n Line number to insert before
*
* @fn void R ( n -- )
* @brief Replace line n
* @param[in] n Line number to replace
*
* @fn void D ( n -- )
* @brief Delete line n
* @param[in] n Line number to delete
*
* @fn void P ( n -- )
* @brief Position to line n
* @param[in] n Line number to position to
*
* @fn void COPY ( n1 n2 -- )
* @brief Copy line n1 to line n2
* @param[in] n1 Source line number
* @param[in] n2 Destination line number
*
* @fn void M ( n1 n2 -- )
* @brief Move line n1 to line n2
* @param[in] n1 Source line number
* @param[in] n2 Destination line number
*
* @fn void TILL ( c -- )
* @brief Search for character c
* @param[in] c Character to search for
*
* @fn void N ( -- )
* @brief Search for next occurrence
*
* @fn void WHERE ( -- )
* @brief Show current position
*
* @fn void TOP ( -- )
* @brief Go to top of screen
*
* @fn void BOTTOM ( -- )
* @brief Go to bottom of screen
*
* @fn void CLEAR ( -- )
* @brief Clear current screen
*
* @}
*/
/**
* @brief Register all editor words with the Forth virtual machine
*
* @param vm Pointer to the Forth virtual machine instance
*/
void register_editor_words(VM * vm);
#endif /* EDITOR_WORDS_H */
+76
View File
@@ -0,0 +1,76 @@
/*
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 FORMAT_WORDS_H
#define FORMAT_WORDS_H
#include "vm.h"
/* FORTH-79 Formatting & Conversion Words:
* . ( n -- ) Print signed number
* .R ( n width -- ) Print signed number right-justified
* U. ( u -- ) Print unsigned number
* U.R ( u width -- ) Print unsigned number right-justified
* D. ( d -- ) Print double number
* D.R ( d width -- ) Print double number right-justified
* .S ( -- ) Print stack contents
* ? ( addr -- ) Print value at address
* DUMP ( addr u -- ) Hex dump u bytes from addr
* <# ( -- ) Begin numeric conversion
* # ( ud1 -- ud2 ) Convert one digit
* #> ( ud -- addr u ) End numeric conversion
* #S ( ud -- 0 0 ) Convert all remaining digits
* HOLD ( c -- ) Insert character in conversion
* SIGN ( n -- ) Insert sign if negative
* BASE ( -- addr ) Variable containing number base
* DECIMAL ( -- ) Set base to 10
* HEX ( -- ) Set base to 16
* OCTAL ( -- ) Set base to 8
*/
/*
* @brief Registers all formatting and conversion words with the Forth virtual machine
* @param vm Pointer to the Forth virtual machine instance
*/
void register_format_words(VM * vm);
#endif /* FORMAT_WORDS_H */
@@ -0,0 +1,38 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
#ifndef FRAMEBUFFER_WORDS_H
#define FRAMEBUFFER_WORDS_H
#include "vm.h"
/**
* @defgroup framebuffer_words Framebuffer Words
* @{
*
* @brief Raw hardware-boundary FORTH words for the Console drawing fabric
* (FABRIC-0.md item 4.3.3). Deliberately raster-native -- no Cartesian
* awareness, no policy. That lives in capsules/fabric.4th instead, per the
* compose-in-FORTH-first rule.
*
* Kernel-only: no-op on hosted builds (no framebuffer exists there).
*
* @par PLOT ( x y color -- )
* Write one pixel at raster coordinates (top-left origin, Y-down).
* Out-of-bounds writes are silently ignored (inherited from fb_put_pixel).
*
* @par FB-WIDTH ( -- n )
* @par FB-HEIGHT ( -- n )
* Push the framebuffer's pixel width / height.
* @}
*/
void register_framebuffer_words(VM *vm);
#endif /* FRAMEBUFFER_WORDS_H */
@@ -0,0 +1,25 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/**
* inference_words.h — Module 26: SSM inference engine + Jacquard FORTH words
*/
#ifndef INFERENCE_WORDS_H
#define INFERENCE_WORDS_H
#include "vm.h"
/**
* Register all inference engine / Jacquard FORTH words with the VM.
* Module 26 in register_forth79_words().
*/
void register_inference_words(VM *vm);
#endif /* INFERENCE_WORDS_H */
+73
View File
@@ -0,0 +1,73 @@
/*
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_WORDS_H //!< Include guard
#define IO_WORDS_H //!< Include guard
#include "vm.h"
/* FORTH-79 I/O & Terminal Words:
* EMIT ( c -- ) Output character c
* CR ( -- ) Output carriage return
* KEY ( -- c ) Input character from keyboard
* ?TERMINAL ( -- flag ) True if key pressed
* TYPE ( addr u -- ) Output u characters from addr
* SPACE ( -- ) Output one space
* SPACES ( n -- ) Output n spaces
*/
/**
* @brief Registers all I/O related FORTH words with the virtual machine
* @param vm Pointer to the virtual machine instance
*
* Registers the following FORTH-79 I/O & Terminal Words:
* - EMIT ( c -- ) Output character c
* - CR ( -- ) Output carriage return
* - KEY ( -- c ) Input character from keyboard
* - ?TERMINAL ( -- flag ) True if key pressed
* - TYPE ( addr u -- ) Output u characters from addr
* - SPACE ( -- ) Output one space
* - SPACES ( n -- ) Output n spaces
*/
void register_io_words(VM * vm);
#endif /* IO_WORDS_H */
@@ -0,0 +1,78 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
#ifndef KEYBOARD_WORDS_H
#define KEYBOARD_WORDS_H
#include "vm.h"
/**
* @defgroup keyboard_words Keyboard Words
* @{
*
* @brief Raw hardware-boundary FORTH word for verifying the interrupt-driven
* keyboard path (FABRIC-0.md item 4.3.5). Deliberately a diagnostic peek at
* the raw scancode ring buffer -- no set-2 translation, no REPL wiring.
* Those belong to the later REPL keyboard-input work noted in FABRIC-0.md.
*
* Kernel-only, amd64-only today (i8042 is amd64 hardware); no-op elsewhere
* so dictionary parity across all three architectures is unaffected.
*
* @par KBD-SCAN ( -- c -1 | 0 )
* Pop one raw scancode off the interrupt-fed ring buffer. Pushes the
* scancode and -1 (true) if one was available, or just 0 (false) if the
* buffer was empty.
*
* @par KBD-DEBUG ( -- isr_count spurious_count )
* Standing diagnostic: count of real keyboard IRQs serviced and count of
* LAPIC spurious-vector interrupts, both since boot.
*
* @par VKBD-EVENT ( -- code value -1 | 0 )
* Items 4.3.5c (riscv64) / 4.3.5e (aarch64). Pop one decoded EV_KEY event
* (Linux input-event code/value pair) off the virtio-input interrupt-fed
* ring buffer. Kernel-only, riscv64/aarch64 (virtio-keyboard-pci); no-op
* on amd64 (i8042 hardware instead, see KBD-SCAN).
*
* @par VKBD-DEBUG ( -- isr_count )
* Standing diagnostic: count of virtio-input ISR invocations since boot.
*
* @par KEY-EVENT ( -- keycode pressed -1 | 0 )
* Item 4.3.5f convergence checkpoint. One shared event shape across all
* three architectures: keycode in the Linux input keycode namespace
* (identical numbering to amd64's raw XT Set-1 scancode for the
* non-extended key range, by historical design, not translated arbitrarily
* -- see the implementation's own doc comment), pressed is 1 or 0. This is
* the word the later REPL keyboard-input work (M8) is expected to build
* on; KBD-SCAN/VKBD-EVENT remain as lower-level per-device diagnostics.
*
* @par ALT+TAB ( -- )
* FABRIC-0.md item 4.4y-revised. Programmatic equivalent of the physical
* Alt+TAB graphics/text toggle -- calls the same state-machine transition
* (console_fb_toggle_graphics() / vt100_toggle_graphics()) the keyboard
* interception in repl.c uses, so there is exactly one place the toggle
* actually happens.
* @}
*/
void register_keyboard_words(VM *vm);
/**
* FABRIC-0.md item 4.4v: shared C-level poll, one converged event stream
* (Linux input keycode namespace, all three architectures) -- see
* KEY-EVENT's doc comment above for the full per-arch translation
* rationale. Used by both kbw_key_event() (the FORTH word) and the REPL
* keyboard bridge (repl.c), so there is exactly one implementation.
*
* @param keycode Written with the Linux input keycode on success.
* @param pressed Written with 1 (press/repeat) or 0 (release) on success.
* @return 1 if an event was available, 0 otherwise.
*/
int sk_key_event_poll(uint16_t *keycode, int *pressed);
#endif /* KEYBOARD_WORDS_H */
+62
View File
@@ -0,0 +1,62 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
#ifndef LOG_WORDS_H
#define LOG_WORDS_H
#include "vm.h"
/**
* @defgroup log_words Log Words
* @{
*
* @brief FORTH words for log-level control and structured string emission.
*
* @par Level constants ( -- n )
* Push the corresponding LogLevel integer onto the stack.
* LOG-ERROR ( -- 0 )
* LOG-WARN ( -- 1 )
* LOG-INFO ( -- 2 )
* LOG-TEST ( -- 3 )
* LOG-DEBUG ( -- 4 )
*
* @par LOG-LEVEL! ( n -- )
* Set the current log level to n. Values outside 0–4 clamp to LOG-DEBUG.
*
* @par LOG-LEVEL@ ( -- n )
* Push the current log level.
*
* @par LOG-INFO" ( "ccc<quote>" -- ) IMMEDIATE
* @par LOG-WARN" ( "ccc<quote>" -- ) IMMEDIATE
* @par LOG-ERROR" ( "ccc<quote>" -- ) IMMEDIATE
* @par LOG-TEST" ( "ccc<quote>" -- ) IMMEDIATE
* @par LOG-DEBUG" ( "ccc<quote>" -- ) IMMEDIATE
* Parse the string literal up to the closing double-quote and emit it
* via log_message() at the corresponding level. Works in both interpret
* and compile mode (like ."). The level filter is applied at runtime so
* DEBUG messages are free when the level is INFO.
*
* @par LOG-INFO-STR ( c-addr u -- )
* @par LOG-WARN-STR ( c-addr u -- )
* @par LOG-ERROR-STR ( c-addr u -- )
* @par LOG-TEST-STR ( c-addr u -- )
* @par LOG-DEBUG-STR ( c-addr u -- )
* Consume a counted string from the stack (as produced by S") and emit
* it via log_message() at the corresponding level.
*
* @par Runtime words (not for direct use)
* (do-log-error) (do-log-warn) (do-log-info)
* (do-log-test) (do-log-debug)
* Threaded-code runtime helpers compiled by the LOG-*" immediates.
* @}
*/
void register_log_words(VM *vm);
#endif /* LOG_WORDS_H */
@@ -0,0 +1,78 @@
/*
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 LOGICAL_WORDS_H
#define LOGICAL_WORDS_H
#include "vm.h"
/**
* @defgroup logical_ops FORTH-79 Logical Operations
* @{
*
* @brief FORTH-79 Standard logical and comparison operations
*
* @details Implementation of the following FORTH-79 words:
* - AND ( n1 n2 -- n3 ) Bitwise AND
* - OR ( n1 n2 -- n3 ) Bitwise OR
* - XOR ( n1 n2 -- n3 ) Bitwise XOR
* - NOT ( n1 -- n2 ) Bitwise NOT
* - 0= ( n -- flag ) True if n is zero
* - 0< ( n -- flag ) True if n is negative
* - 0> ( n -- flag ) True if n is positive
* - = ( n1 n2 -- flag ) True if n1 equals n2
* - < ( n1 n2 -- flag ) True if n1 < n2
* - > ( n1 n2 -- flag ) True if n1 > n2
* - U< ( u1 u2 -- flag ) True if u1 < u2 (unsigned)
* - U> ( u1 u2 -- flag ) True if u1 > u2 (unsigned)
* - WITHIN ( n low high -- flag ) True if low <= n < high
* @}
*/
/*
* @brief Registers all logical and comparison words with the Forth virtual machine
*
* @param vm Pointer to the Forth virtual machine instance
*/
void register_logical_words(VM * vm);
#endif /* LOGICAL_WORDS_H */
@@ -0,0 +1,185 @@
/*
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 mama_forth_words.h
* @brief Mama FORTH vocabulary words for kernel capsule system (M7.1)
*
* This header defines the Mama FORTH vocabulary - kernel-only words
* for the LithosAnanke capsule birth protocol.
*
* These words are ONLY available in __STARKERNEL__ builds.
* Hosted StarForth builds do not include capsule functionality.
*
* Mama FORTH Vocabulary:
* CAPSULE-COUNT ( -- n ) Number of capsules in directory
* CAPSULE@ ( idx -- desc ) Get descriptor address by index
* CAPSULE-HASH@ ( desc -- hash ) Get capsule content hash
* CAPSULE-FLAGS@ ( desc -- flags ) Get capsule flags
* CAPSULE-LEN@ ( desc -- len ) Get payload length
* CAPSULE-BIRTH ( hash -- vm-id ) Birth baby VM from (p) capsule
* CAPSULE-RUN ( hash -- ) Run (e) capsule on Mama
* MAMA-VM-ID ( -- 0 ) Mama's VM ID (always 0)
* VM-COUNT ( -- n ) Number of live VMs
*/
#ifndef MAMA_FORTH_WORDS_H
#define MAMA_FORTH_WORDS_H
#include "../../../include/vm.h"
#ifdef __STARKERNEL__
/**
* @brief Register Mama FORTH vocabulary words with the VM
*
* Creates the MAMA vocabulary and registers all capsule-related words.
* Only available in kernel builds.
*
* @param vm Pointer to the VM instance
*/
void register_mama_forth_words(VM *vm);
/* Individual word implementations (kernel-only) */
/**
* @brief CAPSULE-COUNT ( -- n )
* Push number of capsules in the capsule directory.
*/
void mama_word_capsule_count(VM *vm);
/**
* @brief CAPSULE@ ( idx -- desc )
* Get capsule descriptor address by index.
*/
void mama_word_capsule_fetch(VM *vm);
/**
* @brief CAPSULE-HASH@ ( desc -- hash )
* Get content hash from capsule descriptor.
*/
void mama_word_capsule_hash_fetch(VM *vm);
/**
* @brief CAPSULE-FLAGS@ ( desc -- flags )
* Get flags from capsule descriptor.
*/
void mama_word_capsule_flags_fetch(VM *vm);
/**
* @brief CAPSULE-LEN@ ( desc -- len )
* Get payload length from capsule descriptor.
*/
void mama_word_capsule_len_fetch(VM *vm);
/**
* @brief CAPSULE-BIRTH ( capsule-id -- vm-id )
* Birth a baby VM from a production (p) capsule.
* Returns the new VM's ID, or -1 on failure.
*/
void mama_word_capsule_birth(VM *vm);
/**
* @brief CAPSULE-RUN ( capsule-id -- )
* Run an experiment (e) capsule on Mama.
*/
void mama_word_capsule_run(VM *vm);
/**
* @brief MAMA-VM-ID ( -- 0 )
* Push Mama's VM ID (always 0).
*/
void mama_word_mama_vm_id(VM *vm);
/**
* @brief VM-COUNT ( -- n )
* Push number of registered VMs.
*/
void mama_word_vm_count(VM *vm);
/**
* @brief BIRTH ( c-addr u -- )
* Birth a named VM from its capsule. Idempotent if already live.
*/
void mama_word_birth(VM *vm);
/**
* @brief KILL ( c-addr u -- )
* Destroy a named VM. Hera cannot be killed. Idempotent if dead.
*/
void mama_word_kill(VM *vm);
/**
* @brief START ( c-addr u -- )
* Enter a named VM's REPL synchronously. Blocks until target STOPs.
*/
void mama_word_start(VM *vm);
/**
* @brief STOP ( -- )
* Self-stop: set vm->halted so the REPL loop exits. Registered in
* every VM (including child VMs) so any VM can stop itself.
*/
void mama_word_stop(VM *vm);
/**
* @brief USE ( c-addr u -- )
* Redirect REPL input to a named VM without C stack push.
* USE Hera resets to default (Mama).
*/
void mama_word_use(VM *vm);
/**
* @brief EXEC ( c-addr u -- )
* Execute a named capsule on the current VM. Registered in both Mama
* and all child VMs so any VM can load a capsule (e.g. doe.4th).
*/
void mama_word_exec(VM *vm);
/**
* @brief CAPSULE-TEST ( -- )
* Print diagnostic message confirming capsule system is active.
*/
void mama_word_capsule_test(VM *vm);
/**
* @brief Register minimal word set for child VMs: STOP and EXEC.
*
* Call from capsule_vm_alloc_hook() instead of passing mama_word_stop /
* mama_word_exec as cross-TU function pointers (which generate
* R_X86_64_REX_GOTPCRELX relocations not relaxed by the PE32+ linker).
*/
void register_child_vm_words(VM *vm);
#else /* !__STARKERNEL__ */
/**
* @brief Stub for hosted builds - does nothing
*/
static inline void register_mama_forth_words(VM *vm) {
(void)vm;
/* Mama FORTH words are kernel-only */
}
#endif /* __STARKERNEL__ */
#endif /* MAMA_FORTH_WORDS_H */
+75
View File
@@ -0,0 +1,75 @@
/*
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 MEMORY_WORDS_H
#define MEMORY_WORDS_H
#include "vm.h"
/**
* @defgroup memory_ops FORTH-79 Memory Operations
* @{
* @brief Memory operation words defined in FORTH-79 standard
*
* @par Available operations:
* - ! ( n addr -- ) Store n at addr
* - @ ( addr -- n ) Fetch n from addr
* - C! ( c addr -- ) Store character at addr
* - C@ ( addr -- c ) Fetch character from addr
* - +! ( n addr -- ) Add n to value at addr
* - 2! ( d addr -- ) Store double at addr
* - 2@ ( addr -- d ) Fetch double from addr
* - FILL ( addr u c -- ) Fill u bytes at addr with c
* - MOVE ( addr1 addr2 u -- ) Move u bytes from addr1 to addr2
* - CMOVE ( addr1 addr2 u -- ) Move u bytes, low to high addresses
* - CMOVE> ( addr1 addr2 u -- ) Move u bytes, high to low addresses
* - ERASE ( addr u -- ) Fill u bytes at addr with zeros
* @}
*/
/*
* @brief Registers all memory operation words with the Forth virtual machine
* @param vm Pointer to the virtual machine instance
*/
void register_memory_words(VM * vm);
#endif /* MEMORY_WORDS_H */
@@ -0,0 +1,61 @@
/*
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 MIXED_ARITHMETIC_WORDS_H
#define MIXED_ARITHMETIC_WORDS_H
#include "vm.h"
/* FORTH-79 Mixed Arithmetic Words (single/double precision):
* M+ ( d1 n -- d2 ) Add single to double
* M- ( d1 n -- d2 ) Subtract single from double
* M-STAR-SLASH ( d1 n1 n2 -- d2 ) Multiply double by single, divide by single
* SM/REM ( d n1 -- n2 n3 ) Symmetric divide/remainder
* FM/MOD ( d n1 -- n2 n3 ) Floored divide/mod
* UM* ( u1 u2 -- ud ) Unsigned multiply to double
* UM/MOD ( ud u1 -- u2 u3 ) Unsigned divide/mod
* STAR-SLASH-MOD ( n1 n2 n3 -- n4 n5 ) Intermediate multiply/divide/mod
*/
void register_mixed_arithmetic_words(VM * vm);
#endif /* MIXED_ARITHMETIC_WORDS_H */
@@ -0,0 +1,59 @@
/*
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_benchmark_words.h
*
* FORTH words for benchmarking hot-words cache impact
*/
#ifndef PHYSICS_BENCHMARK_WORDS_H
#define PHYSICS_BENCHMARK_WORDS_H
#include "vm.h"
/**
* Register all physics benchmark words with the VM
*/
void register_physics_benchmark_words(VM *vm);
#endif /* PHYSICS_BENCHMARK_WORDS_H */
@@ -0,0 +1,79 @@
/*
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_diagnostic_words.h
*
* Interactive FORTH words for live physics feedback loop demonstration.
*/
#ifndef PHYSICS_DIAGNOSTIC_WORDS_H
#define PHYSICS_DIAGNOSTIC_WORDS_H
#include "vm.h"
/**
* Display detailed physics metrics for the most recently executed word
*/
void forth_PHYSICS_WORD_METRICS(VM *vm);
/**
* Calculate and show knob adjustments based on current thermal metrics
*/
void forth_PHYSICS_CALC_KNOBS(VM *vm);
/**
* Execute a word repeatedly (burn) and show thermal feedback
*/
void forth_PHYSICS_BURN(VM *vm);
/**
* Display the complete feedback loop: inputs → math → knobs → effect
*/
void forth_PHYSICS_SHOW_FEEDBACK(VM *vm);
/**
* Register all physics diagnostic words with the VM
*/
void register_physics_diagnostic_words(VM *vm);
#endif /* PHYSICS_DIAGNOSTIC_WORDS_H */
@@ -0,0 +1,60 @@
/*
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_freeze_words.h
*
* Phase 2 Freeze/Decay Control Words for physics-driven adaptive runtime
*/
#ifndef PHYSICS_FREEZE_WORDS_H
#define PHYSICS_FREEZE_WORDS_H
#include "vm.h"
/**
* Register all Phase 2 freeze/decay control words with the VM
*/
void register_physics_freeze_words(VM *vm);
#endif /* PHYSICS_FREEZE_WORDS_H */
@@ -0,0 +1,61 @@
/*
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_pipelining_diagnostic_words.h
*
* Declarations for pipelining metrics diagnostic words.
* Phase 1: Display word-to-word transition statistics collected during execution.
*/
#ifndef PHYSICS_PIPELINING_DIAGNOSTIC_WORDS_H
#define PHYSICS_PIPELINING_DIAGNOSTIC_WORDS_H
#include "vm.h"
/**
* Register all pipelining diagnostic words
* Called during VM initialization
*/
void register_physics_pipelining_diagnostic_words(VM *vm);
#endif /* PHYSICS_PIPELINING_DIAGNOSTIC_WORDS_H */
+25
View File
@@ -0,0 +1,25 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/**
* q48_words.h — Module 25: Q48.16 fixed-point FORTH words
*/
#ifndef Q48_WORDS_H
#define Q48_WORDS_H
#include "vm.h"
/**
* Register all Q48.16 FORTH words with the VM.
* Module 25 in register_forth79_words().
*/
void register_q48_words(VM *vm);
#endif /* Q48_WORDS_H */
@@ -0,0 +1,81 @@
/*
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 RETURN_STACK_WORDS_H
#define RETURN_STACK_WORDS_H
#include "vm.h"
/**
* @defgroup ReturnStackOps FORTH-79 Return Stack Operations
* @{
*
* @def >R
* @brief Move item from data to return stack
* Stack effect: ( n -- ) ( R: -- n )
*
* @def R>
* @brief Move item from return to data stack
* Stack effect: ( -- n ) ( R: n -- )
*
* @def R@
* @brief Copy top of return stack to data stack
* Stack effect: ( -- n ) ( R: n -- n )
*
* @def RP!
* @brief Set return stack pointer
* Stack effect: ( addr -- )
*
* @def RP@
* @brief Get return stack pointer
* Stack effect: ( -- addr )
* @}
*/
/*
* @brief Registers all return stack manipulation words with the virtual machine
*
* @param vm Pointer to the virtual machine instance
*/
void register_return_stack_words(VM * vm);
#endif /* RETURN_STACK_WORDS_H */
+36
View File
@@ -0,0 +1,36 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
#ifndef SCROLL_WORDS_H
#define SCROLL_WORDS_H
#include "vm.h"
/**
* @defgroup scroll_words REPL Scrollback Words
* @{
*
* @brief `SCROLL-BACK`/`SCROLL-FWD`, the REPL scrollback controls
* (FABRIC-0.md item 4.4q). Kernel-only; no-op on hosted builds (no
* framebuffer console there).
*
* @par SCROLL-BACK ( n -- )
* Move the scrollback view back @c n lines (toward older output) and
* redraw. Clamped at the oldest stored line -- no error on over-request,
* it just stops there.
*
* @par SCROLL-FWD ( n -- )
* Move the scrollback view forward @c n lines (toward live output) and
* redraw. Clamped at the live view.
* @}
*/
void register_scroll_words(VM *vm);
#endif /* SCROLL_WORDS_H */
+75
View File
@@ -0,0 +1,75 @@
/*
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 STACK_WORDS_H
#define STACK_WORDS_H
#include "vm.h"
/**
* @defgroup stack_ops FORTH-79 Stack Operations
* @{
*
* @brief Standard FORTH-79 stack manipulation words
*
* @details The following FORTH words are implemented for stack manipulation:
* - DROP ( n -- ) Remove top stack item
* - DUP ( n -- n n ) Duplicate top stack item
* - ?DUP ( n -- n n | n -- 0 ) Duplicate if non-zero
* - SWAP ( n1 n2 -- n2 n1 ) Exchange top two stack items
* - OVER ( n1 n2 -- n1 n2 n1 ) Copy second stack item to top
* - ROT ( n1 n2 n3 -- n2 n3 n1 ) Rotate top three stack items
* - -ROT ( n1 n2 n3 -- n3 n1 n2 ) Reverse rotate top three items
* - DEPTH ( -- n ) Return number of stack items
* - PICK ( n -- stack[n] ) Copy nth stack item to top
* - ROLL ( n -- ) Move nth stack item to top
* @}
*/
/*
* @brief Registers all stack operation words with the virtual machine
*
* @param vm Pointer to the virtual machine instance
*/
void register_stack_words(VM * vm);
#endif /* STACK_WORDS_H */
@@ -0,0 +1,131 @@
/*
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_WORDS_H
#define STARFORTH_WORDS_H
#include "vm.h"
/** @name StarForth Implementation Vocabulary Words */
///@{
/**
* @brief Fetch entropy value from address
* @param vm Pointer to VM instance
* @stack ( addr -- n )
*/
void starforth_word_entropy_fetch(VM * vm);
/**
* @brief Store entropy value to address
* @param vm Pointer to VM instance
* @stack ( n addr -- )
*/
void starforth_word_entropy_store(VM * vm);
/**
* @brief Calculate word entropy
* @param vm Pointer to VM instance
* @stack ( -- )
*/
void starforth_word_word_entropy(VM * vm);
/**
* @brief Reset entropy values
* @param vm Pointer to VM instance
* @stack ( -- )
*/
void starforth_word_reset_entropy(VM * vm);
/*
* @brief Display top N words by entropy
* @param vm Pointer to VM instance
* @stack ( n -- )
*/
void starforth_word_top_words(VM * vm);
/*
* @brief Shebang-style comment for init-4.4th metadata
* @param vm Pointer to VM instance
* @stack ( -- )
*/
void starforth_word_paren_dash(VM * vm);
/*
* @brief Initialize system from init-4.4th
* @param vm Pointer to VM instance
* @stack ( -- )
*/
void starforth_word_init(VM * vm);
/*
* @brief Set PRNG seed for reproducible random sequences
* @param vm Pointer to VM instance
* @stack ( n -- )
*/
void starforth_word_seed(VM * vm);
/**
* @brief Generate bounded random number
* @param vm Pointer to VM instance
* @stack ( lo hi -- n )
* @note Returns random value in inclusive range [lo, hi]
*/
void starforth_word_random(VM * vm);
/*
* @brief Wait/sleep for specified milliseconds
* @param vm Pointer to VM instance
* @stack ( n -- )
* @note General-purpose delay, also useful for VM cooling between benchmarks
*/
void starforth_word_wait(VM * vm);
///@}
/*
* @brief Register all StarForth words with VM
* @param vm Pointer to VM instance where words will be registered
*/
void register_starforth_words(VM * vm);
#endif /* STARFORTH_WORDS_H */
+152
View File
@@ -0,0 +1,152 @@
/*
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 STRING_WORDS_H
#define STRING_WORDS_H
#include "vm.h"
/** @file string_words.h
* @brief FORTH-79 Standard string and text processing words implementation
*
* This file contains the implementation of FORTH-79 standard string and text processing words.
* These words provide functionality for string manipulation, input handling, and parsing.
*/
/** @name FORTH-79 String & Text Processing Words
* @{
*/
/**
* @brief COUNT ( addr1 -- addr2 u ) Get string length and address
* Returns the character count and address of a counted string
*/
/**
* @brief EXPECT ( addr u -- ) Accept input line
* Accepts up to u characters of input and stores them at addr
*/
/**
* @brief SPAN ( -- addr ) Variable: number of characters input
* Contains the count of characters actually received by EXPECT
*/
/**
* @brief QUERY ( -- ) Accept input into TIB
* Accepts a line of input into the Terminal Input Buffer
*/
/**
* @brief TIB ( -- addr ) Terminal input buffer address
* Returns the address of the Terminal Input Buffer
*/
/**
* @brief WORD ( c -- addr ) Parse next word, delimited by c
* Parses the next word from input stream using c as delimiter
*/
/**
* @brief >IN ( -- addr ) Input stream pointer
* Contains the offset into the input buffer
*/
/**
* @brief SOURCE ( -- addr u ) Input source specification
* Returns current input buffer address and length
*/
/**
* @brief BL ( -- c ) ASCII space character (32)
* Pushes ASCII space character value onto stack
*/
/**
* @brief FIND ( addr -- addr flag ) Find word in dictionary
* Searches dictionary for word, returns status flag
*/
/**
* @brief ' ( -- xt ) Get execution token of next word
* Returns execution token of next word in input stream
*/
/**
* @brief ['] ( -- xt ) Compile execution token
* Compiles execution token as literal
*/
/**
* @brief LITERAL ( n -- ) Compile literal number
* Compiles top stack value as literal
*/
/**
* @brief [LITERAL] ( n -- ) Compile literal at compile time
* Compiles literal during compilation
*/
/**
* @brief CONVERT ( d1 addr1 -- d2 addr2 ) Convert string to number
* Converts string starting at addr1 to number
*/
/**
* @brief NUMBER ( addr -- n flag ) Convert string to single number
* Converts string to single precision number
*/
/**
* @brief ENCLOSE ( addr c -- addr1 n1 n2 n3 ) Parse for delimiter
* Parses string for delimiter character
*/
/** @} */
/**
* @brief Register all string processing words with VM
* @param vm Pointer to VM instance where words will be registered
*/
void register_string_words(VM * vm);
#endif /* STRING_WORDS_H */
+99
View File
@@ -0,0 +1,99 @@
/*
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 SYSTEM_WORDS_H
#define SYSTEM_WORDS_H
#include "vm.h"
/**
* @defgroup system_words FORTH-79 System & Environment Words
* @{
*
* @brief System and environment control words for FORTH-79
*
* @details The following words provide system and environment control functionality:
*
* @par COLD ( -- )
* Performs a cold start of the system, reinitializing all system variables
*
* @par WARM ( -- )
* Performs a warm start of the system, preserving some system state
*
* @par BYE ( -- )
* Exits the FORTH system and returns to the host operating system
*
* @par SAVE-SYSTEM ( -- )
* Saves the current system image to disk
*
* @par WORDS ( -- )
* Lists all words in the current vocabulary
*
* @par VLIST ( -- )
* Lists all words in all vocabularies
*
* @par ?STACK ( -- )
* Checks the stack depth and reports any stack errors
*
* @par PAGE ( -- )
* Clears the screen or outputs a form feed
*
* @par NOP ( -- )
* Performs no operation
*
* @par 79-STANDARD ( -- )
* Ensures FORTH-79 standard compliance
* @}
*/
/**
* @brief Registers all system words with the virtual machine
*
* @param vm Pointer to the virtual machine instance
*
* @details This function registers all FORTH-79 system and environment words
* with the specified virtual machine instance, making them available for use
* in the FORTH environment.
*/
void register_system_words(VM * vm);
#endif /* SYSTEM_WORDS_H */
+38
View File
@@ -0,0 +1,38 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
#ifndef TTF_WORDS_H
#define TTF_WORDS_H
#include "vm.h"
/**
* @defgroup ttf_words TrueType Text Words
* @{
*
* @brief `TTF-TEXT`, the TrueType-backed text entry point (FABRIC-0.md item
* 4.3.7e). Kernel-only; no-op on hosted builds (no framebuffer/capsule
* system exists there).
*
* @par TTF-TEXT ( c-addr u x y size color -- )
* Render a UTF-8 string via the v1 default font capsule
* (`fonts:JetBrainsMono-Regular.ttf`, loaded and cached lazily on first
* use). (x,y) is raster pixel space (top-left origin, Y-down) -- NOT
* capsules/fabric.4th's Cartesian Y-up convention used by the stroke
* font's `TEXT` (4.3.6f); the two are deliberately different coordinate
* systems, not interchangeable. (x,y) is the first glyph's baseline-left
* origin. `size` is capped at `TTF_CACHE_MAX_SIZE_PX` (see ttf.h).
* Advances proportionally per glyph via the font's real hmtx advance
* width, scaled to pixels.
* @}
*/
void register_ttf_words(VM *vm);
#endif /* TTF_WORDS_H */
@@ -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 VOCABULARY_WORDS_H
#define VOCABULARY_WORDS_H
#include "vm.h"
/**
* @defgroup vocabulary FORTH-79 Vocabulary System Words
* @{
* @brief Standard vocabulary manipulation words
*
* @par VOCABULARY ( -- )
* Create new vocabulary
* @par DEFINITIONS ( -- )
* Set current vocabulary
* @par CONTEXT ( -- addr )
* Variable: search vocabulary pointer
* @par CURRENT ( -- addr )
* Variable: definition vocabulary pointer
* @par FORTH ( -- )
* Select FORTH vocabulary
* @par ONLY ( -- )
* Set minimal search order
* @par ALSO ( -- )
* Duplicate top of search order
* @par ORDER ( -- )
* Display search order
* @par PREVIOUS ( -- )
* Remove top of search order
* @par (FIND) ( addr -- addr flag )
* Find word in dictionary (primitive)
* @}
*/
/*
* @brief Registers all vocabulary-related words in the Forth system
* @param vm Pointer to the virtual machine instance
*/
void register_vocabulary_words(VM * vm);
/**
* @brief Direct vocabulary creation (kernel bootstrap)
* @param vm Pointer to the virtual machine instance
* @param name Vocabulary name
*/
void vocabulary_create_vocabulary_direct(VM *vm, const char *name);
/*
* @brief DEFINITIONS word implementation (kernel bootstrap)
* @param vm Pointer to the virtual machine instance
*/
void vocabulary_word_definitions(VM *vm);
/*
* @brief FORTH word implementation - selects FORTH vocabulary
* @param vm Pointer to the virtual machine instance
*/
void vocabulary_word_forth(VM *vm);
#endif /* VOCABULARY_WORDS_H */
+367
View File
@@ -0,0 +1,367 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/**
* inference_words.c — Module 26: SSM inference engine + Jacquard FORTH words
*
* Exposes the SSM physics engine to FORTH:
* - Variance / decay-slope / window-width inference on arbitrary arrays
* - Full inference engine run on this VM's rolling window
* - L8 Jacquard mode selector: update, query, apply
* - Bayesian latency posteriors for cache-hit and bucket-search latencies
* - Rolling window diversity (entropy metric)
* - Readable inference output fields from vm->last_inference_outputs
*
* All Q48.16 values pushed as cell_t (int64_t), reinterpreted as uint64_t.
*
* Words registered:
* Q.VARIANCE ( addr u -- q ) variance of u cells at addr (Q48.16)
* INFER-DECAY-SLOPE ( addr u -- q ) exponential decay slope (Q48.16)
* INFER-WINDOW-WIDTH ( addr u -- n ) optimal window width from inflection
* WINDOW-DIVERSITY ( -- u ) rolling window diversity (entropy)
* INFER-RUN ( -- ) run full inference, update vm->last_inference_outputs
* INFER-WINDOW@ ( -- u ) last adaptive_window_width
* INFER-DECAY@ ( -- q ) last adaptive_decay_slope (Q48.16)
* INFER-VARIANCE@ ( -- q ) last window_variance_q48
* INFER-FIT@ ( -- q ) last slope_fit_quality_q48
* INFER-EARLY-EXIT@ ( -- flag ) 1 if last INFER-RUN used ANOVA early-exit
* L8-MODE ( -- n ) current Jacquard mode (0-15)
* L8-UPDATE ( entropy_q cv_q temporal_q stability_q -- )
* L8-APPLY ( -- ) apply current mode to vm->ssm_config (legacy 16-mode)
* L8-TABLE-FORCE ( config_idx -- ) force the adaptive table onto config_idx (0-127)
* BAYES-CACHE-MEAN ( -- q ) Bayesian mean latency for cache hits (Q48.16)
* BAYES-CACHE-LOWER ( -- q ) 95% credible lower bound, cache hits
* BAYES-CACHE-UPPER ( -- q ) 95% credible upper bound, cache hits
* BAYES-BUCKET-MEAN ( -- q ) Bayesian mean latency for bucket searches
* BAYES-BUCKET-LOWER ( -- q ) 95% credible lower bound, bucket searches
* BAYES-BUCKET-UPPER ( -- q ) 95% credible upper bound, bucket searches
*/
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "vm.h"
#include "word_registry.h"
#include "log.h"
#include "q48_16.h"
#include "inference_engine.h"
#include "ssm_jacquard.h"
#include "rolling_window_of_truth.h"
#include "physics_hotwords_cache.h"
#include "platform_alloc.h"
#include "platform_lock.h"
/* ── helpers ──────────────────────────────────────────────────────────── */
static inline q48_16_t q48_pop_inf(VM *vm) { return (q48_16_t)(uint64_t)VM_POP(vm); }
static inline void q48_push_inf(VM *vm, q48_16_t q) { VM_PUSH(vm, (cell_t)(int64_t)q); }
/* Translate Q48.16 integer to double for ssm_l8_metrics_t (double-based). */
static inline double q48_to_dbl(q48_16_t q) { return (double)q / 65536.0; }
/* ── array-based inference primitives ───────────────────────────────── */
/*
* Validate a FORTH array reference: addr is a vaddr_t, u is cell count.
* Returns pointer to data or NULL on bounds error (sets vm->error).
*/
static const uint64_t *array_ptr(VM *vm, vaddr_t addr, cell_t u)
{
if (u <= 0 || addr >= (vaddr_t)VM_MEMORY_SIZE) {
log_message(LOG_ERROR, "inference: bad array reference (addr=%lu u=%ld)",
(unsigned long)addr, (long)u);
vm->error = 1;
return NULL;
}
size_t bytes = (size_t)u * sizeof(cell_t);
if ((size_t)addr + bytes > VM_MEMORY_SIZE) {
log_message(LOG_ERROR, "inference: array reference out of bounds (addr=%lu u=%ld)",
(unsigned long)addr, (long)u);
vm->error = 1;
return NULL;
}
return (const uint64_t *)(vm->memory + addr);
}
/* Q.VARIANCE ( addr u -- q ) — variance of u uint64_t cells at addr */
static void infer_word_q_variance(VM *vm)
{
cell_t u = VM_POP(vm);
vaddr_t addr = (vaddr_t)VM_POP(vm);
const uint64_t *data = array_ptr(vm, addr, u);
if (!data) { q48_push_inf(vm, 0); return; }
q48_push_inf(vm, compute_variance_q48(data, (uint64_t)u));
}
/* INFER-DECAY-SLOPE ( addr u -- q ) — decay slope via linear regression */
static void infer_word_decay_slope(VM *vm)
{
cell_t u = VM_POP(vm);
vaddr_t addr = (vaddr_t)VM_POP(vm);
const uint64_t *data = array_ptr(vm, addr, u);
if (!data) { q48_push_inf(vm, 0); return; }
q48_push_inf(vm, (q48_16_t)infer_decay_slope_q48(data, (uint64_t)u));
}
/* INFER-WINDOW-WIDTH ( addr u -- n ) — optimal window width */
static void infer_word_window_width(VM *vm)
{
cell_t u = VM_POP(vm);
vaddr_t addr = (vaddr_t)VM_POP(vm);
const uint64_t *data = array_ptr(vm, addr, u);
if (!data) { VM_PUSH(vm, 0); return; }
q48_16_t var = compute_variance_q48(data, (uint64_t)u);
uint32_t w = find_variance_inflection(data, (uint64_t)u, var);
VM_PUSH(vm, (cell_t)(int64_t)w);
}
/* ── rolling-window stats ─────────────────────────────────────────────── */
/* WINDOW-DIVERSITY ( -- u ) */
static void infer_word_window_diversity(VM *vm)
{
uint64_t d = rolling_window_measure_diversity(&vm->rolling_window);
VM_PUSH(vm, (cell_t)(int64_t)d);
}
/* ── full inference run ────────────────────────────────────────────────── */
/*
* INFER-RUN ( -- )
* Runs the full inference engine on this VM's rolling window and dictionary
* heat, updating vm->last_inference_outputs. Allocates the outputs struct
* on first call (matches the pattern in vm_time.c).
*/
static void infer_word_run(VM *vm)
{
/* Allocate outputs struct if not yet done */
if (!vm->last_inference_outputs) {
vm->last_inference_outputs = (InferenceOutputs *)sf_malloc(sizeof(InferenceOutputs));
if (!vm->last_inference_outputs) {
log_message(LOG_ERROR, "INFER-RUN: allocation failed for InferenceOutputs");
vm->error = 1;
return;
}
memset(vm->last_inference_outputs, 0, sizeof(InferenceOutputs));
}
/* Walk dictionary to collect heat stats (mirror of vm_time.c) */
uint64_t hot_word_count = 0;
uint64_t stale_word_count = 0;
uint64_t total_heat = 0;
uint32_t word_count = 0;
sf_mutex_lock(&vm->dict_lock);
DictEntry *e = vm->latest;
while (e) {
if (e->execution_heat > HOTWORDS_EXECUTION_HEAT_THRESHOLD)
hot_word_count++;
else if (e->execution_heat > 0 && e->execution_heat < 10)
stale_word_count++;
total_heat += e->execution_heat;
word_count++;
e = e->link;
}
sf_mutex_unlock(&vm->dict_lock);
uint64_t traj_len = (vm->rolling_window.window_pos > 0)
? vm->rolling_window.window_pos
: vm->rolling_window.total_executions;
InferenceInputs inputs;
memset(&inputs, 0, sizeof(inputs));
inputs.vm = vm;
inputs.window = &vm->rolling_window;
inputs.trajectory_length = traj_len;
inputs.prefetch_hits = vm->pipeline_metrics.prefetch_hits;
inputs.prefetch_attempts = vm->pipeline_metrics.prefetch_attempts;
inputs.hot_word_count = hot_word_count;
inputs.stale_word_count = stale_word_count;
inputs.total_heat = total_heat;
inputs.word_count = word_count;
inputs.last_total_heat = vm->total_heat_at_last_check;
inputs.last_stale_count = vm->stale_word_count_at_check;
inference_engine_run(&inputs, vm->last_inference_outputs);
}
/* ── inference output accessors ───────────────────────────────────────── */
/* INFER-WINDOW@ ( -- u ) */
static void infer_word_window_fetch(VM *vm)
{
uint32_t w = vm->last_inference_outputs
? vm->last_inference_outputs->adaptive_window_width : 0;
VM_PUSH(vm, (cell_t)(int64_t)w);
}
/* INFER-DECAY@ ( -- q ) */
static void infer_word_decay_fetch(VM *vm)
{
uint64_t d = vm->last_inference_outputs
? vm->last_inference_outputs->adaptive_decay_slope : 0;
q48_push_inf(vm, (q48_16_t)d);
}
/* INFER-VARIANCE@ ( -- q ) */
static void infer_word_variance_fetch(VM *vm)
{
uint64_t v = vm->last_inference_outputs
? vm->last_inference_outputs->window_variance_q48 : 0;
q48_push_inf(vm, (q48_16_t)v);
}
/* INFER-FIT@ ( -- q ) */
static void infer_word_fit_fetch(VM *vm)
{
uint64_t f = vm->last_inference_outputs
? vm->last_inference_outputs->slope_fit_quality_q48 : 0;
q48_push_inf(vm, (q48_16_t)f);
}
/* INFER-EARLY-EXIT@ ( -- flag ) */
static void infer_word_early_exit_fetch(VM *vm)
{
uint32_t ex = vm->last_inference_outputs
? vm->last_inference_outputs->early_exited : 0;
VM_PUSH(vm, (cell_t)(int64_t)ex);
}
/* ── L8 Jacquard ──────────────────────────────────────────────────────── */
/* L8-MODE ( -- n ) */
static void infer_word_l8_mode(VM *vm)
{
ssm_l8_state_t *l8 = (ssm_l8_state_t *)vm->ssm_l8_state;
int mode = l8 ? (int)l8->current_mode : 0;
VM_PUSH(vm, (cell_t)mode);
}
/*
* L8-UPDATE ( entropy_q cv_q temporal_q stability_q -- )
* Takes four Q48.16 values, converts to double, calls ssm_l8_update().
* Stack order: stability TOS, temporal, cv, entropy at bottom.
*/
static void infer_word_l8_update(VM *vm)
{
ssm_l8_state_t *l8 = (ssm_l8_state_t *)vm->ssm_l8_state;
if (!l8) { VM_POP(vm); VM_POP(vm); VM_POP(vm); VM_POP(vm); return; }
ssm_l8_metrics_t metrics;
metrics.stability_score = q48_to_dbl(q48_pop_inf(vm));
metrics.temporal_decay = q48_to_dbl(q48_pop_inf(vm));
metrics.cv = q48_to_dbl(q48_pop_inf(vm));
metrics.entropy = q48_to_dbl(q48_pop_inf(vm));
ssm_l8_update(&metrics, l8);
}
/* L8-APPLY ( -- ) */
static void infer_word_l8_apply(VM *vm)
{
ssm_l8_state_t *l8 = (ssm_l8_state_t *)vm->ssm_l8_state;
ssm_config_t *cfg = (ssm_config_t *)vm->ssm_config;
if (!l8 || !cfg) return;
ssm_apply_mode(l8, cfg);
}
/*
* L8-TABLE-FORCE ( config_idx -- )
* Forces the adaptive 128-config table onto config_idx (masked to 0-127)
* as if the bandit's own UCB selection had picked it, and applies its
* bits immediately. Unlike L8-UPDATE/L8-APPLY (the legacy 16-mode path,
* which the table's own periodic heartbeat tick ignores and will
* overwrite at its next trial boundary regardless), this drives the same
* mechanism the heartbeat itself uses, so an external choice (e.g. a DoE
* campaign) stays in effect and gets scored coherently by the bandit's
* own reward loop rather than being silently overwritten out from under
* it. See ssm_l8_force_config() for the full rationale.
*/
static void infer_word_l8_table_force(VM *vm)
{
ssm_l8_state_t *l8 = (ssm_l8_state_t *)vm->ssm_l8_state;
ssm_config_t *cfg = (ssm_config_t *)vm->ssm_config;
cell_t idx = VM_POP(vm);
if (!l8 || !cfg) return;
ssm_l8_force_config(l8, cfg, (uint8_t)idx);
}
/* ── Bayesian latency posteriors ──────────────────────────────────────── */
static BayesianLatencyPosterior cache_posterior(VM *vm)
{
BayesianLatencyPosterior zero;
memset(&zero, 0, sizeof(zero));
if (!vm->hotwords_cache) return zero;
return hotwords_posterior_cache_hits(&vm->hotwords_cache->stats);
}
static BayesianLatencyPosterior bucket_posterior(VM *vm)
{
BayesianLatencyPosterior zero;
memset(&zero, 0, sizeof(zero));
if (!vm->hotwords_cache) return zero;
return hotwords_posterior_bucket_searches(&vm->hotwords_cache->stats);
}
static void infer_word_bayes_cache_mean(VM *vm)
{
q48_push_inf(vm, (q48_16_t)(int64_t)cache_posterior(vm).mean_ns_q48);
}
static void infer_word_bayes_cache_lower(VM *vm)
{
q48_push_inf(vm, (q48_16_t)(int64_t)cache_posterior(vm).credible_lower_95);
}
static void infer_word_bayes_cache_upper(VM *vm)
{
q48_push_inf(vm, (q48_16_t)(int64_t)cache_posterior(vm).credible_upper_95);
}
static void infer_word_bayes_bucket_mean(VM *vm)
{
q48_push_inf(vm, (q48_16_t)(int64_t)bucket_posterior(vm).mean_ns_q48);
}
static void infer_word_bayes_bucket_lower(VM *vm)
{
q48_push_inf(vm, (q48_16_t)(int64_t)bucket_posterior(vm).credible_lower_95);
}
static void infer_word_bayes_bucket_upper(VM *vm)
{
q48_push_inf(vm, (q48_16_t)(int64_t)bucket_posterior(vm).credible_upper_95);
}
/* ── registration ─────────────────────────────────────────────────────── */
void register_inference_words(VM *vm)
{
register_word(vm, "Q.VARIANCE", infer_word_q_variance);
register_word(vm, "INFER-DECAY-SLOPE", infer_word_decay_slope);
register_word(vm, "INFER-WINDOW-WIDTH", infer_word_window_width);
register_word(vm, "WINDOW-DIVERSITY", infer_word_window_diversity);
register_word(vm, "INFER-RUN", infer_word_run);
register_word(vm, "INFER-WINDOW@", infer_word_window_fetch);
register_word(vm, "INFER-DECAY@", infer_word_decay_fetch);
register_word(vm, "INFER-VARIANCE@", infer_word_variance_fetch);
register_word(vm, "INFER-FIT@", infer_word_fit_fetch);
register_word(vm, "INFER-EARLY-EXIT@", infer_word_early_exit_fetch);
register_word(vm, "L8-MODE", infer_word_l8_mode);
register_word(vm, "L8-UPDATE", infer_word_l8_update);
register_word(vm, "L8-APPLY", infer_word_l8_apply);
register_word(vm, "L8-TABLE-FORCE", infer_word_l8_table_force);
register_word(vm, "BAYES-CACHE-MEAN", infer_word_bayes_cache_mean);
register_word(vm, "BAYES-CACHE-LOWER", infer_word_bayes_cache_lower);
register_word(vm, "BAYES-CACHE-UPPER", infer_word_bayes_cache_upper);
register_word(vm, "BAYES-BUCKET-MEAN", infer_word_bayes_bucket_mean);
register_word(vm, "BAYES-BUCKET-LOWER", infer_word_bayes_bucket_lower);
register_word(vm, "BAYES-BUCKET-UPPER", infer_word_bayes_bucket_upper);
}
+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.
*/
/* io_words.c - FORTH-79 I/O & Terminal Words */
#include "include/io_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
#include "../../include/platform_io.h"
#include <stdio.h>
/**
* @brief FORTH word EMIT - Output character to terminal
* @param vm Pointer to the VM structure
* @details Stack effect: ( c -- )
* Outputs the character from top of stack to the terminal
*/
static void io_word_emit(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
char c = (char) vm->data_stack[vm->dsp--];
putchar(c);
fflush(stdout);
}
/**
* @brief FORTH word CR - Output carriage return
* @param vm Pointer to the VM structure
* @details Stack effect: ( -- )
* Outputs a newline character to the terminal
*/
static void io_word_cr(VM *vm) {
(void)vm;
putchar('\n');
fflush(stdout);
}
/**
* @brief FORTH word KEY - Input character from terminal
* @param vm Pointer to the VM structure
* @details Stack effect: ( -- c )
* Reads one character from terminal and pushes it to stack
*/
static void io_word_key(VM *vm) {
if (vm->dsp >= STACK_SIZE - 1) {
vm->error = 1;
return;
}
int c = getchar();
vm->data_stack[++vm->dsp] = (cell_t) c;
}
/**
* @brief FORTH word ?TERMINAL - Check if input is available
* @param vm Pointer to the VM structure
* @details Stack effect: ( -- flag )
* Pushes true if input is available, false otherwise
*/
static void io_word_question_terminal(VM *vm) {
if (vm->dsp >= STACK_SIZE - 1) {
vm->error = 1;
return;
}
vm->data_stack[++vm->dsp] = sf_terminal_ready() ? -1 : 0;
}
/**
* @brief FORTH word TYPE - Output string of characters
* @param vm Pointer to the VM structure
* @details Stack effect: ( addr u -- )
* Outputs u characters from memory starting at addr
*/
static void io_word_type(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "TYPE: Data stack underflow");
vm->error = 1;
return;
}
cell_t count = vm_pop(vm);
vaddr_t addr = VM_ADDR(vm_pop(vm));
// Bounds check via vm_addr_ok (avoids the addr+count overflow a manual
// sum would risk for large signed operands)
if (count < 0 || !vm_addr_ok(vm, addr, (size_t) count)) {
log_message(LOG_ERROR, "TYPE: Invalid range [addr=%llu, count=%ld)",
(unsigned long long) addr, (long) count);
vm->error = 1;
return;
}
// Output characters from VM memory
const uint8_t *src = vm_ptr(vm, addr);
for (cell_t i = 0; i < count; i++) {
putchar(src[i]);
}
fflush(stdout);
log_message(LOG_DEBUG, "TYPE: Output %ld characters from address %llu",
(long) count, (unsigned long long) addr);
}
/**
* @brief FORTH word SPACE - Output one space character
* @param vm Pointer to the VM structure
* @details Stack effect: ( -- )
* Outputs a single space character to terminal
*/
static void io_word_space(VM *vm) {
(void)vm;
putchar(' ');
fflush(stdout);
}
/**
* @brief FORTH word SPACES - Output multiple spaces
* @param vm Pointer to the VM structure
* @details Stack effect: ( n -- )
* Outputs n space characters to terminal
*/
static void io_word_spaces(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t count = vm->data_stack[vm->dsp--];
if (count < 0) return;
for (cell_t i = 0; i < count; i++) {
putchar(' ');
}
fflush(stdout);
}
/* (do-string) — runtime called by compiled ." ; reads inline string from
* threaded code via the return-stack IP, prints it, then advances IP past
* the padded inline block. NOT immediate.
*
* Inline layout immediately after the (do-string) cell in the thread:
* [1 length byte][n string bytes][padding to next cell boundary]
*/
static void io_runtime_do_string(VM *vm) {
if (vm->rsp < 0) { vm->error = 1; return; }
/* return_stack[rsp] holds ip+1 as a raw C pointer — points at inline data */
uint8_t *data = (uint8_t *)(uintptr_t)vm->return_stack[vm->rsp];
if (!data) { vm->error = 1; return; }
uint8_t n = data[0];
for (uint8_t i = 0; i < n; i++)
putchar((unsigned char)data[1 + i]);
fflush(stdout);
/* advance IP past inline block (length byte + string bytes, cell-aligned) */
size_t skip = 1 + (size_t)n;
size_t padded = (skip + (sizeof(cell_t) - 1)) & ~(sizeof(cell_t) - 1);
vm->return_stack[vm->rsp] = (cell_t)(uintptr_t)(data + padded);
}
/* ." ( "ccc<quote>" -- )
* Immediate. Interpretation: parse and print the string directly.
* Compilation: compile (do-string) + inline [len][chars][pad].
*/
static void io_word_dot_quote(VM *vm) {
const char *src = vm->input_buffer;
size_t pos = vm->input_pos;
size_t end = vm->input_length;
if (!src || pos > end) {
vm->error = 1;
return;
}
if (pos < end && src[pos] == ' ') pos++;
size_t start = pos;
while (pos < end && src[pos] != '"') pos++;
if (pos >= end) { vm->error = 1; return; }
size_t n = pos - start;
if (n > 255) n = 255; /* single length byte — clamp silently */
vm->input_pos = pos + 1;
if (vm->mode == MODE_INTERPRET) {
for (size_t i = 0; i < n; i++)
putchar((unsigned char)src[start + i]);
fflush(stdout);
return;
}
/* Compilation: emit (do-string) cell then inline [len][chars][pad] */
DictEntry *do_str = vm_find_word(vm, "(do-string)", 11);
if (!do_str) { vm->error = 1; return; }
vm_compile_word(vm, do_str);
size_t skip = 1 + n;
size_t padded = (skip + (sizeof(cell_t) - 1)) & ~(sizeof(cell_t) - 1);
uint8_t *raw = (uint8_t *)vm_allot(vm, padded);
if (!raw) { vm->error = 1; return; }
raw[0] = (uint8_t)n;
for (size_t i = 0; i < n; i++) raw[1 + i] = (uint8_t)src[start + i];
for (size_t i = 1 + n; i < padded; i++) raw[i] = 0;
}
/*
* @brief Register all I/O words with the VM
* @param vm Pointer to the VM structure
* @details Registers all FORTH-79 I/O and terminal words with the virtual machine
*/
void register_io_words(VM *vm) {
register_word(vm, "EMIT", io_word_emit);
register_word(vm, "CR", io_word_cr);
register_word(vm, "KEY", io_word_key);
register_word(vm, "?TERMINAL", io_word_question_terminal);
register_word(vm, "TYPE", io_word_type);
register_word(vm, "SPACE", io_word_space);
register_word(vm, "SPACES", io_word_spaces);
register_word(vm, "(do-string)", io_runtime_do_string);
register_word(vm, ".\"", io_word_dot_quote);
vm_make_immediate(vm);
}
+189
View File
@@ -0,0 +1,189 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/* keyboard_words.c — raw scancode-ring diagnostic word (FABRIC-0.md item
* 4.3.5). Kernel-only, amd64-only; no-op elsewhere. */
#include "include/keyboard_words.h"
#include "../../include/word_registry.h"
#if defined(__STARKERNEL__) && defined(ARCH_AMD64)
#include "starkernel/i8042.h"
#endif
#if defined(__STARKERNEL__) && (defined(ARCH_RISCV64) || defined(ARCH_AARCH64))
#include "starkernel/virtio_input.h"
#endif
#if defined(__STARKERNEL__)
#include "starkernel/console.h"
#endif
/* KBD-SCAN ( -- c -1 | 0 ) */
static void kbw_scan(VM *vm)
{
#if defined(__STARKERNEL__) && defined(ARCH_AMD64)
uint8_t sc;
if (i8042_pop_scancode(&sc)) {
vm_push(vm, (cell_t)sc);
vm_push(vm, -1);
} else {
vm_push(vm, 0);
}
#else
vm_push(vm, 0);
#endif
}
/* KBD-DEBUG ( -- isr_count spurious_count ): cheap standing diagnostic,
* not scaffolding -- confirms the interrupt path is alive (isr_count) and
* flags the failure mode item 4.3.5 found (a real IRQ misreported as
* spurious) without needing a live debugger. */
extern volatile uint32_t g_i8042_isr_count;
extern volatile uint32_t g_spurious_count;
static void kbw_debug(VM *vm)
{
#if defined(__STARKERNEL__) && defined(ARCH_AMD64)
vm_push(vm, (cell_t)g_i8042_isr_count);
vm_push(vm, (cell_t)g_spurious_count);
#else
vm_push(vm, 0);
vm_push(vm, 0);
#endif
}
/* VKBD-EVENT ( -- code value -1 | 0 ): item 4.3.5c (riscv64) / 4.3.5e
* (aarch64) virtio-input. Pop one decoded EV_KEY event off the
* interrupt-fed ring buffer. Pushes code and value and -1 (true) if one
* was available, or just 0 (false) if the buffer was empty. Distinct word
* from KBD-SCAN rather than a shared one: different device, different
* event shape (Linux-style code/value pair vs. a raw XT scancode byte) --
* convergence onto one interface is item 4.3.5f's explicit job, not this
* one's. */
static void kbw_vevent(VM *vm)
{
#if defined(__STARKERNEL__) && (defined(ARCH_RISCV64) || defined(ARCH_AARCH64))
uint16_t code;
uint32_t value;
if (virtio_input_pop_event(&code, &value)) {
vm_push(vm, (cell_t)code);
vm_push(vm, (cell_t)value);
vm_push(vm, -1);
} else {
vm_push(vm, 0);
}
#else
vm_push(vm, 0);
#endif
}
/* VKBD-DEBUG ( -- isr_count ): standing diagnostic, same shape as
* KBD-DEBUG -- confirms the virtio-input interrupt path is alive. */
extern volatile uint32_t g_virtio_input_isr_count;
static void kbw_vdebug(VM *vm)
{
#if defined(__STARKERNEL__) && (defined(ARCH_RISCV64) || defined(ARCH_AARCH64))
vm_push(vm, (cell_t)g_virtio_input_isr_count);
#else
vm_push(vm, 0);
#endif
}
/* KEY-EVENT ( -- keycode pressed -1 | 0 ): item 4.3.5f convergence
* checkpoint. One shared shape across all three architectures instead of
* KBD-SCAN's raw XT scancode byte or VKBD-EVENT's Linux code/value pair.
* keycode is the Linux input keycode namespace (linux/input-event-codes.h);
* pressed is 1 (press) or 0 (release).
*
* amd64 needs a real translation, not an invented one: XT Set-1's make
* code for a key and Linux's keycode for that same key are the *same
* number*, confirmed against this build host's own
* /usr/include/linux/input-event-codes.h and against this tree's own
* live-observed values (item 4.3.5's done note: 'a' -> 30, 'b' -> 48,
* matching KEY_A=30/KEY_B=48 exactly) -- not a coincidence, a documented
* historical property of how the Linux input keycode numbering was
* originally defined around the AT/XT scancode set. Break codes set XT's
* bit 7 (make|0x80) rather than carrying a separate press/release field,
* so `keycode = sc & 0x7F, pressed = !(sc & 0x80)` is the whole
* translation for the entire non-extended key range. Extended
* (0xE0-prefixed) scancodes are not decoded here -- this checkpoint needs
* one shared shape proven live per architecture, not full keyboard
* coverage; that belongs to the REPL wiring item (M8) this checkpoint
* unblocks.
*
* riscv64/aarch64 need no translation at all -- virtio-input's `code`
* field already lives in the same Linux keycode namespace; `value` is
* mapped 1:1 except autorepeat (value=2), folded into "still pressed"
* here since this checkpoint's shape only distinguishes press/release. */
/* FABRIC-0.md item 4.4v: shared C-level implementation, so the REPL
* keyboard bridge (repl.c) and the KEY-EVENT FORTH word below poll the
* exact same converged event stream rather than each re-deriving the
* per-arch translation kbw_key_event's own doc comment already explains
* (XT Set-1 make code == Linux keycode for the non-extended range; break
* codes set bit 7 instead of carrying a separate field). */
int sk_key_event_poll(uint16_t *keycode, int *pressed)
{
#if defined(__STARKERNEL__) && defined(ARCH_AMD64)
uint8_t sc;
if (i8042_pop_scancode(&sc)) {
*keycode = (uint16_t)(sc & 0x7Fu);
*pressed = (sc & 0x80u) ? 0 : 1;
return 1;
}
return 0;
#elif defined(__STARKERNEL__) && (defined(ARCH_RISCV64) || defined(ARCH_AARCH64))
uint16_t code;
uint32_t value;
if (virtio_input_pop_event(&code, &value)) {
*keycode = code;
*pressed = (value == 0u) ? 0 : 1;
return 1;
}
return 0;
#else
(void)keycode; (void)pressed;
return 0;
#endif
}
static void kbw_key_event(VM *vm)
{
uint16_t keycode;
int pressed;
if (sk_key_event_poll(&keycode, &pressed)) {
vm_push(vm, (cell_t)keycode);
vm_push(vm, (cell_t)pressed);
vm_push(vm, -1);
} else {
vm_push(vm, 0);
}
}
/* ALT+TAB ( -- ): FABRIC-0.md item 4.4y-revised. Programmatic equivalent of
* the physical Alt+TAB interception (repl.c's sk_kbd_getc()) -- both call
* console_fb_toggle_graphics(), so there is exactly one state-machine
* transition, invoked two ways. */
static void kbw_alt_tab(VM *vm)
{
(void)vm;
#if defined(__STARKERNEL__)
console_fb_toggle_graphics();
#endif
}
void register_keyboard_words(VM *vm)
{
register_word(vm, "KBD-SCAN", kbw_scan);
register_word(vm, "KBD-DEBUG", kbw_debug);
register_word(vm, "VKBD-EVENT", kbw_vevent);
register_word(vm, "VKBD-DEBUG", kbw_vdebug);
register_word(vm, "KEY-EVENT", kbw_key_event);
register_word(vm, "ALT+TAB", kbw_alt_tab);
}
@@ -0,0 +1,89 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/**
* lifecycle_words_hosted.c - BIRTH KILL PAUSE RESUME USE for hosted Linux build
*
* Hosted-build counterpart to src/starkernel/capsule/lifecycle_words.c.
* All five words are ( c-addr u -- ), stack-clean, errors logged not pushed.
*
* In the hosted build there is no capsule blob or kernel VM registry, so
* these implementations consume their arguments and log intent. The kernel
* build (compiled with -D__STARKERNEL__) skips this file entirely because
* the Makefile glob does not include src/word_source/ for that target.
*/
#ifndef __STARKERNEL__
#include "../../include/vm.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
#include <string.h>
#define NAME_MAX_LEN 64
static void extract_name(VM *vm, cell_t caddr, cell_t u, char *out, int max) {
int len = (int)u;
if (len >= max) len = max - 1;
if (len < 0) len = 0;
if (len > 0 && caddr < (cell_t)VM_MEMORY_SIZE)
memcpy(out, (const char *)vm->memory + caddr, (size_t)len);
out[len] = '\0';
}
static void hosted_lifecycle_birth(VM *vm) {
cell_t u = vm_pop(vm);
cell_t caddr = vm_pop(vm);
char name[NAME_MAX_LEN];
extract_name(vm, caddr, u, name, NAME_MAX_LEN);
log_message(LOG_INFO, "BIRTH %s (hosted)", name);
}
static void hosted_lifecycle_kill(VM *vm) {
cell_t u = vm_pop(vm);
cell_t caddr = vm_pop(vm);
char name[NAME_MAX_LEN];
extract_name(vm, caddr, u, name, NAME_MAX_LEN);
log_message(LOG_INFO, "KILL %s (hosted)", name);
}
static void hosted_lifecycle_pause(VM *vm) {
cell_t u = vm_pop(vm);
cell_t caddr = vm_pop(vm);
char name[NAME_MAX_LEN];
extract_name(vm, caddr, u, name, NAME_MAX_LEN);
log_message(LOG_INFO, "PAUSE %s (hosted)", name);
}
static void hosted_lifecycle_resume(VM *vm) {
cell_t u = vm_pop(vm);
cell_t caddr = vm_pop(vm);
char name[NAME_MAX_LEN];
extract_name(vm, caddr, u, name, NAME_MAX_LEN);
log_message(LOG_INFO, "RESUME %s (hosted)", name);
}
static void hosted_lifecycle_use(VM *vm) {
cell_t u = vm_pop(vm);
cell_t caddr = vm_pop(vm);
char name[NAME_MAX_LEN];
extract_name(vm, caddr, u, name, NAME_MAX_LEN);
log_message(LOG_INFO, "USE %s (hosted)", name);
}
void register_lifecycle_words(VM *vm) {
register_word(vm, "BIRTH", hosted_lifecycle_birth);
register_word(vm, "KILL", hosted_lifecycle_kill);
register_word(vm, "PAUSE", hosted_lifecycle_pause);
register_word(vm, "RESUME", hosted_lifecycle_resume);
register_word(vm, "USE", hosted_lifecycle_use);
}
#endif /* !__STARKERNEL__ */
+348
View File
@@ -0,0 +1,348 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/* log_words.c — FORTH words for log level control and string emission */
#include "include/log_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
#include <stdint.h>
#ifdef __STARKERNEL__
#include "starkernel/log_region.h"
#include "starkernel/log_attrib.h" /* vm_log_attributed_vm() */
#include "starkernel/capsule_birth.h" /* capsule_vm_registry_get, VMRegistryEntry */
#include "starkernel/console.h" /* console_println() -- (LOG-APPEND-RAW) diagnostics */
#include <string.h> /* strlen */
#endif
/* ── Level constants ─────────────────────────────────────────────────── */
/** @brief LOG-ERROR ( -- n ) Push the @c LOG_ERROR level constant. */
static void log_word_error(VM *vm) { vm_push(vm, (cell_t)LOG_ERROR); }
/** @brief LOG-WARN ( -- n ) Push the @c LOG_WARN level constant. */
static void log_word_warn (VM *vm) { vm_push(vm, (cell_t)LOG_WARN); }
/** @brief LOG-INFO ( -- n ) Push the @c LOG_INFO level constant. */
static void log_word_info (VM *vm) { vm_push(vm, (cell_t)LOG_INFO); }
/** @brief LOG-TEST ( -- n ) Push the @c LOG_TEST level constant. */
static void log_word_test (VM *vm) { vm_push(vm, (cell_t)LOG_TEST); }
/** @brief LOG-DEBUG ( -- n ) Push the @c LOG_DEBUG level constant. */
static void log_word_debug(VM *vm) { vm_push(vm, (cell_t)LOG_DEBUG); }
/* ── LOG-LEVEL! ( n -- ) ─────────────────────────────────────────────── */
/**
* @brief LOG-LEVEL! ( n -- )
*
* Pops @c n from the stack and calls @c log_set_level() to update the active
* log filter. Values below @c LOG_ERROR are clamped to @c LOG_ERROR; values
* above @c LOG_DEBUG are clamped to @c LOG_DEBUG.
*/
static void log_word_set_level(VM *vm)
{
if (vm->dsp < 0) {
log_message(LOG_ERROR, "LOG-LEVEL!: stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
if (n < (cell_t)LOG_ERROR) n = (cell_t)LOG_ERROR;
if (n > (cell_t)LOG_DEBUG) n = (cell_t)LOG_DEBUG;
log_set_level((LogLevel)n);
}
/* ── LOG-LEVEL@ ( -- n ) ─────────────────────────────────────────────── */
/**
* @brief LOG-LEVEL@ ( -- n )
*
* Pushes the current log level onto the stack.
*/
static void log_word_get_level(VM *vm)
{
vm_push(vm, (cell_t)log_get_level());
}
/* ── (do-log-*) runtime words ────────────────────────────────────────── */
/*
* Called at runtime when a compiled LOG-INFO" etc. executes.
* Reads the inline counted string from the threaded-code stream (same
* layout as (do-string)) and routes it through log_message().
*
* Inline layout: [1 length byte][n string bytes][padding to cell boundary]
*/
static void log_do_emit(VM *vm, LogLevel level)
{
if (vm->rsp < 0) {
log_message(LOG_ERROR, "(do-log-*): return-stack underflow");
vm->error = 1;
return;
}
uint8_t *data = (uint8_t *)(uintptr_t)vm->return_stack[vm->rsp];
if (!data) {
log_message(LOG_ERROR, "(do-log-*): NULL inline-string pointer");
vm->error = 1;
return;
}
uint8_t n = data[0];
/* advance IP before emitting so re-entrant calls see correct state */
size_t skip = 1 + (size_t)n;
size_t padded = (skip + (sizeof(cell_t) - 1)) & ~(sizeof(cell_t) - 1);
vm->return_stack[vm->rsp] = (cell_t)(uintptr_t)(data + padded);
log_message(level, "%.*s", (int)n, (const char *)(data + 1));
}
static void log_do_error(VM *vm) { log_do_emit(vm, LOG_ERROR); }
static void log_do_warn (VM *vm) { log_do_emit(vm, LOG_WARN); }
static void log_do_info (VM *vm) { log_do_emit(vm, LOG_INFO); }
static void log_do_test (VM *vm) { log_do_emit(vm, LOG_TEST); }
static void log_do_debug(VM *vm) { log_do_emit(vm, LOG_DEBUG); }
/* ── Shared parse/compile helper ─────────────────────────────────────── */
/*
* Parses a string literal "ccc" from the input stream (skipping an optional
* leading space, consuming up to the closing double-quote). In interpret
* mode emits directly via log_message(). In compile mode emits the named
* runtime word followed by the inline counted string block.
*/
static void log_emit_string(VM *vm, LogLevel level,
const char *rt_name, int rt_nlen)
{
const char *src = vm->input_buffer;
size_t pos = vm->input_pos;
size_t end = vm->input_length;
if (!src || pos > end) {
log_message(LOG_ERROR, "LOG-*\": no input buffer");
vm->error = 1;
return;
}
if (pos < end && src[pos] == ' ') pos++;
size_t start = pos;
while (pos < end && src[pos] != '"') pos++;
if (pos >= end) {
log_message(LOG_ERROR, "LOG-*\": unterminated string literal");
vm->error = 1;
return;
}
size_t n = pos - start;
if (n > 255) n = 255;
vm->input_pos = pos + 1;
if (vm->mode == MODE_INTERPRET) {
log_message(level, "%.*s", (int)n, src + start);
return;
}
/* Compilation: emit runtime word then inline [len][chars][pad] */
DictEntry *rt = vm_find_word(vm, rt_name, (size_t)rt_nlen);
if (!rt) {
log_message(LOG_ERROR, "LOG-*\": runtime word '%.*s' not found", rt_nlen, rt_name);
vm->error = 1;
return;
}
vm_compile_word(vm, rt);
size_t skip = 1 + n;
size_t padded = (skip + (sizeof(cell_t) - 1)) & ~(sizeof(cell_t) - 1);
uint8_t *raw = (uint8_t *)vm_allot(vm, padded);
if (!raw) {
log_message(LOG_ERROR, "LOG-*\": vm_allot failed");
vm->error = 1;
return;
}
raw[0] = (uint8_t)n;
for (size_t i = 0; i < n; i++) raw[1 + i] = (uint8_t)src[start + i];
for (size_t i = 1 + n; i < padded; i++) raw[i] = 0;
}
/* ── LOG-ERROR" LOG-WARN" LOG-INFO" LOG-TEST" LOG-DEBUG" (IMMEDIATE) ── */
/** @brief LOG-ERROR" ( "ccc<quote>" -- ) Log string at ERROR level. IMMEDIATE. */
static void log_word_error_quote(VM *vm) {
log_emit_string(vm, LOG_ERROR, "(do-log-error)", 14);
}
/** @brief LOG-WARN" ( "ccc<quote>" -- ) Log string at WARN level. IMMEDIATE. */
static void log_word_warn_quote(VM *vm) {
log_emit_string(vm, LOG_WARN, "(do-log-warn)", 13);
}
/** @brief LOG-INFO" ( "ccc<quote>" -- ) Log string at INFO level. IMMEDIATE. */
static void log_word_info_quote(VM *vm) {
log_emit_string(vm, LOG_INFO, "(do-log-info)", 13);
}
/** @brief LOG-TEST" ( "ccc<quote>" -- ) Log string at TEST level. IMMEDIATE. */
static void log_word_test_quote(VM *vm) {
log_emit_string(vm, LOG_TEST, "(do-log-test)", 13);
}
/** @brief LOG-DEBUG" ( "ccc<quote>" -- ) Log string at DEBUG level. IMMEDIATE. */
static void log_word_debug_quote(VM *vm) {
log_emit_string(vm, LOG_DEBUG, "(do-log-debug)", 14);
}
/* ── LOG-*-STR ( c-addr u -- ) ──────────────────────────────────────── */
/*
* Stack-consuming variants: take a counted string from the stack (as
* produced by S") and route it through log_message() at the named level.
* addr is a VM byte offset into vm->memory, identical to how TYPE works.
*/
static void log_str_emit(VM *vm, LogLevel level)
{
if (vm->dsp < 1) {
log_message(LOG_ERROR, "LOG-*-STR: stack underflow");
vm->error = 1;
return;
}
cell_t u = vm_pop(vm);
cell_t addr = vm_pop(vm);
if (u <= 0) return;
if (addr < 0 || (addr + u) > (cell_t)VM_MEMORY_SIZE) {
log_message(LOG_ERROR, "LOG-*-STR: address out of range");
vm->error = 1;
return;
}
if (u > LOG_LINE_MAX - 1) u = LOG_LINE_MAX - 1;
log_message(level, "%.*s", (int)u, (const char *)&vm->memory[addr]);
}
/** @brief LOG-ERROR-STR ( c-addr u -- ) Log string from stack at ERROR level. */
static void log_word_error_str(VM *vm) { log_str_emit(vm, LOG_ERROR); }
/** @brief LOG-WARN-STR ( c-addr u -- ) Log string from stack at WARN level. */
static void log_word_warn_str (VM *vm) { log_str_emit(vm, LOG_WARN); }
/** @brief LOG-INFO-STR ( c-addr u -- ) Log string from stack at INFO level. */
static void log_word_info_str (VM *vm) { log_str_emit(vm, LOG_INFO); }
/** @brief LOG-TEST-STR ( c-addr u -- ) Log string from stack at TEST level. */
static void log_word_test_str (VM *vm) { log_str_emit(vm, LOG_TEST); }
/** @brief LOG-DEBUG-STR ( c-addr u -- ) Log string from stack at DEBUG level. */
static void log_word_debug_str(VM *vm) { log_str_emit(vm, LOG_DEBUG); }
/* ── (LOG-APPEND-RAW) ( level timestamp msg-addr msg-u -- ) ── */
/*
* Kernel-only. Writes one record directly to Artemis's on-disk log ring
* (log_region.c) -- the primitive LOG-APPEND (capsules/artemis/init.4th)
* wraps this with the FORTH-level word name callers actually use.
* Deliberately does NOT call log_message() anywhere in this path (see
* log_region.c's own doc comment on why) -- errors are reported back via
* vm->error, never logged.
*
* Source is NOT a caller-supplied parameter -- an earlier revision took
* source-addr/source-u off the stack, which let any caller claim to be
* any source. It is derived from vm_log_attributed_vm() (log_attrib.h)
* instead: the real VM currently executing, looked up by name via the
* VM registry, or the fixed "HADES" pseudo-source when no VM is attributed
* (matching log_attrib.h's own documented convention).
*/
#ifdef __STARKERNEL__
static void log_word_append_raw(VM *vm)
{
/* Diagnostics here go via console_println(), not log_message() -- this
* function's own doc comment above forbids log_message() (recursion
* into the log ring it writes to); console_println() carries no such
* risk and covers the "by hand at the console for a smoke test" case
* that same comment names (FABRIC-3.md §XXXII.6). */
if (vm->dsp < 3) {
console_println("(LOG-APPEND-RAW): stack underflow");
vm->error = 1;
return;
}
cell_t msg_u = vm_pop(vm);
cell_t msg_addr = vm_pop(vm);
cell_t timestamp = vm_pop(vm);
cell_t level = vm_pop(vm);
if (msg_u < 0) {
console_println("(LOG-APPEND-RAW): negative message length");
vm->error = 1;
return;
}
if (msg_addr < 0 || (msg_addr + msg_u) > (cell_t)VM_MEMORY_SIZE) {
console_println("(LOG-APPEND-RAW): message address out of range");
vm->error = 1;
return;
}
const char *msg = (const char *)&vm->memory[msg_addr];
char source[VM_NAME_MAX];
VM *attrib = vm_log_attributed_vm();
VMRegistryEntry entry;
if (attrib && capsule_vm_registry_get(attrib->stadium_vm_id, &entry) == 0) {
size_t i = 0;
for (; i < sizeof(source) - 1 && entry.name[i]; i++) source[i] = entry.name[i];
source[i] = '\0';
} else {
const char *hades = "HADES";
size_t i = 0;
for (; i < sizeof(source) - 1 && hades[i]; i++) source[i] = hades[i];
source[i] = '\0';
}
/* log_region_append()'s return is three-valued (see log_region.h):
* 0 success, 1 dropped by design (protected a higher-priority record,
* not a fault -- no vm->error), -1 a genuine write failure. */
if (log_region_append((uint8_t)level, (uint64_t)timestamp,
source, (uint32_t)strlen(source),
msg, (uint32_t)msg_u) < 0) {
vm->error = 1;
}
}
#endif
/* ── Registration ────────────────────────────────────────────────────── */
/**
* @brief Register all log words with the VM dictionary.
*
* Level constants: LOG-ERROR LOG-WARN LOG-INFO LOG-TEST LOG-DEBUG
* Level control: LOG-LEVEL! LOG-LEVEL@
* Runtime (hidden): (do-log-error) (do-log-warn) (do-log-info)
* (do-log-test) (do-log-debug)
* String literals: LOG-ERROR" LOG-WARN" LOG-INFO" LOG-TEST" LOG-DEBUG"
* Stack string: LOG-ERROR-STR LOG-WARN-STR LOG-INFO-STR
* LOG-TEST-STR LOG-DEBUG-STR
*/
void register_log_words(VM *vm)
{
/* Level constants (unchanged) */
register_word(vm, "LOG-ERROR", log_word_error);
register_word(vm, "LOG-WARN", log_word_warn);
register_word(vm, "LOG-INFO", log_word_info);
register_word(vm, "LOG-TEST", log_word_test);
register_word(vm, "LOG-DEBUG", log_word_debug);
register_word(vm, "LOG-LEVEL!", log_word_set_level);
register_word(vm, "LOG-LEVEL@", log_word_get_level);
/* Runtime words for compiled LOG-*" — registered before the immediates
* that reference them so vm_find_word() can locate them at compile time */
register_word(vm, "(do-log-error)", log_do_error);
register_word(vm, "(do-log-warn)", log_do_warn);
register_word(vm, "(do-log-info)", log_do_info);
register_word(vm, "(do-log-test)", log_do_test);
register_word(vm, "(do-log-debug)", log_do_debug);
/* String-literal LOG-*" words — IMMEDIATE */
register_word(vm, "LOG-ERROR\"", log_word_error_quote); vm_make_immediate(vm);
register_word(vm, "LOG-WARN\"", log_word_warn_quote); vm_make_immediate(vm);
register_word(vm, "LOG-INFO\"", log_word_info_quote); vm_make_immediate(vm);
register_word(vm, "LOG-TEST\"", log_word_test_quote); vm_make_immediate(vm);
register_word(vm, "LOG-DEBUG\"", log_word_debug_quote); vm_make_immediate(vm);
/* Stack-consuming LOG-*-STR words */
register_word(vm, "LOG-ERROR-STR", log_word_error_str);
register_word(vm, "LOG-WARN-STR", log_word_warn_str);
register_word(vm, "LOG-INFO-STR", log_word_info_str);
register_word(vm, "LOG-TEST-STR", log_word_test_str);
register_word(vm, "LOG-DEBUG-STR", log_word_debug_str);
#ifdef __STARKERNEL__
register_word(vm, "(LOG-APPEND-RAW)", log_word_append_raw);
#endif
}
+454
View File
@@ -0,0 +1,454 @@
/*
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.
*/
/* logical_words.c - FORTH-79 Logical & Comparison Words */
#include "include/logical_words.h"
#include "../../include/log.h"
#include "../../include/word_registry.h"
/** @brief FORTH-79 TRUE value (-1, all bits set) */
#define FORTH_TRUE ((cell_t)-1)
/** @brief FORTH-79 FALSE value (0) */
#define FORTH_FALSE ((cell_t)0)
/**
* @brief FORTH word: AND ( n1 n2 -- n3 )
* @param vm Pointer to the virtual machine instance
*
* Performs bitwise AND operation on top two stack values.
* Stack effect: ( n1 n2 -- n3 ) where n3 = n1 AND n2
*/
static void logical_word_and(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "AND: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = n1 & n2;
vm_push(vm, result);
log_message(LOG_DEBUG, "AND: %ld AND %ld = %ld", (long) n1, (long) n2, (long) result);
}
/* OR - Bitwise OR ( n1 n2 -- n3 ) */
static void logical_word_or(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "OR: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = n1 | n2;
vm_push(vm, result);
log_message(LOG_DEBUG, "OR: %ld OR %ld = %ld", (long) n1, (long) n2, (long) result);
}
/* XOR - Bitwise XOR ( n1 n2 -- n3 ) */
static void logical_word_xor(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "XOR: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = n1 ^ n2;
vm_push(vm, result);
log_message(LOG_DEBUG, "XOR: %ld XOR %ld = %ld", (long) n1, (long) n2, (long) result);
}
/* NOT ( flag -- flag ) FORTH-79 logical NOT: 0 -> TRUE, non-zero -> FALSE */
static void logical_word_not(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "NOT: Stack underflow");
vm->error = 1;
return;
}
cell_t n1 = vm_pop(vm);
cell_t result = (n1 == 0) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, "NOT: %ld -> %ld", (long) n1, (long) result);
}
/* INVERT ( n1 -- n2 ) Bitwise complement (FORTH-83 extension) */
static void logical_word_invert(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "INVERT: Stack underflow");
vm->error = 1;
return;
}
cell_t n1 = vm_pop(vm);
vm_push(vm, ~n1);
}
/* LSHIFT ( x1 u -- x2 ) Logical left shift, u bits (FORTH-83 extension) */
static void logical_word_lshift(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "LSHIFT: Stack underflow");
vm->error = 1;
return;
}
ucell_t u = (ucell_t) vm_pop(vm);
ucell_t x1 = (ucell_t) vm_pop(vm);
if (u >= (ucell_t)(sizeof(cell_t) * 8)) {
log_message(LOG_ERROR, "LSHIFT: shift count %lu out of range", (unsigned long) u);
vm->error = 1;
return;
}
cell_t result = (cell_t)(x1 << u);
vm_push(vm, result);
log_message(LOG_DEBUG, "LSHIFT: %lu << %lu = %ld", (unsigned long) x1, (unsigned long) u, (long) result);
}
/* RSHIFT ( x1 u -- x2 ) Logical right shift, u bits (FORTH-83 extension) */
static void logical_word_rshift(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "RSHIFT: Stack underflow");
vm->error = 1;
return;
}
ucell_t u = (ucell_t) vm_pop(vm);
ucell_t x1 = (ucell_t) vm_pop(vm);
if (u >= (ucell_t)(sizeof(cell_t) * 8)) {
log_message(LOG_ERROR, "RSHIFT: shift count %lu out of range", (unsigned long) u);
vm->error = 1;
return;
}
cell_t result = (cell_t)(x1 >> u);
vm_push(vm, result);
log_message(LOG_DEBUG, "RSHIFT: %lu >> %lu = %ld", (unsigned long) x1, (unsigned long) u, (long) result);
}
/* 0= - Test for zero ( n -- flag ) */
static void logical_word_zero_equals(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "0=: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
cell_t result = (n == 0) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, "0=: %ld = 0? %s", (long) n, (result == FORTH_TRUE) ? "TRUE" : "FALSE");
}
/* 0< - Test for negative ( n -- flag ) */
static void logical_word_zero_less(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "0<: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
cell_t result = (n < 0) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, "0<: %ld < 0? %s", (long) n, (result == FORTH_TRUE) ? "TRUE" : "FALSE");
}
/* 0> - Test for positive ( n -- flag ) */
static void logical_word_zero_greater(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "0>: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
cell_t result = (n > 0) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, "0>: %ld > 0? %s", (long) n, (result == FORTH_TRUE) ? "TRUE" : "FALSE");
}
/* = - Test for equality ( n1 n2 -- flag ) */
static void logical_word_equals(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "=: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = (n1 == n2) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, "=: %ld = %ld? %s", (long) n1, (long) n2, (result == FORTH_TRUE) ? "TRUE" : "FALSE");
}
/* <> - Test for inequality ( n1 n2 -- flag ) */
static void logical_word_not_equals(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "<>: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = (n1 != n2) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, "<>: %ld <> %ld? %s", (long) n1, (long) n2, (result == FORTH_TRUE) ? "TRUE" : "FALSE");
}
/* < - Test for less than ( n1 n2 -- flag ) */
static void logical_word_less_than(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "<: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = (n1 < n2) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, "<: %ld < %ld? %s", (long) n1, (long) n2, (result == FORTH_TRUE) ? "TRUE" : "FALSE");
}
/* > - Test for greater than ( n1 n2 -- flag ) */
static void logical_word_greater_than(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, ">: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = (n1 > n2) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, ">: %ld > %ld? %s", (long) n1, (long) n2, (result == FORTH_TRUE) ? "TRUE" : "FALSE");
}
/* >= - Greater than or equal ( n1 n2 -- flag ) */
static void logical_word_greater_equal(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, ">=: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = (n1 >= n2) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, ">=: %ld >= %ld? %s", (long)n1, (long)n2,
(result == FORTH_TRUE) ? "TRUE" : "FALSE");
}
/* <= - Less than or equal ( n1 n2 -- flag ) */
static void logical_word_less_equal(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "<=: Stack underflow");
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
cell_t result = (n1 <= n2) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, "<=: %ld <= %ld? %s", (long)n1, (long)n2,
(result == FORTH_TRUE) ? "TRUE" : "FALSE");
}
/* U< - Unsigned less than ( u1 u2 -- flag ) */
static void logical_word_u_less_than(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "U<: Stack underflow");
vm->error = 1;
return;
}
uintptr_t u2 = (uintptr_t) vm_pop(vm);
uintptr_t u1 = (uintptr_t) vm_pop(vm);
cell_t result = (u1 < u2) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, "U<: %lu U< %lu? %s", (unsigned long) u1, (unsigned long) u2,
(result == FORTH_TRUE) ? "TRUE" : "FALSE");
}
/* U> - Unsigned greater than ( u1 u2 -- flag ) */
static void logical_word_u_greater_than(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "U>: Stack underflow");
vm->error = 1;
return;
}
uintptr_t u2 = (uintptr_t) vm_pop(vm);
uintptr_t u1 = (uintptr_t) vm_pop(vm);
cell_t result = (u1 > u2) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, "U>: %lu U> %lu? %s", (unsigned long) u1, (unsigned long) u2,
(result == FORTH_TRUE) ? "TRUE" : "FALSE");
}
/* WITHIN - Test if n is within bounds ( n low high -- flag ) */
static void logical_word_within(VM *vm) {
if (vm->dsp < 2) {
log_message(LOG_ERROR, "WITHIN: Stack underflow");
vm->error = 1;
return;
}
cell_t high = vm_pop(vm);
cell_t low = vm_pop(vm);
cell_t n = vm_pop(vm);
cell_t result = (n >= low && n < high) ? FORTH_TRUE : FORTH_FALSE;
vm_push(vm, result);
log_message(LOG_DEBUG, "WITHIN: %ld <= %ld < %ld? %s", (long) low, (long) n, (long) high,
(result == FORTH_TRUE) ? "TRUE" : "FALSE");
}
/* TRUE constant */
static void logical_word_true(VM *vm) {
vm_push(vm, FORTH_TRUE);
log_message(LOG_DEBUG, "TRUE: Pushed -1");
}
/* FALSE constant */
static void logical_word_false(VM *vm) {
vm_push(vm, FORTH_FALSE);
log_message(LOG_DEBUG, "FALSE: Pushed 0");
}
/* 0<> ( n -- f ) push TRUE (-1) if TOS != 0 else FALSE (0) */
void logical_word_zero_not_equal(VM *vm) {
if (vm->dsp < 0) {
/* underflow guard: need 1 item */
vm->error = 1;
log_message(LOG_ERROR, "0<>: stack underflow");
return;
}
cell_t x = vm_pop(vm);
vm_push(vm, x != 0 ? -1 : 0); /* Forth truth values: -1 true, 0 false */
}
/**
* @brief Registers all FORTH-79 logical and comparison words with the VM
* @param vm Pointer to the virtual machine instance
*
* Registers logical operations (AND, OR, XOR, NOT),
* comparison operations (=, <>, <, >, etc.),
* and constant words (TRUE, FALSE) with the virtual machine.
*/
void register_logical_words(VM *vm) {
/* Bitwise operations */
register_word(vm, "AND", logical_word_and);
register_word(vm, "OR", logical_word_or);
register_word(vm, "XOR", logical_word_xor);
register_word(vm, "NOT", logical_word_not);
register_word(vm, "INVERT", logical_word_invert);
register_word(vm, "LSHIFT", logical_word_lshift);
register_word(vm, "RSHIFT", logical_word_rshift);
/* Zero comparisons */
register_word(vm, "0=", logical_word_zero_equals);
register_word(vm, "0<", logical_word_zero_less);
register_word(vm, "0>", logical_word_zero_greater);
register_word(vm, "0<>", logical_word_zero_not_equal);
/* Comparisons */
register_word(vm, "=", logical_word_equals);
register_word(vm, "<>", logical_word_not_equals);
register_word(vm, "<", logical_word_less_than);
register_word(vm, ">", logical_word_greater_than);
register_word(vm, ">=", logical_word_greater_equal);
register_word(vm, "<=", logical_word_less_equal);
/* Unsigned comparisons */
register_word(vm, "U<", logical_word_u_less_than);
register_word(vm, "U>", logical_word_u_greater_than);
/* Range test */
register_word(vm, "WITHIN", logical_word_within);
/* Constants */
register_word(vm, "TRUE", logical_word_true);
register_word(vm, "FALSE", logical_word_false);
}
+368
View File
@@ -0,0 +1,368 @@
/*
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.
*/
#include "include/memory_words.h"
#include "../../include/word_registry.h"
#include "vm.h"
#include "../../include/log.h"
#include "include/block_words.h" /* blk_vm_is_cross_device_copy() -- Phase 8 v3 */
#include <string.h>
/**
* @brief Implements FORTH word '@' - Fetch cell from VM memory
*
* Stack effect: ( addr -- n )
* Fetches a cell value from the given memory address in VM memory.
*
* @param vm Pointer to the VM structure
*/
void memory_word_fetch(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
vaddr_t addr = VM_ADDR(vm_pop(vm));
if (!vm_addr_ok(vm, addr, sizeof(cell_t))) {
vm->error = 1;
return;
}
cell_t value = vm_load_cell(vm, addr);
vm_push(vm, value);
}
/**
* @brief Implements FORTH word '!' - Store cell into VM memory
*
* Stack effect: ( n addr -- )
* Stores a cell value at the given memory address in VM memory.
*
* @param vm Pointer to the VM structure
*/
void memory_word_store(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
vaddr_t addr = VM_ADDR(vm_pop(vm));
cell_t value = vm_pop(vm);
if (!vm_addr_ok(vm, addr, sizeof(cell_t))) {
vm->error = 1;
return;
}
vm_store_cell(vm, addr, value);
}
/**
* @brief Implements FORTH word 'C@' - Fetch byte from VM memory
*
* Stack effect: ( addr -- c )
* Fetches a byte value from the given memory address in VM memory.
*
* @param vm Pointer to the VM structure
*/
void memory_word_cfetch(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
vaddr_t addr = VM_ADDR(vm_pop(vm));
if (!vm_addr_ok(vm, addr, 1)) {
vm->error = 1;
return;
}
uint8_t value = vm_load_u8(vm, addr);
vm_push(vm, (cell_t) value);
}
/**
* @brief Implements FORTH word 'C!' - Store byte into VM memory
*
* Stack effect: ( c addr -- )
* Stores a byte value at the given memory address in VM memory.
*
* @param vm Pointer to the VM structure
*/
void memory_word_cstore(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
vaddr_t addr = VM_ADDR(vm_pop(vm));
cell_t value = vm_pop(vm);
if (!vm_addr_ok(vm, addr, 1)) {
vm->error = 1;
return;
}
vm_store_u8(vm, addr, (uint8_t)(value & 0xFF));
}
/**
* @brief Implements FORTH word '+!' - Add to contents of cell
*
* Stack effect: ( n addr -- )
* Adds n to the contents of the cell at the given address.
*
* @param vm Pointer to the VM structure
*/
void memory_word_plus_store(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
vaddr_t addr = VM_ADDR(vm_pop(vm));
cell_t n = vm_pop(vm);
if (!vm_addr_ok(vm, addr, sizeof(cell_t))) {
vm->error = 1;
return;
}
cell_t current = vm_load_cell(vm, addr);
vm_store_cell(vm, addr, current + n);
}
/**
* @brief Implements FORTH word '-!' - Subtract from contents of cell
*
* Stack effect: ( n addr -- )
* Subtracts n from the contents of the cell at the given address.
*
* @param vm Pointer to the VM structure
*/
void memory_word_minus_store(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
vaddr_t addr = VM_ADDR(vm_pop(vm));
cell_t n = vm_pop(vm);
if (!vm_addr_ok(vm, addr, sizeof(cell_t))) {
vm->error = 1;
return;
}
cell_t current = vm_load_cell(vm, addr);
vm_store_cell(vm, addr, current - n);
}
/**
* @brief Implements FORTH word 'FILL' - Fill memory with byte value
*
* Stack effect: ( addr len c -- )
* Fills len bytes of memory starting at addr with the value c.
*
* @param vm Pointer to the VM structure
*/
void memory_word_fill(VM *vm) {
if (vm->dsp < 2) {
vm->error = 1;
return;
}
cell_t c_val = vm_pop(vm);
size_t len = (size_t) vm_pop(vm);
cell_t addr_cell = vm_pop(vm);
/* Try VM address first */
vaddr_t addr = VM_ADDR(addr_cell);
if (vm_addr_ok(vm, addr, len)) {
/* It's a valid VM address, use vm_ptr */
uint8_t *ptr = vm_ptr(vm, addr);
memset(ptr, (int) (c_val & 0xFF), len);
} else {
/* It's an external pointer (from BLOCK/BUFFER), use it directly */
uint8_t *ptr = (uint8_t *) (uintptr_t) addr_cell;
memset(ptr, (int) (c_val & 0xFF), len);
}
}
/**
* @brief Implements FORTH word 'MOVE' - Copy memory region
*
* Stack effect: ( addr1 addr2 len -- )
* Copies len bytes from addr1 to addr2, handling overlapping regions correctly.
*
* @param vm Pointer to the VM structure
*/
void memory_word_move(VM *vm) {
if (vm->dsp < 2) {
vm->error = 1;
return;
}
size_t len = (size_t) vm_pop(vm);
vaddr_t addr2 = VM_ADDR(vm_pop(vm));
vaddr_t addr1 = VM_ADDR(vm_pop(vm));
if (!vm_addr_ok(vm, addr1, len) || !vm_addr_ok(vm, addr2, len)) {
vm->error = 1;
return;
}
/* Phase 8 v3 (2026-09-23): refuse a same-VM, cross-device raw block
* copy -- see blk_vm_is_cross_device_copy()'s own doc comment. */
if (blk_vm_is_cross_device_copy(vm, addr1, addr2)) {
vm->error = 1;
return;
}
uint8_t *src = vm_ptr(vm, addr1);
uint8_t *dst = vm_ptr(vm, addr2);
memmove(dst, src, len);
}
/**
* @brief Implements FORTH word 'ERASE' - Zero memory region
*
* Stack effect: ( addr len -- )
* Sets len bytes to zero starting at addr.
*
* @param vm Pointer to the VM structure
*/
void memory_word_erase(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
size_t len = (size_t) vm_pop(vm);
vaddr_t addr = VM_ADDR(vm_pop(vm));
if (!vm_addr_ok(vm, addr, len)) {
vm->error = 1;
return;
}
uint8_t *ptr = vm_ptr(vm, addr);
memset(ptr, 0, len);
}
/**
* @brief Implements FORTH word '2@' - Fetch double cell
*
* Stack effect: ( addr -- x_low x_high )
* Fetches two consecutive cells from memory, pushing low cell then high cell.
*
* @param vm Pointer to the VM structure
*/
void memory_word_2fetch(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
vaddr_t addr = VM_ADDR(vm_pop(vm));
const size_t sz = sizeof(cell_t);
/* need room for two cells starting at addr */
if (!vm_addr_ok(vm, addr, sz) || !vm_addr_ok(vm, addr + (vaddr_t) sz, sz)) {
vm->error = 1;
return;
}
cell_t low = vm_load_cell(vm, addr);
cell_t high = vm_load_cell(vm, addr + (vaddr_t) sz);
/* Push order: low first, high second (so high ends up on top) */
vm_push(vm, low);
vm_push(vm, high);
}
/**
* @brief Implements FORTH word '2!' - Store double cell
*
* Stack effect: ( x_low x_high addr -- )
* Stores two consecutive cells to memory, low cell at addr, high cell at addr+cell.
*
* @param vm Pointer to the VM structure
*/
void memory_word_2store(VM *vm) {
if (vm->dsp < 2) {
vm->error = 1;
return;
}
vaddr_t addr = VM_ADDR(vm_pop(vm)); /* top of stack: addr */
cell_t high = vm_pop(vm); /* next: high part */
cell_t low = vm_pop(vm); /* next: low part */
const size_t sz = sizeof(cell_t);
if (!vm_addr_ok(vm, addr, sz) || !vm_addr_ok(vm, addr + (vaddr_t) sz, sz)) {
vm->error = 1;
return;
}
vm_store_cell(vm, addr, low);
vm_store_cell(vm, addr + (vaddr_t) sz, high);
}
/* CELLS ( n -- n' ) Multiply by cell size (bytes per cell). */
/**
* @brief Implements FORTH word 'CELLS' - Scale by cell size
*
* Stack effect: ( n -- n' )
* Multiplies top of stack by cell size in bytes.
*
* @param vm Pointer to the VM structure
*/
static void memory_word_cells(VM *vm) {
if (!vm) return;
if (vm->dsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "CELLS: stack underflow");
return;
}
cell_t n = vm_pop(vm);
vm_push(vm, n * (cell_t) sizeof(cell_t));
}
/**
* @brief Register all memory manipulation words with the VM
*
* Registers all standard memory words (@, !, C@, C!, etc.) with the VM's word dictionary.
*
* @param vm Pointer to the VM structure
*/
void register_memory_words(VM *vm) {
register_word(vm, "@", memory_word_fetch);
register_word(vm, "!", memory_word_store);
register_word(vm, "C@", memory_word_cfetch);
register_word(vm, "C!", memory_word_cstore);
register_word(vm, "+!", memory_word_plus_store);
register_word(vm, "-!", memory_word_minus_store);
register_word(vm, "2@", memory_word_2fetch);
register_word(vm, "2!", memory_word_2store);
register_word(vm, "FILL", memory_word_fill);
register_word(vm, "MOVE", memory_word_move);
register_word(vm, "ERASE", memory_word_erase);
register_word(vm, "CELLS", memory_word_cells);
}
+410
View File
@@ -0,0 +1,410 @@
/*
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.
*/
/* mixed_arithmetic_words.c - FORTH-79 Mixed Arithmetic Words */
#include "include/mixed_arithmetic_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
/* Implementation of FORTH-79 Mixed Arithmetic Words */
/**
* @brief M+ ( d n -- d )
*
* Adds the single-cell signed integer @c n to the double-cell value @c d,
* producing a new double-cell result. Handles carry propagation: if the low
* cell addition overflows a positive @c n, the high cell is incremented; if it
* underflows a negative @c n, the high cell is decremented.
*
* Stack effect: ( d_high d_low n -- d_high' d_low' )
*
* @param vm Active VM; sets @c vm->error = 1 on stack underflow
*/
void mixed_math_word_m_plus(VM *vm) {
if (vm->dsp < 2) {
vm->error = 1;
return;
}
cell_t n = vm_pop(vm); // single
cell_t dlow = vm_pop(vm); // low part of double
cell_t dhigh = vm_pop(vm); // high part of double
cell_t old_dlow = dlow;
dlow += n;
if (n > 0 && dlow < old_dlow) {
dhigh++;
} else if (n < 0 && dlow > old_dlow) {
dhigh--;
}
// Push high first, then low (low is TOS)
vm_push(vm, dhigh);
vm_push(vm, dlow);
}
/**
* @brief M- ( d n -- d )
*
* Subtracts the single-cell signed integer @c n from the double-cell value @c d,
* producing a new double-cell result. Mirrors @c M+ carry logic: a positive @c n
* that causes the low cell to wrap below zero decrements the high cell, and vice versa.
*
* Stack effect: ( d_high d_low n -- d_high' d_low' )
*
* @param vm Active VM; sets @c vm->error = 1 on stack underflow
*/
void mixed_math_word_m_minus(VM *vm) {
if (vm->dsp < 2) {
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
cell_t dlow = vm_pop(vm);
cell_t dhigh = vm_pop(vm);
cell_t old_dlow = dlow;
dlow -= n;
if (n > 0 && dlow > old_dlow) {
dhigh--;
} else if (n < 0 && dlow < old_dlow) {
dhigh++;
}
vm_push(vm, dhigh);
vm_push(vm, dlow);
}
/**
* @brief M* ( n1 n2 -- d )
*
* Multiplies two single-cell signed integers, producing a double-cell result.
* A "double" here is two full @c cell_t-width cells (128 bits total on a
* 64-bit build), matching what D+/D-/D./etc. all expect -- NOT the low/high
* 32-bit halves of a single 64-bit product. The previous implementation
* confused the two: it computed an ordinary 64-bit @c long long product
* (already truncated/overflowed for any operand pair whose true product
* doesn't fit in 64 bits) and then split THAT into 32-bit low/high halves,
* as if @c cell_t were 32-bit. For a small negative product like
* `-123 456 M*` (-56088, which fits entirely in one 64-bit cell with sign
* extension), that produced a positive, zero-extended @c dlow paired with a
* correctly-looking @c dhigh=-1 -- a malformed double that D.'s overflow
* check (correctly) rejected as DOUBLE-OVERFLOW on every architecture,
* every time a negative product was printed (FABRIC-3.md §XII.4 bug 1).
*
* Fixed via @c __int128 (a genuine 64x64->128-bit multiply, no truncation)
* -- an established, safe pattern in this codebase for exactly this
* operation: see src/starkernel/crypto/fe25519.c and scalar25519.c, and
* src/starkernel/arch/amd64/timer.c's own doc comment confirming
* __int128 multiply/shift-by-constant compile cleanly with zero undefined
* symbols on all three target toolchains (only *division* needs libgcc
* support unavailable in this freestanding build -- not used here).
*
* Stack effect: ( n1 n2 -- d_low d_high ) TOS = high
*
* @param vm Active VM; sets @c vm->error = 1 on stack underflow
*/
void mixed_math_word_m_star(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
if (sizeof(cell_t) == 8) {
__int128 result = (__int128) n1 * (__int128) n2;
cell_t dlow = (cell_t)(unsigned __int128) result; // low 64 bits
cell_t dhigh = (cell_t)(result >> 64); // high 64 bits, sign-preserving
vm_push(vm, dlow); // lo first
vm_push(vm, dhigh); // hi last (TOS)
} else {
cell_t result = n1 * n2;
vm_push(vm, result);
vm_push(vm, 0);
}
}
/**
* @brief M/MOD ( d n -- rem quot )
*
* Divides the double-cell dividend @c d by the single-cell divisor @c n,
* pushing the remainder deeper and the quotient on TOS. A "double" here is
* two full @c cell_t-width cells (128 bits total on a 64-bit build) -- same
* convention as M* (see that word's own doc comment for the full story).
* The previous implementation reconstructed the dividend as a plain 64-bit
* @c long long from `(dhigh << 32) | (dlow & 0xFFFFFFFF)`, the same wrong
* "32-bit halves of a 64-bit value" assumption M* had -- silently discarding
* the upper 32 bits of @c dlow and losing @c dhigh entirely once it wasn't
* just 0 or -1. Latent for any dividend whose true magnitude fit in 32 bits
* (never exercised otherwise until M*'s own fix started producing genuine
* wide doubles to feed it -- FABRIC-3.md §XII.4).
*
* Fixed via a hand-rolled unsigned 128-bit-by-64-bit bit-serial (restoring)
* long division, not @c __int128's own `/`/`%` operators: those need
* libgcc's `__udivti3`/`__umodti3`, unavailable in this freestanding,
* `-nostdlib` build (confirmed in `src/starkernel/arch/amd64/timer.c`'s own
* doc comment -- `__int128` multiply/shift-by-constant/compare/subtract all
* compile cleanly with zero undefined symbols on all three toolchains, only
* division doesn't). Shift-by-1, compare, and subtract on `unsigned
* __int128` are all in that safe set, so the classic 128-iteration
* shift-and-subtract algorithm works without any libgcc dependency.
* Operates on magnitudes (converted via unsigned negation from 0, which is
* well-defined even for the extreme negative edge, unlike signed negation);
* signs are reapplied afterward matching C99 truncating-toward-zero
* division (quotient sign = XOR of operand signs, remainder sign = the
* dividend's own sign) -- the same convention the previous, narrower
* implementation already used via plain C `/`/`%`, unchanged here.
*
* On 32-bit builds, only handles the case where the high cell is zero; sets
* @c vm->error = 1 otherwise.
*
* Stack effect: ( d_high d_low n -- remainder quotient ) TOS = quotient
*
* @param vm Active VM; sets @c vm->error = 1 on underflow or division by zero
*/
void mixed_math_word_m_slash_mod(VM *vm) {
if (vm->dsp < 2) {
vm->error = 1;
return;
}
cell_t n = vm_pop(vm);
cell_t dlow = vm_pop(vm);
cell_t dhigh = vm_pop(vm);
if (n == 0) {
vm->error = 1;
return;
}
if (sizeof(cell_t) == 8) {
int dividend_neg = (dhigh < 0);
int divisor_neg = (n < 0);
unsigned __int128 udividend = ((unsigned __int128) (ucell_t) dhigh << 64) |
(unsigned __int128) (ucell_t) dlow;
if (dividend_neg) udividend = (unsigned __int128) 0 - udividend;
ucell_t udivisor = (ucell_t) n;
if (divisor_neg) udivisor = (ucell_t) 0 - udivisor;
unsigned __int128 uquot = 0, urem = 0;
for (int i = 127; i >= 0; i--) {
urem = (urem << 1) | ((udividend >> i) & 1);
if (urem >= (unsigned __int128) udivisor) {
urem -= (unsigned __int128) udivisor;
uquot |= ((unsigned __int128) 1 << i);
}
}
cell_t quotient = (dividend_neg ^ divisor_neg) ? -(cell_t) uquot : (cell_t) uquot;
cell_t remainder = dividend_neg ? -(cell_t) urem : (cell_t) urem;
vm_push(vm, remainder); // remainder first (deeper)
vm_push(vm, quotient); // quotient last (TOS)
} else {
if (dhigh == 0) {
vm_push(vm, dlow % n);
vm_push(vm, dlow / n);
} else {
vm->error = 1; // 128-bit not yet implemented
}
}
}
/**
* @brief MOD ( n1 n2 -- r )
*
* Computes @c n1 % @c n2, leaving the remainder on the stack. Sets
* @c vm->error = 1 if @c n2 is zero (division by zero guard).
*
* Stack effect: ( n1 n2 -- remainder )
*
* @param vm Active VM; sets @c vm->error = 1 on underflow or zero divisor
*/
void mixed_math_word_mod(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
if (n2 == 0) {
vm->error = 1;
return;
}
vm_push(vm, n1 % n2);
}
/**
* @brief /MOD ( n1 n2 -- rem quot )
*
* Divides @c n1 by @c n2, pushing the remainder deeper and the quotient on TOS.
* Sets @c vm->error = 1 if @c n2 is zero.
*
* Stack effect: ( n1 n2 -- remainder quotient ) TOS = quotient
*
* @param vm Active VM; sets @c vm->error = 1 on underflow or zero divisor
*/
void mixed_math_word_slash_mod(VM *vm) {
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
if (n2 == 0) {
vm->error = 1;
return;
}
cell_t quotient = n1 / n2;
cell_t remainder = n1 % n2;
vm_push(vm, remainder); // deeper
vm_push(vm, quotient); // TOS
}
/**
* @brief STAR-SLASH (FORTH word: @c *-/ ) — multiply then divide. ( n1 n2 n3 -- n4 )
*
* Computes @c (n1 * n2) / n3 using a @c long long intermediate on 64-bit builds
* to avoid intermediate overflow. On 32-bit builds falls back to @c long double.
* Sets @c vm->error = 1 if @c n3 is zero.
*
* Stack effect: ( n1 n2 n3 -- quotient )
*
* @param vm Active VM; sets @c vm->error = 1 on underflow or zero divisor
*/
void mixed_math_word_star_slash(VM *vm) {
if (vm->dsp < 2) {
vm->error = 1;
return;
}
cell_t n3 = vm_pop(vm);
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
if (n3 == 0) {
vm->error = 1;
return;
}
if (sizeof(cell_t) == 8) {
long long intermediate = (long long) n1 * (long long) n2;
vm_push(vm, (cell_t)(intermediate / n3));
} else {
long double intermediate = (long double) n1 * (long double) n2;
vm_push(vm, (cell_t)(intermediate / n3));
}
}
/**
* @brief STAR-SLASH-MOD (FORTH word: @c *-/MOD ) — multiply then divide, leaving remainder. ( n1 n2 n3 -- rem quot )
*
* Computes @c (n1 * n2) / n3 and leaves both the remainder (deeper) and
* quotient (TOS). Uses @c long long on 64-bit builds; falls back to
* @c long double with integer remainder reconstruction on 32-bit builds.
* Sets @c vm->error = 1 if @c n3 is zero.
*
* Stack effect: ( n1 n2 n3 -- remainder quotient ) TOS = quotient
*
* @param vm Active VM; sets @c vm->error = 1 on underflow or zero divisor
*/
void mixed_math_word_star_slash_mod(VM *vm) {
if (vm->dsp < 2) {
vm->error = 1;
return;
}
cell_t n3 = vm_pop(vm);
cell_t n2 = vm_pop(vm);
cell_t n1 = vm_pop(vm);
if (n3 == 0) {
vm->error = 1;
return;
}
if (sizeof(cell_t) == 8) {
long long intermediate = (long long) n1 * (long long) n2;
cell_t quotient = (cell_t)(intermediate / n3);
cell_t remainder = (cell_t)(intermediate % n3);
vm_push(vm, remainder); // remainder deeper
vm_push(vm, quotient); // quotient TOS
} else {
long double intermediate = (long double) n1 * (long double) n2;
cell_t quotient = (cell_t)(intermediate / n3);
cell_t remainder = n1 * n2 - quotient * n3;
vm_push(vm, remainder);
vm_push(vm, quotient);
}
}
/**
* @brief Register all FORTH-79 mixed arithmetic words with the VM dictionary.
*
* Registers: @c M+, @c M-, @c M*, @c M/MOD, @c MOD, @c /MOD, @c *\/,
* @c *\/MOD. Called during VM bootstrap.
*
* @param vm Active VM to register words into
*/
void register_mixed_arithmetic_words(VM *vm) {
register_word(vm, "M+", mixed_math_word_m_plus);
register_word(vm, "M-", mixed_math_word_m_minus);
register_word(vm, "M*", mixed_math_word_m_star);
register_word(vm, "M/MOD", mixed_math_word_m_slash_mod);
register_word(vm, "MOD", mixed_math_word_mod);
register_word(vm, "/MOD", mixed_math_word_slash_mod);
register_word(vm, "*/", mixed_math_word_star_slash);
register_word(vm, "*/MOD", mixed_math_word_star_slash_mod);
}
@@ -0,0 +1,359 @@
/*
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_benchmark_words.c
*
* Benchmark words for measuring hot-words cache impact
*
* Usage:
* BENCH-DICT-LOOKUP (run before/after comparison)
* PHYSICS-CACHE-STATS (show detailed statistics)
* PHYSICS-TOGGLE-CACHE (enable/disable at runtime)
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "vm.h"
#include "physics_hotwords_cache.h"
#include "platform_time.h"
#include "word_registry.h"
/* Forward declarations */
extern DictEntry **sf_fc_list[256];
extern size_t sf_fc_count[256];
/* ============================================================================
* Benchmark: Dictionary Lookup Performance
* ============================================================================
*/
/**
* BENCH-DICT-LOOKUP ( iterations -- )
*
* Benchmark dictionary lookup performance with statistically valid sample sizes.
*
* STATISTICAL VALIDITY:
* - Minimum recommended: 10,000 iterations (95% confidence interval)
* - Standard test: 100,000 iterations (tight confidence intervals)
* - Stress test: 1,000,000 iterations (ultra-precise measurements)
*
* FOR BAYESIAN INFERENCE:
* - All latencies recorded in 64-bit Q48.16 fixed-point format
* - Variance accumulators enable prior/posterior calculations
* - Sample sizes affect confidence in ML decisions
*
* USAGE:
* 100000 BENCH-DICT-LOOKUP (statistically valid baseline)
* 1000000 BENCH-DICT-LOOKUP (stress test, ~30sec on modern CPUs)
*/
void forth_BENCH_DICT_LOOKUP(VM *vm) {
if (vm->error) return;
if (vm->dsp < 1) {
vm->error = 1;
printf("BENCH-DICT-LOOKUP: Need iteration count on stack\n");
return;
}
cell_t iterations = vm->data_stack[--vm->dsp];
if (iterations < 1) {
printf("BENCH-DICT-LOOKUP: iterations must be >= 1\n");
return;
}
/* Warn if sample size is too small for statistical validity */
if (iterations < 10000) {
printf("⚠️ WARNING: %ld iterations < 10,000 (minimum for 95%% confidence)\n", iterations);
printf(" Consider using >= 100,000 for statistically valid results_run_01_2025_12_08\n\n");
}
// Test words: common in FORTH programs (balanced distribution)
const char *test_words[] = {
"IF", "THEN", "ELSE", "DO", "LOOP", "+LOOP",
"DUP", "DROP", "SWAP", "OVER", "ROT",
"+", "-", "*", "/", "MOD",
"@", "!", "C@", "C!",
"EMIT", "KEY", ".",
NULL
};
printf("\n╔════════════════════════════════════════════════════════════════╗\n");
printf("║ Dictionary Lookup Benchmark (Statistically Valid Sample) ║\n");
printf("║ Iterations: %ld (confidence: ", iterations);
if (iterations >= 1000000) printf("99.9%%) [STRESS TEST]\n");
else if (iterations >= 100000) printf("99%%) [STANDARD]\n");
else if (iterations >= 10000) printf("95%%) [MINIMUM]\n");
else printf("LOW) [UNRELIABLE]\n");
printf("╚════════════════════════════════════════════════════════════════╝\n\n");
// Reset stats before benchmark
if (!vm->hotwords_cache) {
printf("BENCH-DICT-LOOKUP: Cache not initialized\n");
return;
}
hotwords_stats_reset(&vm->hotwords_cache->stats);
uint64_t start = sf_monotonic_ns();
// Repeatedly lookup test words
int word_idx = 0;
for (cell_t i = 0; i < iterations; i++) {
const char *word_name = test_words[word_idx];
if (!word_name) {
word_idx = 0;
word_name = test_words[word_idx];
}
// Perform lookup - all latencies recorded with 64-bit fixed-point precision
DictEntry *found = vm_find_word(vm, word_name, strlen(word_name));
if (!found) {
printf("Warning: Word '%s' not found\n", word_name);
}
word_idx++;
}
uint64_t elapsed = sf_monotonic_ns() - start;
double elapsed_ms = (double)elapsed / 1000000.0;
double avg_lookup_us = (double)elapsed / (double)iterations / 1000.0;
printf("WALL-CLOCK TIMING:\n");
printf(" Total time: %.2f ms\n", elapsed_ms);
printf(" Avg per lookup: %.3f µs\n", avg_lookup_us);
printf(" Lookups/sec: %.0f\n", 1000000.0 / avg_lookup_us);
// Show detailed cache stats if enabled (includes 64-bit fixed-point statistics)
if (ENABLE_HOTWORDS_CACHE) {
printf("\n");
hotwords_stats_print(&vm->hotwords_cache->stats);
} else {
printf(" (Hot-words cache disabled at compile time)\n\n");
}
}
/* ============================================================================
* Diagnostic: Show Cache Statistics
* ============================================================================
*/
/**
* PHYSICS-CACHE-STATS ( -- )
*
* Display detailed hot-words cache statistics
*/
void forth_PHYSICS_CACHE_STATS(VM *vm) {
if (vm->error) return;
if (!ENABLE_HOTWORDS_CACHE) {
printf("Hot-words cache is disabled at compile time.\n");
printf("Rebuild with: make clean && make ENABLE_HOTWORDS_CACHE=1\n");
return;
}
if (!vm->hotwords_cache) {
printf("PHYSICS-CACHE-STATS: Cache not initialized\n");
return;
}
hotwords_stats_print(&vm->hotwords_cache->stats);
printf("CACHE CONTENTS:\n");
for (uint32_t i = 0; i < vm->hotwords_cache->cache_count; i++) {
DictEntry *w = vm->hotwords_cache->cache[i];
if (!w) continue;
printf(" [%2u] %s (execution_heat=%ld, temp=0x%04x)\n",
i, w->name, w->execution_heat, w->physics.temperature_q8);
}
printf("\n");
}
/* ============================================================================
* Control: Toggle Cache at Runtime
* ============================================================================
*/
/**
* PHYSICS-TOGGLE-CACHE ( -- )
*
* Enable/disable hot-words cache at runtime (for A/B testing)
*/
void forth_PHYSICS_TOGGLE_CACHE(VM *vm) {
if (vm->error) return;
if (!ENABLE_HOTWORDS_CACHE) {
printf("Hot-words cache is disabled at compile time.\n");
return;
}
if (!vm->hotwords_cache) {
printf("PHYSICS-TOGGLE-CACHE: Cache not initialized\n");
return;
}
int was_enabled = vm->hotwords_cache->enabled;
hotwords_cache_set_enabled(vm->hotwords_cache, !was_enabled);
printf("Hot-words cache: %s → %s\n",
was_enabled ? "ENABLED" : "DISABLED",
!was_enabled ? "ENABLED" : "DISABLED");
}
/**
* PHYSICS-RESET-STATS ( -- )
*
* Reset cache statistics for clean before/after comparison
*/
void forth_PHYSICS_RESET_STATS(VM *vm) {
if (vm->error) return;
if (!vm->hotwords_cache) {
printf("PHYSICS-RESET-STATS: Cache not initialized\n");
return;
}
hotwords_stats_reset(&vm->hotwords_cache->stats);
/* Reset pipeline metrics for clean per-run measurement */
vm->pipeline_metrics.prefetch_hits = 0;
vm->pipeline_metrics.prefetch_attempts = 0;
vm->pipeline_metrics.window_tuning_checks = 0;
printf("Cache statistics reset.\n");
}
/* ============================================================================
* Helper: Show Current Build Configuration
* ============================================================================
*/
/**
* PHYSICS-BUILD-INFO ( -- )
*
* Show current build configuration for this variant
*/
void forth_PHYSICS_BUILD_INFO(VM *vm) {
if (vm->error) return;
printf("\n═══════════════════════════════════════════════════════════════\n");
printf("Physics System Build Configuration\n");
printf("═══════════════════════════════════════════════════════════════\n");
printf("Hot-Words Cache: %s\n",
ENABLE_HOTWORDS_CACHE ? "ENABLED" : "DISABLED");
printf("Cache Size: %d entries\n", HOTWORDS_CACHE_SIZE);
printf("Execution Heat Threshold: %d\n", HOTWORDS_EXECUTION_HEAT_THRESHOLD);
printf("Reorder Threshold: %d\n", HOTWORDS_EXECUTION_HEAT_DELTA_THRESHOLD);
printf("\nCurrent State:\n");
if (vm->hotwords_cache) {
printf("Cache enabled: %s\n",
vm->hotwords_cache->enabled ? "YES" : "NO");
printf("Cached entries: %u / %d\n",
vm->hotwords_cache->cache_count, HOTWORDS_CACHE_SIZE);
} else {
printf("Cache enabled: (not initialized)\n");
printf("Cached entries: (not initialized)\n");
}
printf("\n═══════════════════════════════════════════════════════════════\n\n");
}
/* ============================================================================
* Word Registration
* ============================================================================
*/
/**
* PHYSICS-BAYESIAN-REPORT ( addr_baseline -- )
*
* Generate Bayesian inference report comparing current cache stats
* against baseline stats stored at given address
*
* Usage:
* PHYSICS-RESET-STATS
* [run baseline benchmark without cache]
* [save stats somewhere]
* PHYSICS-RESET-STATS
* [run benchmark with cache]
* PHYSICS-BAYESIAN-REPORT
*/
void forth_PHYSICS_BAYESIAN_REPORT(VM *vm) {
if (vm->error) return;
if (!vm->hotwords_cache) {
printf("PHYSICS-BAYESIAN-REPORT: Cache not initialized\n");
return;
}
/* Generate comparison report with hardcoded baseline for now */
/* In production, would compare against externally provided baseline */
printf("\nGenerating Bayesian inference report...\n");
printf("(Report uses current cache stats)\n");
/* For now, use a synthetic baseline for demonstration */
HotwordsStats baseline_stats = {0};
/* Assume baseline: all lookups go to bucket (no cache) */
/* Use bucket search stats from current run as proxy for baseline */
baseline_stats.bucket_hits = vm->hotwords_cache->stats.cache_hits +
vm->hotwords_cache->stats.bucket_hits;
baseline_stats.bucket_search_total_ns_q48 = vm->hotwords_cache->stats.bucket_search_total_ns_q48;
baseline_stats.bucket_search_variance_sum_q48 = vm->hotwords_cache->stats.bucket_search_variance_sum_q48;
baseline_stats.min_bucket_search_ns = vm->hotwords_cache->stats.min_bucket_search_ns;
baseline_stats.max_bucket_search_ns = vm->hotwords_cache->stats.max_bucket_search_ns;
baseline_stats.bucket_search_samples = vm->hotwords_cache->stats.bucket_search_samples;
hotwords_bayesian_report(&vm->hotwords_cache->stats, &baseline_stats);
}
void register_physics_benchmark_words(VM *vm) {
register_word(vm, "BENCH-DICT-LOOKUP", forth_BENCH_DICT_LOOKUP);
register_word(vm, "PHYSICS-CACHE-STATS", forth_PHYSICS_CACHE_STATS);
register_word(vm, "PHYSICS-TOGGLE-CACHE", forth_PHYSICS_TOGGLE_CACHE);
register_word(vm, "PHYSICS-RESET-STATS", forth_PHYSICS_RESET_STATS);
register_word(vm, "PHYSICS-BUILD-INFO", forth_PHYSICS_BUILD_INFO);
register_word(vm, "PHYSICS-BAYESIAN-REPORT", forth_PHYSICS_BAYESIAN_REPORT);
}
@@ -0,0 +1,445 @@
/*
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_diagnostic_words.c
*
* Interactive FORTH diagnostic words for live physics feedback loop demonstration.
* These words allow you to:
*
* 1. Define a test word
* 2. Execute it repeatedly
* 3. Watch metrics (temperature, execution heat, latency) update in real time
* 4. Calculate knob adjustments based on physics math
* 5. See how the system responds to changing conditions
*
* Usage in REPL:
* : HOTTEST 100 0 DO 1 2 + 3 * LOOP ;
* HOTTEST
* PHYSICS-WATCH-WORD
* PHYSICS-CALC-KNOBS
* PHYSICS-SHOW-FEEDBACK
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include "vm.h"
#include "physics_metadata.h"
#include "word_registry.h"
/* ============================================================================
* Diagnostic: Show Word Metrics
* ============================================================================
*/
/**
* PHYSICS-WORD-METRICS ( -- )
*
* Display detailed physics metrics for a word.
* Looks up the most recently executed word and shows its state.
*/
void forth_PHYSICS_WORD_METRICS(VM *vm) {
if (vm->error) return;
// Find the most recently executed word (highest last_active_ns)
DictEntry *most_recent = NULL;
uint64_t max_active = 0;
for (DictEntry *w = vm->latest; w; w = w->link) {
if (w->physics.last_active_ns > max_active) {
max_active = w->physics.last_active_ns;
most_recent = w;
}
}
if (!most_recent) {
printf("No words executed yet.\n");
return;
}
printf("\n=== Physics Metrics: %s ===\n", most_recent->name);
printf("Temperature (Q8): 0x%04x\n", most_recent->physics.temperature_q8);
printf("Execution Heat: %ld\n", most_recent->execution_heat);
printf("Avg Latency: %u ns\n", most_recent->physics.avg_latency_ns);
printf("Mass (bytes): %u\n", most_recent->physics.mass_bytes);
printf("Last Active: %lu ns\n", most_recent->physics.last_active_ns);
// Calculate useful derived metrics
uint32_t baseline_temp = 0x2000;
uint32_t max_temp = 0xFFFF;
int16_t temp_delta = (int16_t)most_recent->physics.temperature_q8 - (int16_t)baseline_temp;
float thermal_pressure = (float)temp_delta / (float)(max_temp - baseline_temp);
if (thermal_pressure < 0.0f) thermal_pressure = 0.0f;
if (thermal_pressure > 1.0f) thermal_pressure = 1.0f;
printf("\nDerived:\n");
printf("Thermal Pressure: %.3f (0.0=cold, 1.0=hot)\n", thermal_pressure);
printf("Normalized Temp: %.2f%% (0%%=baseline, 100%%=max)\n",
thermal_pressure * 100.0f);
}
/* ============================================================================
* Diagnostic: Calculate Knob Adjustments
* ============================================================================
*/
/**
* PHYSICS-CALC-KNOBS ( -- )
*
* Calculate what knobs should be adjusted based on current word metrics.
* Shows:
* - Thermal pressure (0.0 to 1.0)
* - Recommended priority adjustment
* - Recommended sampling rate
* - Stack depth limit
* - Core affinity preference
*/
void forth_PHYSICS_CALC_KNOBS(VM *vm) {
if (vm->error) return;
// Find most recent word
DictEntry *most_recent = NULL;
uint64_t max_active = 0;
for (DictEntry *w = vm->latest; w; w = w->link) {
if (w->physics.last_active_ns > max_active) {
max_active = w->physics.last_active_ns;
most_recent = w;
}
}
if (!most_recent) {
printf("No words executed yet.\n");
return;
}
// --- Calculate thermal pressure ---
uint16_t baseline_temp = 0x2000;
uint16_t max_temp = 0xFFFF;
uint16_t current_temp = most_recent->physics.temperature_q8;
int16_t temp_delta = (int16_t)current_temp - (int16_t)baseline_temp;
uint16_t temp_range = max_temp - baseline_temp;
float thermal_pressure = (float)temp_delta / (float)temp_range;
if (thermal_pressure < 0.0f) thermal_pressure = 0.0f;
if (thermal_pressure > 1.0f) thermal_pressure = 1.0f;
printf("\n=== Knob Calculations: %s ===\n", most_recent->name);
printf("\nINPUT METRICS:\n");
printf(" Temperature: 0x%04x\n", current_temp);
printf(" Thermal Pressure: %.3f\n", thermal_pressure);
// --- KNOB 1: Priority ---
int base_priority = 0;
int max_priority_boost = -20;
int priority_boost = (int)(thermal_pressure * max_priority_boost);
int adjusted_priority = base_priority + priority_boost;
printf("\nKNOB 1: OS Priority\n");
printf(" Formula: priority = base + (thermal × max_boost)\n");
printf(" Base priority: %d\n", base_priority);
printf(" Max boost: %d\n", max_priority_boost);
printf(" Adjustment: %d\n", priority_boost);
printf(" ➜ ADJUSTED: %d %s\n", adjusted_priority,
thermal_pressure > 0.5f ? "(HIGH PRIORITY)" : "(normal)");
// --- KNOB 2: Sampling Rate ---
uint32_t normal_sampling = 100;
uint32_t high_temp_sampling = 10;
uint32_t adjusted_sampling =
(uint32_t)(normal_sampling - thermal_pressure * (normal_sampling - high_temp_sampling));
printf("\nKNOB 2: Sampling Rate\n");
printf(" Formula: sampling = normal - (thermal × (normal - high_temp))\n");
printf(" Normal: %u%%\n", normal_sampling);
printf(" High-temp: %u%%\n", high_temp_sampling);
printf(" ➜ ADJUSTED: %u%% %s\n", adjusted_sampling,
adjusted_sampling < 50 ? "(LOW OVERHEAD)" : "(normal)");
// --- KNOB 3: Stack Depth Limit ---
uint32_t normal_stack_limit = 256;
uint32_t min_stack_limit = 16;
uint32_t adjusted_stack_limit =
(uint32_t)(normal_stack_limit - thermal_pressure * (normal_stack_limit - min_stack_limit));
printf("\nKNOB 3: Stack Depth Limit\n");
printf(" Formula: limit = normal - (thermal × (normal - min))\n");
printf(" Normal: %u\n", normal_stack_limit);
printf(" Minimum: %u\n", min_stack_limit);
printf(" ➜ ADJUSTED: %u %s\n", adjusted_stack_limit,
adjusted_stack_limit < 100 ? "(PREVENTS RUNAWAY)" : "(no limit)");
// --- KNOB 4: Cache Affinity ---
int preferred_core = (thermal_pressure > 0.5f) ? 0 : -1;
printf("\nKNOB 4: Cache Affinity\n");
printf(" If thermal_pressure > 0.5: pin to core 0\n");
printf(" ➜ PREFERRED CORE: %s\n", preferred_core >= 0 ? "0 (PINNED)" : "any (no preference)");
// --- Summary ---
printf("\n=== SUMMARY ===\n");
printf("If executed with these knobs:\n");
printf(" Priority boosted: %s\n", thermal_pressure > 0.3f ? "YES" : "no");
printf(" Sampling reduced: %s\n", adjusted_sampling < 80 ? "YES" : "no");
printf(" Stack limited: %s\n", adjusted_stack_limit < 200 ? "YES" : "no");
printf(" Core pinned: %s\n", preferred_core >= 0 ? "YES" : "no");
printf("\nEffect: %s execution with %s overhead.\n",
thermal_pressure > 0.5f ? "OPTIMIZED" : "Normal",
adjusted_sampling < 50 ? "MINIMAL" : "standard");
}
/* ============================================================================
* Diagnostic: Run Repeated Execution and Show Feedback
* ============================================================================
*/
/**
* PHYSICS-BURN ( n -- )
*
* Execute the top-of-stack word n times and show thermal feedback.
*
* Usage:
* : HOTTEST 100 0 DO 1 2 + 3 * LOOP ;
* HOTTEST
* 10 PHYSICS-BURN (execute 10 times)
* PHYSICS-SHOW-FEEDBACK
*/
void forth_PHYSICS_BURN(VM *vm) {
if (vm->error) return;
if (vm->dsp < 1) {
vm->error = 1;
printf("PHYSICS-BURN: Need count on stack\n");
return;
}
cell_t burn_count = vm->data_stack[--vm->dsp];
if (burn_count < 1) {
printf("PHYSICS-BURN: count must be >= 1\n");
return;
}
// Find most recent word to burn
DictEntry *target = NULL;
uint64_t max_active = 0;
for (DictEntry *w = vm->latest; w; w = w->link) {
if (w->physics.last_active_ns > max_active) {
max_active = w->physics.last_active_ns;
target = w;
}
}
if (!target || !target->func) {
printf("PHYSICS-BURN: No word to burn\n");
return;
}
printf("\nBurning: %s × %ld times\n", target->name, burn_count);
// Record starting state
uint16_t start_temp = target->physics.temperature_q8;
uint64_t start_heat = target->execution_heat;
// Execute repeatedly
for (cell_t i = 0; i < burn_count; i++) {
target->func(vm);
if (vm->error) {
printf("Error during burn iteration %ld\n", i);
vm->error = 0; // Continue
}
// Show progress every 10% or every iteration if small count
if (burn_count <= 10 || (i + 1) % (burn_count / 10 + 1) == 0) {
uint16_t current_temp = target->physics.temperature_q8;
uint64_t current_heat = target->execution_heat;
float temp_change = (float)((int16_t)current_temp - (int16_t)start_temp);
printf(" [%3ld/%ld] Temp: 0x%04x (Δ %+.0f), Execution Heat: %ld (Δ %ld)\n",
i + 1, burn_count, current_temp, temp_change,
current_heat, current_heat - start_heat);
}
}
// Show final state
uint16_t final_temp = target->physics.temperature_q8;
uint64_t final_heat = target->execution_heat;
printf("\nBurn Complete:\n");
printf(" Start temp: 0x%04x\n", start_temp);
printf(" Final temp: 0x%04x\n", final_temp);
printf(" Change: %+d (Δ %.1f%%)\n", (int16_t)final_temp - (int16_t)start_temp,
100.0f * ((float)final_temp - (float)start_temp) / (float)start_temp);
printf(" Execution heat increased: %ld → %ld (Δ %ld)\n",
start_heat, final_heat, final_heat - start_heat);
}
/* ============================================================================
* Diagnostic: Show Complete Feedback Loop
* ============================================================================
*/
/**
* PHYSICS-SHOW-FEEDBACK ( -- )
*
* Display the complete feedback loop for the most recent word.
* Shows: Metrics → Calculations → Knobs → Effect
*/
void forth_PHYSICS_SHOW_FEEDBACK(VM *vm) {
if (vm->error) return;
printf("\n╔════════════════════════════════════════════════════════════════╗\n");
printf("║ Live Feedback Loop Demonstration ║\n");
printf("╚════════════════════════════════════════════════════════════════╝\n");
// Find most recent word
DictEntry *most_recent = NULL;
uint64_t max_active = 0;
for (DictEntry *w = vm->latest; w; w = w->link) {
if (w->physics.last_active_ns > max_active) {
max_active = w->physics.last_active_ns;
most_recent = w;
}
}
if (!most_recent) {
printf("No words executed yet.\n");
return;
}
printf("\nSTEP 1: GRAB INPUTS\n");
printf("════════════════════════════════════════════════════════════════\n");
printf("Word: %s\n", most_recent->name);
printf(" temperature_q8 = 0x%04x\n", most_recent->physics.temperature_q8);
printf(" execution_heat = %ld (execution count)\n", most_recent->execution_heat);
printf(" avg_latency_ns = %u ns\n", most_recent->physics.avg_latency_ns);
printf(" mass_bytes = %u\n", most_recent->physics.mass_bytes);
// Calculate thermal pressure
uint16_t baseline = 0x2000;
uint16_t current = most_recent->physics.temperature_q8;
float pressure = (float)((int16_t)current - (int16_t)baseline) / (float)(0xFFFF - baseline);
if (pressure < 0.0f) pressure = 0.0f;
if (pressure > 1.0f) pressure = 1.0f;
printf("\nSTEP 2: APPLY MATHEMATICS\n");
printf("════════════════════════════════════════════════════════════════\n");
printf("thermal_pressure = (temp - baseline) / max\n");
printf(" = (0x%04x - 0x%04x) / 0xFFFF\n", current, baseline);
printf(" = %.3f\n", pressure);
printf("\nSTEP 3: CALCULATE KNOB ADJUSTMENTS\n");
printf("════════════════════════════════════════════════════════════════\n");
// Priority
int priority = (int)(pressure * -20);
printf("priority_adjust = thermal_pressure × (-20)\n");
printf(" = %.3f × (-20) = %d\n", pressure, priority);
// Sampling
uint32_t sampling = (uint32_t)(100 - pressure * 90);
printf("sampling = 100 - (thermal_pressure × 90)\n");
printf(" = 100 - (%.3f × 90) = %u%%\n", pressure, sampling);
// Stack
uint32_t stack_limit = (uint32_t)(256 - pressure * 240);
printf("stack_limit = 256 - (thermal_pressure × 240)\n");
printf(" = 256 - (%.3f × 240) = %u\n", pressure, stack_limit);
// Affinity
printf("core_affinity = %s\n", pressure > 0.5f ? "0 (pinned)" : "any");
printf("\nSTEP 4: TUNE THE KNOBS\n");
printf("════════════════════════════════════════════════════════════════\n");
printf("BEFORE adjustment:\n");
printf(" priority: 0 (normal), sampling: 100%%, stack: 256, core: any\n");
printf("\nAFTER adjustment:\n");
printf(" priority: %d %s\n", priority, priority < -10 ? "(BOOSTED)" : "");
printf(" sampling: %u%% %s\n", sampling, sampling < 50 ? "(REDUCED)" : "");
printf(" stack_limit: %u %s\n", stack_limit, stack_limit < 128 ? "(LIMITED)" : "");
printf(" core: %s\n", pressure > 0.5f ? "0 (PINNED)" : "any");
printf("\nSTEP 5: OBSERVE EFFECT\n");
printf("════════════════════════════════════════════════════════════════\n");
if (pressure > 0.7f) {
printf("✓ Word is VERY HOT (%.1f%% thermal load)\n", pressure * 100.0f);
printf(" → Priority BOOSTED: gets more CPU time\n");
printf(" → Sampling REDUCED: minimal profiling overhead\n");
printf(" → Stack LIMITED: prevents deep recursion\n");
printf(" → Core PINNED: keeps warm data in CPU cache\n");
printf(" RESULT: System fully optimized for this hot path\n");
} else if (pressure > 0.4f) {
printf("✓ Word is WARM (%.1f%% thermal load)\n", pressure * 100.0f);
printf(" → Moderate adjustments applied\n");
printf(" → System balances performance with overhead\n");
} else {
printf("✓ Word is COOL (%.1f%% thermal load)\n", pressure * 100.0f);
printf(" → Minimal adjustments, normal execution\n");
}
printf("\n════════════════════════════════════════════════════════════════\n");
printf("FEEDBACK LOOP COMPLETE\n");
printf("Next execution will use adjusted knobs → new metrics → loop\n");
printf("════════════════════════════════════════════════════════════════\n\n");
}
/* ============================================================================
* Word Registration
* ============================================================================
*/
/**
* Register all physics diagnostic words with the VM
*/
void register_physics_diagnostic_words(VM *vm) {
register_word(vm, "PHYSICS-WORD-METRICS", forth_PHYSICS_WORD_METRICS);
register_word(vm, "PHYSICS-CALC-KNOBS", forth_PHYSICS_CALC_KNOBS);
register_word(vm, "PHYSICS-BURN", forth_PHYSICS_BURN);
register_word(vm, "PHYSICS-SHOW-FEEDBACK", forth_PHYSICS_SHOW_FEEDBACK);
}
+470
View File
@@ -0,0 +1,470 @@
/*
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_freeze_words.c - Phase 2 Freeze/Decay Control Words
Implements the FORTH interface for Phase 2 physics model enhancements:
- FREEZE-WORD: Lock a word's execution heat (prevents decay)
- UNFREEZE-WORD: Allow heat decay to resume
- FROZEN?: Query freeze status
- HEAT!: Set execution heat manually (diagnostics)
- HEAT@: Read current execution heat
- SHOW-HEAT: Display heat for a single word
- ALL-HEATS: Display heat for all words in dictionary
Semantic Note:
- WORD_FROZEN (0x04) prevents heat decay via physics_metadata_apply_linear_decay()
- Works independently from WORD_PINNED (0x08) which locks heat at maximum
- FROZEN words maintain their heat indefinitely across OS context switches
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "vm.h"
#include "physics_metadata.h"
#include "word_registry.h"
/**
* @brief FREEZE-WORD ( caddr u -- )
*
* Set the @c WORD_FROZEN flag on the named word, preventing @c physics_metadata_apply_linear_decay()
* from reducing its @c execution_heat. Frozen words stay hot across OS context switches indefinitely.
* Silently succeeds if the word is not found (lenient policy).
*
* Stack effect: ( caddr u -- )
*
* @param vm Active VM; @c vm->error is set and returns early on underflow
*/
void forth_FREEZE_WORD(VM *vm) {
if (vm->error) return;
/* Stack: ( caddr u -- ) -- dsp is a 0-indexed top-of-stack pointer,
* so 2 items on stack means dsp >= 1, not dsp >= 2. */
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t len = vm->data_stack[--vm->dsp];
cell_t caddr = vm->data_stack[--vm->dsp];
if (len <= 0 || len > WORD_NAME_MAX) {
return; /* Silently fail on invalid length */
}
/* caddr is a VM memory offset, not a host pointer -- resolve via vm_ptr() */
const char *name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)caddr);
if (!name) return;
/* Look up the word */
DictEntry *entry = vm_find_word(vm, name, (size_t)len);
if (entry) {
entry->flags |= WORD_FROZEN;
}
/* Silently succeed even if word not found (lenient) */
}
/**
* @brief UNFREEZE-WORD ( caddr u -- )
*
* Clears the @c WORD_FROZEN flag on the named word, allowing its @c execution_heat
* to decay normally via Loop #3. Does not alter @c WORD_PINNED. Silently succeeds
* if the word is not found or @c u is out of range.
*
* Stack effect: ( caddr u -- )
*
* @param vm Active VM; @c vm->error is set and returns early on underflow
*/
void forth_UNFREEZE_WORD(VM *vm) {
if (vm->error) return;
/* Stack: ( caddr u -- ) -- dsp is a 0-indexed top-of-stack pointer,
* so 2 items on stack means dsp >= 1, not dsp >= 2. */
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t len = vm->data_stack[--vm->dsp];
cell_t caddr = vm->data_stack[--vm->dsp];
if (len <= 0 || len > WORD_NAME_MAX) {
return;
}
const char *name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)caddr);
if (!name) return;
DictEntry *entry = vm_find_word(vm, name, (size_t)len);
if (entry) {
entry->flags &= ~WORD_FROZEN;
}
}
/**
* @brief FROZEN? ( caddr u -- flag )
*
* Tests whether the @c WORD_FROZEN flag is set on the named word. Pushes
* -1 (FORTH true) if frozen, 0 otherwise. Also pushes 0 when the word is
* not found or @c u is out of range, so callers need not distinguish "not frozen"
* from "not found".
*
* Stack effect: ( caddr u -- flag )
*
* @param vm Active VM; @c vm->error is set and returns early on underflow
*/
void forth_FROZEN_QUERY(VM *vm) {
if (vm->error) return;
/* Stack: ( caddr u -- flag ) -- dsp is a 0-indexed top-of-stack pointer,
* so 2 items on stack means dsp >= 1, not dsp >= 2. */
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t len = vm->data_stack[--vm->dsp];
cell_t caddr = vm->data_stack[--vm->dsp];
if (len <= 0 || len > WORD_NAME_MAX) {
vm->data_stack[vm->dsp++] = 0; /* Not frozen */
return;
}
const char *name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)caddr);
DictEntry *entry = name ? vm_find_word(vm, name, (size_t)len) : NULL;
if (entry && (entry->flags & WORD_FROZEN)) {
vm->data_stack[vm->dsp++] = -1; /* True (frozen) */
} else {
vm->data_stack[vm->dsp++] = 0; /* False (not frozen) */
}
}
/**
* @brief HEAT! ( heat caddr u -- )
*
* Directly writes @c heat into @c entry->execution_heat for the named word.
* Intended for diagnostics and testing; bypasses Loop #1 accumulation and
* Loop #3 decay. Silently succeeds if the word is not found or @c u is out
* of range.
*
* Stack effect: ( heat caddr u -- )
*
* @param vm Active VM; @c vm->error is set and returns early on underflow
*/
void forth_HEAT_STORE(VM *vm) {
if (vm->error) return;
/* Stack: ( heat caddr u -- ) -- dsp is a 0-indexed top-of-stack pointer,
* so 3 items on stack means dsp >= 2, not dsp >= 3. */
if (vm->dsp < 2) {
vm->error = 1;
return;
}
cell_t len = vm->data_stack[--vm->dsp];
cell_t caddr = vm->data_stack[--vm->dsp];
cell_t heat = vm->data_stack[--vm->dsp];
if (len <= 0 || len > WORD_NAME_MAX) {
return;
}
const char *name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)caddr);
if (!name) return;
DictEntry *entry = vm_find_word(vm, name, (size_t)len);
if (entry) {
entry->execution_heat = heat;
}
}
/**
* @brief HEAT@ ( caddr u -- heat )
*
* Pushes the current @c execution_heat of the named word. Pushes 0 when the
* word is not found, @c u is out of range, or the word genuinely has zero heat.
*
* Stack effect: ( caddr u -- heat )
*
* @param vm Active VM; @c vm->error is set and returns early on underflow
*/
void forth_HEAT_FETCH(VM *vm) {
if (vm->error) return;
/* Stack: ( caddr u -- heat ) -- dsp is a 0-indexed top-of-stack pointer,
* so 2 items on stack means dsp >= 1, not dsp >= 2. */
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t len = vm->data_stack[--vm->dsp];
cell_t caddr = vm->data_stack[--vm->dsp];
if (len <= 0 || len > WORD_NAME_MAX) {
vm->data_stack[vm->dsp++] = 0;
return;
}
const char *name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)caddr);
DictEntry *entry = name ? vm_find_word(vm, name, (size_t)len) : NULL;
if (entry) {
vm->data_stack[vm->dsp++] = entry->execution_heat;
} else {
vm->data_stack[vm->dsp++] = 0;
}
}
/**
* @brief SHOW-HEAT ( caddr u -- )
*
* Prints the @c execution_heat of the named word to @c stdout along with its
* freeze and pinned status flags. Output format: @c "NAME: HEAT (frozen) (pinned)".
* Prints @c "Word not found: NAME" when the dictionary lookup fails. Intended for
* interactive diagnostics.
*
* Stack effect: ( caddr u -- )
*
* @param vm Active VM; @c vm->error is set and returns early on underflow
*/
void forth_SHOW_HEAT(VM *vm) {
if (vm->error) return;
/* Stack: ( caddr u -- ) -- dsp is a 0-indexed top-of-stack pointer,
* so 2 items on stack means dsp >= 1, not dsp >= 2. */
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t len = vm->data_stack[--vm->dsp];
cell_t caddr = vm->data_stack[--vm->dsp];
if (len <= 0 || len > WORD_NAME_MAX) {
return;
}
const char *name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)caddr);
if (!name) return;
DictEntry *entry = vm_find_word(vm, name, (size_t)len);
if (entry) {
printf("%.*s: %ld", (int)len, name, entry->execution_heat);
if (entry->flags & WORD_FROZEN) {
printf(" (frozen)");
}
if (entry->flags & WORD_PINNED) {
printf(" (pinned)");
}
printf("\n");
} else {
printf("Word not found: %.*s\n", (int)len, name);
}
}
/**
* @brief ALL-HEATS ( -- )
*
* Iterates the entire dictionary, collects up to 1024 entries, sorts them by
* descending @c execution_heat using an O(n²) bubble sort, then prints a
* formatted table to @c stdout with columns for word name, heat value, and
* status (frozen/pinned). Intended for interactive diagnostics; the O(n²) sort
* is acceptable for dictionary sizes below 1000 words.
*
* Stack effect: ( -- )
*
* @param vm Active VM; no-op if @c vm->error is set
*/
void forth_ALL_HEATS(VM *vm) {
if (vm->error) return;
printf("\n=== Execution Heat (Phase 2) ===\n");
printf("%-16s %10s %s\n", "Word", "Heat", "Status");
printf("%-16s %10s %s\n", "----", "----", "------");
/* Simple O(n²) bubble sort by heat (acceptable for <1000 words) */
int count = 0;
DictEntry *entries[1024];
/* Collect all entries */
for (DictEntry *w = vm->latest; w && count < 1024; w = w->link) {
entries[count++] = w;
}
/* Sort by descending heat */
for (int i = 0; i < count - 1; i++) {
for (int j = 0; j < count - i - 1; j++) {
if (entries[j]->execution_heat < entries[j + 1]->execution_heat) {
DictEntry *tmp = entries[j];
entries[j] = entries[j + 1];
entries[j + 1] = tmp;
}
}
}
/* Display sorted by heat */
for (int i = 0; i < count; i++) {
DictEntry *w = entries[i];
printf("%-16.*s %10ld", w->name_len, w->name, w->execution_heat);
const char *status = "";
if (w->flags & WORD_FROZEN) {
status = " (frozen)";
} else if (w->flags & WORD_PINNED) {
status = " (pinned)";
}
printf("%s\n", status);
}
printf("\n");
}
/**
* @brief DECAY-RATE@ ( -- rate )
*
* Pushes the compile-time constant @c DECAY_RATE_PER_US_Q16 as a Q48.16
* fixed-point value. This is the base heat decay rate per microsecond used
* by Loop #3 before adaptive tuning by Loop #6. Intended for introspection
* and diagnostics.
*
* Stack effect: ( -- rate )
*
* @param vm Active VM; sets @c vm->error = 1 on stack overflow
*/
void forth_DECAY_RATE_FETCH(VM *vm) {
if (vm->error) return;
if (vm->dsp >= STACK_SIZE) {
vm->error = 1;
return;
}
/* Stack: ( -- rate ) */
vm->data_stack[vm->dsp++] = (cell_t)DECAY_RATE_PER_US_Q16;
}
/**
* @brief FREEZE-CRITICAL ( -- )
*
* Sets @c WORD_FROZEN on a hard-coded list of 21 system-critical words
* (DUP, DROP, SWAP, OVER, ROT, @, !, C@, C!, EXECUTE, IF, THEN, ELSE, DO,
* LOOP, BEGIN, UNTIL, REPEAT, ., EMIT, CR). Silently skips any word not found
* in the current dictionary. Intended to be called once at startup to prevent
* essential words from decaying out of the hot-words cache.
*
* Stack effect: ( -- )
*
* @param vm Active VM; no-op if @c vm->error is set
*/
void forth_FREEZE_CRITICAL(VM *vm) {
if (vm->error) return;
/* List of critical words that should never decay */
const char *critical_words[] = {
"DUP",
"DROP",
"SWAP",
"OVER",
"ROT",
"@",
"!",
"C@",
"C!",
"EXECUTE",
"IF",
"THEN",
"ELSE",
"DO",
"LOOP",
"BEGIN",
"UNTIL",
"REPEAT",
".",
"EMIT",
"CR",
NULL
};
for (int i = 0; critical_words[i] != NULL; i++) {
const char *name = critical_words[i];
size_t len = strlen(name);
DictEntry *entry = vm_find_word(vm, name, len);
if (entry) {
entry->flags |= WORD_FROZEN;
}
}
}
/**
* @brief Register all Phase 2 freeze/decay control words with the VM dictionary.
*
* Registers: @c FREEZE-WORD, @c UNFREEZE-WORD, @c FROZEN?, @c HEAT!,
* @c HEAT@, @c SHOW-HEAT, @c ALL-HEATS, @c DECAY-RATE@, @c FREEZE-CRITICAL.
* Called during VM bootstrap by the word registration subsystem.
*
* @param vm Active VM to register words into
*/
void register_physics_freeze_words(VM *vm) {
register_word(vm, "FREEZE-WORD", forth_FREEZE_WORD);
register_word(vm, "UNFREEZE-WORD", forth_UNFREEZE_WORD);
register_word(vm, "FROZEN?", forth_FROZEN_QUERY);
register_word(vm, "HEAT!", forth_HEAT_STORE);
register_word(vm, "HEAT@", forth_HEAT_FETCH);
register_word(vm, "SHOW-HEAT", forth_SHOW_HEAT);
register_word(vm, "ALL-HEATS", forth_ALL_HEATS);
register_word(vm, "DECAY-RATE@", forth_DECAY_RATE_FETCH);
register_word(vm, "FREEZE-CRITICAL", forth_FREEZE_CRITICAL);
}
@@ -0,0 +1,449 @@
/*
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_diagnostic_words.c - Diagnostic words for pipelining metrics
Phase 1 diagnostics: words to display word-to-word transition metrics
collected during normal execution.
*/
#include "../../include/vm.h"
#include "../../include/physics_pipelining_metrics.h"
#include "../../include/word_registry.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/**
* PIPELINING-SHOW-STATS ( <wordname> -- )
*
* Display transition metrics for a given word.
* Typical usage:
* PIPELINING-SHOW-STATS EXIT ( show where EXIT typically goes )
* PIPELINING-SHOW-STATS BENCH-DICT ( show where BENCH-DICT transitions )
*
* Requires: word must exist in dictionary
* Effect: Prints transition statistics to stdout
*/
static void forth_PIPELINING_SHOW_STATS(VM *vm) {
if (!vm || vm->dsp < 0) {
vm->error = 1;
return;
}
/* Pop word name from stack (as address + length) */
cell_t len = vm_pop(vm);
if (vm->error) return;
cell_t addr = vm_pop(vm);
if (vm->error) return;
/* Convert VM address to C pointer */
const char *word_name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)addr);
if (!word_name) {
vm->error = 1;
return;
}
/* Find the word */
DictEntry *entry = vm_find_word(vm, word_name, (size_t)len);
if (!entry) {
printf("(word not found: ");
fwrite(word_name, 1, (size_t)len, stdout);
printf(")\n");
return;
}
if (!entry->transition_metrics) {
printf("(no transition metrics for: ");
fwrite(word_name, 1, (size_t)len, stdout);
printf(")\n");
return;
}
/* Display word name */
printf("\nTransition Metrics for: ");
fwrite(word_name, 1, (size_t)len, stdout);
printf("\n");
printf("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "\n");
/* Display metrics */
WordTransitionMetrics *m = entry->transition_metrics;
printf("Total Transitions Observed: %lu\n", m->total_transitions);
printf("Prefetch Attempts: %lu\n", m->prefetch_attempts);
printf("Prefetch Hits: %lu\n", m->prefetch_hits);
printf("Prefetch Misses: %lu\n", m->prefetch_misses);
printf("Latency Saved (ns): %ld\n", m->prefetch_latency_saved_q48 >> 16);
printf("Misprediction Cost (ns): %ld\n", m->misprediction_cost_q48 >> 16);
if (m->total_transitions > 0) {
double hit_rate = (100.0 * m->prefetch_hits) / m->prefetch_attempts;
printf("Hit Rate: %.1f%%\n", (m->prefetch_attempts > 0) ? hit_rate : 0.0);
}
printf("Most Likely Next Word ID: %u\n", m->most_likely_next_word_id);
printf("Probability (Q48.16): 0x%lx\n", m->max_transition_probability_q48);
printf("\n");
}
/**
* PIPELINING-SHOW-TOP-TRANSITIONS ( <wordname> N -- )
*
* Display the top N words that typically follow a given word.
* Useful for understanding hot execution paths.
*
* Stack: <wordname> N --
* Example:
* " EXIT" 5 PIPELINING-SHOW-TOP-TRANSITIONS
* ( Show top 5 words that usually follow EXIT )
*/
static void forth_PIPELINING_SHOW_TOP_TRANSITIONS(VM *vm) {
if (!vm || vm->dsp < 1) {
vm->error = 1;
return;
}
/* Pop count (how many top transitions to show) */
cell_t top_count = vm_pop(vm);
if (vm->error) return;
/* Pop word name */
cell_t len = vm_pop(vm);
if (vm->error) return;
cell_t addr = vm_pop(vm);
if (vm->error) return;
if (top_count < 1 || top_count > 20) {
printf("(invalid count: must be 1-20)\n");
return;
}
const char *word_name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)addr);
if (!word_name) {
vm->error = 1;
return;
}
DictEntry *entry = vm_find_word(vm, word_name, (size_t)len);
if (!entry || !entry->transition_metrics) {
printf("(word not found or no metrics)\n");
return;
}
WordTransitionMetrics *m = entry->transition_metrics;
if (m->total_transitions == 0) {
printf("(no transitions recorded yet)\n");
return;
}
printf("\nTop %ld transitions from: ", top_count);
fwrite(word_name, 1, (size_t)len, stdout);
printf("\n");
printf("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "\n");
printf("Rank Next Word ID Count Probability\n");
printf("-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "-" "\n");
if (!m->transition_heat) {
printf("(no transition data)\n");
return;
}
/* Simple bubble sort to find top N (inefficient but diagnostic code) */
typedef struct {
uint32_t word_id;
uint64_t count;
} TransitionPair;
TransitionPair *top = (TransitionPair *)malloc(sizeof(TransitionPair) * top_count);
if (!top) return;
memset(top, 0, sizeof(TransitionPair) * top_count);
/* Scan all words and keep top N */
for (uint32_t i = 0; i < DICTIONARY_SIZE; i++) {
uint64_t count = m->transition_heat[i];
if (count == 0) continue;
/* Find position in top list */
int pos = (int)top_count - 1;
while (pos >= 0 && count > top[pos].count) {
pos--;
}
pos++;
if (pos < (int)top_count) {
/* Shift entries to make room */
for (int j = (int)top_count - 1; j > pos; j--) {
top[j] = top[j - 1];
}
top[pos].word_id = i;
top[pos].count = count;
}
}
/* Display results_run_01_2025_12_08 */
for (int i = 0; i < top_count; i++) {
if (top[i].count == 0) break;
double prob_pct = 100.0 * top[i].count / m->total_transitions;
printf("%2d. %4u %5lu %.1f%%\n",
i + 1, top[i].word_id, top[i].count, prob_pct);
}
printf("\n");
free(top);
}
/**
* PIPELINING-RESET-ALL ( -- )
*
* Clear all transition metrics from the dictionary.
* Useful for starting fresh measurements.
*/
static void forth_PIPELINING_RESET_ALL(VM *vm) {
if (!vm) return;
int count = 0;
DictEntry *entry = vm->latest;
while (entry) {
if (entry->transition_metrics) {
transition_metrics_reset(entry->transition_metrics);
count++;
}
entry = entry->link;
}
printf("(reset %d word metrics)\n", count);
}
/**
* PIPELINING-ENABLE ( -- )
*
* Enable pipelining metrics collection (sets ENABLE_PIPELINING flag).
* Note: This is compile-time on/off for now; this word is a placeholder.
*/
static void forth_PIPELINING_ENABLE(VM *vm) {
if (!vm) return;
printf("(pipelining metrics: ");
#if ENABLE_PIPELINING
printf("enabled at compile-time)\n");
#else
printf("disabled - recompile with ENABLE_PIPELINING=1)\n");
#endif
}
/**
* PIPELINING-STATS ( -- )
*
* Display aggregate pipelining statistics across entire dictionary.
*/
static void forth_PIPELINING_STATS(VM *vm) {
if (!vm) return;
printf("\n");
printf("Pipelining Statistics (Aggregate)\n");
printf("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "\n");
uint64_t total_transitions = 0;
uint64_t total_attempts = 0;
uint64_t total_hits = 0;
uint64_t total_misses = 0;
int word_count = 0;
int instrumented_count = 0;
DictEntry *entry = vm->latest;
while (entry) {
word_count++;
if (entry->transition_metrics) {
instrumented_count++;
WordTransitionMetrics *m = entry->transition_metrics;
total_transitions += m->total_transitions;
total_attempts += m->prefetch_attempts;
total_hits += m->prefetch_hits;
total_misses += m->prefetch_misses;
}
entry = entry->link;
}
printf("Total Words in Dictionary: %d\n", word_count);
printf("Words with Metrics: %d (%.1f%%)\n",
instrumented_count,
(100.0 * instrumented_count) / ((word_count > 0) ? word_count : 1));
printf("Total Transitions Observed: %lu\n", total_transitions);
printf("Total Prefetch Attempts: %lu\n", total_attempts);
printf("Total Prefetch Hits: %lu\n", total_hits);
printf("Total Prefetch Misses: %lu\n", total_misses);
if (total_attempts > 0) {
double hit_rate = (100.0 * total_hits) / total_attempts;
printf("Overall Hit Rate: %.1f%%\n", hit_rate);
}
printf("\n");
}
/**
* PIPELINING-ANALYZE-WORD ( <wordname> -- )
*
* Comprehensive analysis of a single word's transition pattern.
* Shows raw data and interpretation hints.
*/
static void forth_PIPELINING_ANALYZE_WORD(VM *vm) {
if (!vm || vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t len = vm_pop(vm);
if (vm->error) return;
cell_t addr = vm_pop(vm);
if (vm->error) return;
const char *word_name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)addr);
if (!word_name) {
vm->error = 1;
return;
}
DictEntry *entry = vm_find_word(vm, word_name, (size_t)len);
if (!entry) {
printf("(word not found)\n");
return;
}
WordTransitionMetrics *m = entry->transition_metrics;
if (!m || m->total_transitions == 0) {
printf("(no transition data)\n");
return;
}
printf("\nAnalysis: ");
fwrite(word_name, 1, (size_t)len, stdout);
printf("\n");
printf("=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "\n");
/* Calculate entropy / distribution spread */
double avg_count = (double)m->total_transitions / DICTIONARY_SIZE;
double max_count = 0;
for (uint32_t i = 0; i < DICTIONARY_SIZE; i++) {
if (m->transition_heat && m->transition_heat[i] > max_count) {
max_count = (double)m->transition_heat[i];
}
}
printf("Execution Pattern:\n");
if (max_count > 10 * avg_count) {
printf(" → Highly predictable (strong preferred successor)\n");
} else if (max_count > 5 * avg_count) {
printf(" → Moderately predictable (clear pattern)\n");
} else if (max_count > 2 * avg_count) {
printf(" → Somewhat variable (multiple paths)\n");
} else {
printf(" → Highly variable (many different successors)\n");
}
printf("Max Transition Count: %lu (%.1f%% of total)\n",
(uint64_t)max_count,
(100.0 * max_count) / m->total_transitions);
printf("Word ID with Max: %u\n", m->most_likely_next_word_id);
printf("Predictability (IQR): %.1f%%\n",
(m->total_transitions > 0) ? (100.0 * m->max_transition_probability_q48 / (1LL << 16)) : 0.0);
if (m->prefetch_attempts > 0) {
printf("\nPrefetch Analysis:\n");
printf(" Hit Rate: %.1f%% (%lu / %lu)\n",
(100.0 * m->prefetch_hits) / m->prefetch_attempts,
m->prefetch_hits,
m->prefetch_attempts);
int64_t net_ns = (m->prefetch_latency_saved_q48 - m->misprediction_cost_q48) >> 16;
if (net_ns > 0) {
printf(" Net Benefit: +%ld ns\n", net_ns);
} else {
printf(" Net Loss: %ld ns\n", net_ns);
}
} else {
printf("\nNo prefetch attempts yet (Phase 3 implementation pending)\n");
}
printf("\n");
}
/* ============================================================================
* Word Registry Entry
* ============================================================================
*/
/**
* Register all pipelining diagnostic words
*/
void register_physics_pipelining_diagnostic_words(VM *vm) {
if (!vm) return;
/* Declare words (forward declarations for compilation) */
extern void forth_PIPELINING_SHOW_STATS(VM *vm);
extern void forth_PIPELINING_SHOW_TOP_TRANSITIONS(VM *vm);
extern void forth_PIPELINING_RESET_ALL(VM *vm);
extern void forth_PIPELINING_ENABLE(VM *vm);
extern void forth_PIPELINING_STATS(VM *vm);
extern void forth_PIPELINING_ANALYZE_WORD(VM *vm);
/* Register diagnostic words */
vm_create_word(vm, "PIPELINING-SHOW-STATS", strlen("PIPELINING-SHOW-STATS"), forth_PIPELINING_SHOW_STATS);
vm_create_word(vm, "PIPELINING-SHOW-TOP-TRANSITIONS", strlen("PIPELINING-SHOW-TOP-TRANSITIONS"), forth_PIPELINING_SHOW_TOP_TRANSITIONS);
vm_create_word(vm, "PIPELINING-RESET-ALL", strlen("PIPELINING-RESET-ALL"), forth_PIPELINING_RESET_ALL);
vm_create_word(vm, "PIPELINING-ENABLE", strlen("PIPELINING-ENABLE"), forth_PIPELINING_ENABLE);
vm_create_word(vm, "PIPELINING-STATS", strlen("PIPELINING-STATS"), forth_PIPELINING_STATS);
vm_create_word(vm, "PIPELINING-ANALYZE-WORD", strlen("PIPELINING-ANALYZE-WORD"), forth_PIPELINING_ANALYZE_WORD);
}
+590
View File
@@ -0,0 +1,590 @@
/*
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.
*/
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "../../include/q48_16.h"
/* ============================================================================
* Core Arithmetic: Multiply
* ============================================================================
*
* Formula: (a / 2^16) * (b / 2^16) * 2^16 = (a * b) / 2^16
*
* Implementation note:
* - Compute a * b in 128-bit intermediate (if available)
* - Shift right by 16 bits
* - For platforms without uint128_t, use uint64_t with overflow check
*/
/**
* @brief Multiply two Q48.16 fixed-point values.
*
* Computes @c (a * b) >> 16 to maintain the Q48.16 scale. Uses
* @c __uint128_t on GCC/Clang (128-bit intermediate, no precision loss)
* and falls back to a manual 64-bit four-partial-product path with carry
* tracking on platforms that lack @c __SIZEOF_INT128__.
*
* @param a First operand in Q48.16 format
* @param b Second operand in Q48.16 format
* @return Product in Q48.16 format (truncated, not rounded)
*/
q48_16_t q48_mul(q48_16_t a, q48_16_t b)
{
/* Use __uint128_t if available (GCC/Clang) */
#ifdef __SIZEOF_INT128__
__uint128_t prod = (__uint128_t)a * (__uint128_t)b;
return (q48_16_t)(prod >> 16);
#else
/* Fallback: manual 64-bit multiplication with overflow detection */
/* Split a and b into high/low parts */
uint64_t a_hi = a >> 32;
uint64_t a_lo = a & 0xFFFFFFFFULL;
uint64_t b_hi = b >> 32;
uint64_t b_lo = b & 0xFFFFFFFFULL;
/* Compute 4 partial products */
uint64_t p_ll = a_lo * b_lo; /* Low * Low */
uint64_t p_lh = a_lo * b_hi; /* Low * High */
uint64_t p_hl = a_hi * b_lo; /* High * Low */
uint64_t p_hh = a_hi * b_hi; /* High * High */
/* Accumulate with carry tracking */
uint64_t carry = 0;
uint64_t mid = p_lh + p_hl;
if (mid < p_lh) carry++; /* Detect overflow in middle term */
uint64_t result_hi = p_hh + (mid >> 32) + (carry << 32);
uint64_t result_lo = p_ll + ((mid & 0xFFFFFFFFULL) << 32);
if (result_lo < p_ll) result_hi++;
/* Return: (result_hi << 32 | result_lo) >> 16 */
/* = (result_hi << 16) | (result_lo >> 16) */
return (result_hi << 16) | (result_lo >> 16);
#endif
}
/* ============================================================================
* Core Arithmetic: Divide
* ============================================================================
*
* Formula: (a / 2^16) / (b / 2^16) * 2^16 = (a << 16) / b
*
* Precondition: b > 0 (division by zero check in caller)
*/
/**
* @brief Divide two Q48.16 fixed-point values.
*
* Computes @c (a << 16) / b. Returns 0 if @c b == 0 (safe, but callers
* should guard). Returns @c UINT64_MAX as a saturation sentinel when @c a
* exceeds 2^48, which would overflow the 16-bit left-shift.
*
* @param a Dividend in Q48.16 format
* @param b Divisor in Q48.16 format; 0 returns 0
* @return Quotient in Q48.16 format, or UINT64_MAX on saturation
*/
q48_16_t q48_div(q48_16_t a, q48_16_t b)
{
if (b == 0) {
/* Division by zero: return 0 (safe, but caller should check) */
return 0;
}
/* Shift a left by 16 bits to maintain Q48.16 precision */
/* Be careful: this could overflow if a is too large */
/* Max safe a: 2^48 (since we're shifting left by 16) */
if (a > 0x0000FFFFFFFFFFFFULL) {
/* a is too large for safe shifting */
/* Return max Q48.16 to signal saturation */
return 0xFFFFFFFFFFFFFFFFULL;
}
q48_16_t shifted = a << 16;
return shifted / b;
}
/* ============================================================================
* Conversions: u64 ↔ Q48.16
* ============================================================================
*/
/**
* @brief Convert a @c double to Q48.16 format.
*
* Multiplies @c d by 65536.0 and rounds to nearest. Intended for
* testing and diagnostics only — not called in the inference engine
* hot path to avoid floating-point dependency.
*
* @param d Value to convert
* @return Nearest Q48.16 representation of @c d
*/
q48_16_t q48_from_double(double d)
{
/* Convert double to Q48.16 */
/* This is for testing/diagnostics only, not used in inference engine */
return (q48_16_t)(d * 65536.0 + 0.5); /* Round to nearest */
}
/**
* @brief Convert a Q48.16 value to @c double.
*
* Divides @c q by 65536.0. Used for logging and RStudio dashboard output;
* not called in the inference engine hot path.
*
* @param q Value in Q48.16 format
* @return Equivalent @c double value
*/
double q48_to_double(q48_16_t q)
{
/* Convert Q48.16 to double */
/* For logging and RStudio dashboard output */
return (double)q / 65536.0;
}
/* ============================================================================
* Approximation: Natural Logarithm (Integer-Only, Newton-Raphson)
* ============================================================================
*
* Purpose: ln(x) in Q48.16 format for exponential decay fitting
*
* Algorithm:
* 1. Use bit position as coarse approximation
* - If x = 2^k * m where 1 <= m < 2, then ln(x) ≈ k*ln(2) + ln(m)
* - ln(2) in Q48.16 = 0xB17217F7 (approx)
*
* 2. Refine ln(m) using Newton-Raphson on exp()
* - For y = ln(x), we want e^y = x
* - Newton: y_{n+1} = y_n + (x - e^{y_n}) / e^{y_n}
* - Simplified: y_{n+1} = y_n - 1 + x / e^{y_n}
*
* 3. Do 4-5 iterations for Q48.16 precision
*/
/**
* @brief Compute an approximation of @c ln(x) in Q48.16 format (integer-only).
*
* Used by @c infer_decay_slope_q48() (Loop #6) for log-linear OLS regression.
* Algorithm: range-reduces @c x to the interval [1, 2) by tracking the
* exponent @c k (so @c x = 2^k * m), applies 6 Newton-Raphson iterations on
* @c e^y = m starting from the first-order approximation @c y_0 = m - 1,
* then assembles the result as @c ln(x) = k*ln(2) + ln(m). Returns 0 for
* @c x == 0 (ln undefined) and for @c x == 65536 (ln(1.0) == 0.0 exactly).
* @c LN2_Q48 = 45426 (0.693147... × 65536).
*
* @param x Input value in Q48.16 format (uint64_t; value of 65536 = 1.0)
* @return @c ln(x) in Q48.16 format, or 0 for x==0 or x==65536
*/
q48_16_t q48_log_approx(uint64_t x)
{
if (x == 0) {
/* ln(0) is undefined; return 0 for safety */
return 0;
}
if (x == 65536) {
/* ln(1.0 in Q48.16) = 0 */
return 0;
}
/* ln(2) in Q48.16 ≈ 0.693147180559945... */
/* 0.693147 * 65536 = 45426.5 ≈ 45426 or 45427 */
const q48_16_t LN2_Q48 = 45426; /* 0x0000B17E in Q48.16 */
/* Step 1: Find bit position k such that x = 2^k * m, 1 <= m < 2 */
int k = 0;
uint64_t m = x;
/* Count leading zeros to find position */
if (m >= 131072) { /* m >= 2 in Q48.16 */
while (m >= 131072) {
m >>= 1;
k++;
}
} else if (m < 65536) { /* m < 1 in Q48.16 */
while (m < 65536) {
m <<= 1;
k--;
}
}
/* Step 2: Compute ln(m) where 1 <= m < 2 using Newton-Raphson */
/* Initial guess: y_0 = (m - 1) for small values */
q48_16_t y = (m > 65536) ? (m - 65536) : 0;
/* Newton iterations: y_{n+1} = y_n + (m - e^{y_n}) / e^{y_n} */
for (int iter = 0; iter < 6; iter++) {
/* Compute e^y */
q48_16_t exp_y = q48_exp_approx(y);
if (exp_y == 0) break; /* Safety check */
/* Compute (m - exp_y) / exp_y, handling both positive and negative delta */
int64_t delta_signed = (int64_t)m - (int64_t)exp_y;
q48_16_t correction;
if (delta_signed > 0) {
correction = q48_div((q48_16_t)delta_signed, exp_y);
y = q48_add(y, correction);
} else if (delta_signed < 0) {
correction = q48_div((q48_16_t)(-delta_signed), exp_y);
if (y > correction) {
y = q48_sub(y, correction);
} else {
y = 0;
}
}
/* Early exit if correction is very small */
if (delta_signed < 100 && delta_signed > -100) break;
}
/* Step 3: Combine ln(x) = k*ln(2) + ln(m) */
q48_16_t result = y;
if (k > 0) {
result = q48_add(result, q48_mul(q48_from_u64(k), LN2_Q48));
} else if (k < 0) {
result = q48_sub(result, q48_mul(q48_from_u64(-k), LN2_Q48));
}
return result;
}
/* ============================================================================
* Approximation: Exponential (Integer-Only, Taylor Series)
* ============================================================================
*
* Purpose: e^x in Q48.16 format (supporting function for ln_approx)
*
* Algorithm: Taylor series around x=0
* e^x = 1 + x + x^2/2! + x^3/3! + x^4/4! + ...
*
* For Q48.16 arithmetic, compute 6-8 terms for good precision.
*/
/**
* @brief Compute an approximation of @c e^q in Q48.16 format (Taylor series).
*
* Supporting function for @c q48_log_approx() Newton iterations. Uses the
* Taylor series @c 1 + x + x²/2! + … up to 10 terms, with an early-exit when
* a term falls below 50 (sub-LSB in Q48.16). Handles negative @c q by
* computing @c e^|q| then returning @c 1/result. Returns 1.0 (65536) for
* @c q == 0, @c UINT64_MAX for @c q >= 16.0, and 0 for @c q <= -16.0.
*
* @param q Exponent in Q48.16 format (signed interpretation)
* @return @c e^q in Q48.16 format
*/
q48_16_t q48_exp_approx(q48_16_t q)
{
/* Handle special cases */
if (q == 0) {
return 65536; /* e^0 = 1 in Q48.16 */
}
/* Check for overflow/underflow */
int64_t q_signed = (int64_t)q;
if (q_signed >= 1048576) { /* q >= 16 */
return 0xFFFFFFFFFFFFFFFFULL; /* Overflow */
}
if (q_signed <= -1048576) { /* q <= -16 */
return 0; /* Underflow to 0 */
}
/* For negative x, compute e^(-x) = 1 / e^x */
int is_negative = (q_signed < 0);
q48_16_t x = is_negative ? (q48_16_t)(-q_signed) : q;
/* Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... */
q48_16_t result = 65536; /* 1.0 in Q48.16 */
q48_16_t term = x; /* First term = x */
result = q48_add(result, term);
/* Compute subsequent terms */
for (int n = 2; n <= 10; n++) {
/* term = term * x / n */
term = q48_mul(term, x);
term = q48_div(term, q48_from_u64(n));
result = q48_add(result, term);
/* Early exit if term becomes negligible */
if (term < 50) break;
}
/* If original was negative, return 1/result */
if (is_negative) {
result = q48_div(65536, result); /* 1.0 / result */
}
return result;
}
/* ============================================================================
* Approximation: Square Root (Integer-Only, Newton-Raphson)
* ============================================================================
*
* Purpose: sqrt(q) in Q48.16 for R² computation
*
* Algorithm: Newton-Raphson on x^2 = q
* x_{n+1} = (x_n + q/x_n) / 2
*/
/**
* @brief Compute an approximation of @c sqrt(q) in Q48.16 format (Newton-Raphson).
*
* Iterates @c x_{n+1} = (x_n + q/x_n) / 2 starting from @c (q >> 1) + 16384,
* for up to 8 iterations. Terminates early when the delta between successive
* estimates falls below 10 (approximately 0.00015 in Q48.16). Used for R²
* computation in the inference engine. Returns 0 for @c q == 0, and 1.0
* (65536) for @c q == 65536.
*
* @param q Input value in Q48.16 format
* @return @c sqrt(q) in Q48.16 format
*/
q48_16_t q48_sqrt_approx(q48_16_t q)
{
if (q == 0) {
return 0;
}
if (q == 65536) { /* sqrt(1.0) = 1.0 */
return 65536;
}
/* Initial guess: x_0 = q >> 1 (roughly sqrt in Q48.16 space) */
q48_16_t x = (q >> 1) + 16384; /* Ensure non-zero start */
for (int iter = 0; iter < 8; iter++) {
/* Newton-Raphson: x_{n+1} = (x_n + q/x_n) / 2 */
q48_16_t q_div_x = q48_div(q, x);
q48_16_t x_next = (x + q_div_x) >> 1;
/* Check convergence */
q48_16_t delta = (x_next > x) ? (x_next - x) : (x - x_next);
if (delta < 10) break; /* Converged to within 0.00015 */
x = x_next;
}
return x;
}
/* ============================================================================
* Approximation: Sine / Cosine (Integer-Only, Taylor Series)
* ============================================================================
*
* FABRIC-0.md item 4.3.3a -- needed by the Console drawing fabric's
* CIRCLE/ARC/ELLIPSE (item 4.3.3b). Radian input, same Taylor-series
* approach as q48_exp_approx above.
*
* PI_Q48 = 205887 (pi * 65536, rounded). TWO_PI_Q48 is derived as
* 2 * PI_Q48 rather than independently rounded, so the range-reduction
* boundary at +-PI_Q48 is self-consistent (no seam).
*/
/**
* @brief Reduce a signed Q48.16 angle into [-PI_Q48, PI_Q48].
*
* A single integer division on the raw Q48.16 representations gives the
* correct (scale-independent) quotient of how many full 2*pi turns to
* remove, then a bounded fix-up loop (at most one or two iterations)
* handles the remainder landing just outside the target interval.
*
* @param x Signed angle in Q48.16 format (any magnitude)
* @return Equivalent angle in Q48.16 format, within [-PI_Q48, PI_Q48]
*/
static int64_t q48_reduce_angle(int64_t x)
{
const int64_t PI_Q48 = 205887; /* pi * 65536, rounded */
const int64_t TWO_PI_Q48 = 2 * PI_Q48; /* derived, not independently rounded */
int64_t k = x / TWO_PI_Q48;
x -= k * TWO_PI_Q48;
while (x > PI_Q48) x -= TWO_PI_Q48;
while (x < -PI_Q48) x += TWO_PI_Q48;
return x;
}
/**
* @brief Compute an approximation of @c sin(q) in Q48.16 format (radians).
*
* Range-reduces into [-pi, pi] via @c q48_reduce_angle(), then sums the
* Taylor series @c x - x^3/3! + x^5/5! - ... for terms n = 3,5,7,9,11,
* with an early exit when a term falls below 10 (sub-LSB in Q48.16). Sine
* is odd (@c sin(-x) = -sin(x)), so the sign is factored out before the
* series and reapplied to the result.
*
* @param q Angle in Q48.16 format (radians, any magnitude)
* @return @c sin(q) in Q48.16 format
*/
q48_16_t q48_sin_approx(q48_16_t q)
{
int64_t x = q48_reduce_angle((int64_t)q);
int negative = (x < 0);
if (negative) x = -x;
q48_16_t xu = (q48_16_t)x;
q48_16_t x2 = q48_mul(xu, xu);
q48_16_t term = xu;
q48_16_t result = xu;
int subtract = 1;
for (int n = 3; n <= 11; n += 2) {
term = q48_mul(term, x2);
term = q48_div(term, q48_from_u64((uint64_t)(n * (n - 1))));
result = subtract ? q48_sub(result, term) : q48_add(result, term);
subtract = !subtract;
if (term < 10) break;
}
return negative ? (q48_16_t)(0ULL - result) : result;
}
/**
* @brief Compute an approximation of @c cos(q) in Q48.16 format (radians).
*
* Range-reduces into [-pi, pi] via @c q48_reduce_angle(), then sums the
* Taylor series @c 1 - x^2/2! + x^4/4! - ... for terms n = 2,4,6,8,10,
* with an early exit when a term falls below 10. Cosine is even
* (@c cos(-x) = cos(x)), so no sign correction is needed -- @c x^2 already
* absorbs the sign of @c x.
*
* @param q Angle in Q48.16 format (radians, any magnitude)
* @return @c cos(q) in Q48.16 format
*/
q48_16_t q48_cos_approx(q48_16_t q)
{
int64_t x = q48_reduce_angle((int64_t)q);
q48_16_t xu = (q48_16_t)(x < 0 ? -x : x);
q48_16_t x2 = q48_mul(xu, xu);
q48_16_t term = 65536; /* 1.0 in Q48.16 */
q48_16_t result = term;
int subtract = 1;
for (int n = 2; n <= 10; n += 2) {
term = q48_mul(term, x2);
term = q48_div(term, q48_from_u64((uint64_t)(n * (n - 1))));
result = subtract ? q48_sub(result, term) : q48_add(result, term);
subtract = !subtract;
if (term < 10) break;
}
return result;
}
/* ============================================================================
* Diagnostic / Testing Utilities
* ============================================================================
*/
/**
* @brief Format a Q48.16 value as a decimal string @c "INTEGER.FRAC".
*
* Writes into a static buffer — not thread-safe and not re-entrant.
* The fractional part is expressed as 5 decimal digits
* (@c frac_part * 100000 / 65536). Intended for diagnostic logging.
*
* @param q Value in Q48.16 format
* @return Pointer to a static buffer containing the formatted string
*/
const char* q48_to_string(q48_16_t q)
{
static char buf[64];
uint64_t integer_part = q >> 16;
uint64_t frac_part = q & 0xFFFFULL;
/* Convert fractional part to 5 decimal digits */
/* frac_part / 65536 * 100000 = frac_part * 100000 / 65536 */
uint32_t frac_digits = (frac_part * 100000ULL) / 65536;
snprintf(buf, sizeof(buf), "%lu.%05u", integer_part, frac_digits);
return buf;
}
/**
* @brief Test whether a Q48.16 value is valid.
*
* Currently accepts any @c uint64_t value as valid — all bit patterns are
* representable in Q48.16. A more sophisticated implementation could check
* for saturation sentinels or range limits. Returns 1 always.
*
* @param q Value to validate
* @return 1 always (placeholder for future range validation)
*/
int q48_is_valid(q48_16_t q)
{
/* Q48.16 is always valid if it fits in uint64_t */
/* More sophisticated validation could check for overflow risk */
return 1; /* For now, all uint64_t values are valid Q48.16 */
}
/* ============================================================================
* Future: FORTH Word Wrappers
* ============================================================================
*
* When integrated with FORTH word registry, these functions become:
*
* Q.+ ( q1 q2 -- q_sum ) -> forth_Q_ADD
* Q.- ( q1 q2 -- q_diff ) -> forth_Q_SUB
* Q.* ( q1 q2 -- q_prod ) -> forth_Q_MUL
* Q./ ( q1 q2 -- q_quot ) -> forth_Q_DIV
* Q.ABS ( q -- |q| ) -> forth_Q_ABS
* Q.LOG ( u -- ln(u) ) -> forth_Q_LOG
* Q.EXP ( q -- e^q ) -> forth_Q_EXP
* Q.SQRT ( q -- sqrt(q) ) -> forth_Q_SQRT
*
* Stack convention: All Q48.16 values pushed/popped as uint64_t
*/
+237
View File
@@ -0,0 +1,237 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/**
* q48_words.c — Module 25: Q48.16 fixed-point FORTH words
*
* FORTH word set for Q48.16 fixed-point arithmetic.
* All stack values are cell_t (int64_t) reinterpreted as uint64_t Q48.16.
* 1.0 = 0x10000 = 65536. Fractional resolution = 1/65536 ≈ 0.0000153.
*
* Words registered:
* Q.+ ( q1 q2 -- q_sum )
* Q.- ( q1 q2 -- q_diff )
* Q.* ( q1 q2 -- q_prod )
* Q./ ( q1 q2 -- q_quot ) division by zero → q=0
* Q.ABS ( q -- |q| )
* Q.NEG ( q -- -q )
* Q.LOG ( q -- ln_q ) natural log, Newton-Raphson
* Q.EXP ( q -- e^q ) Taylor series
* Q.SQRT ( q -- sqrt_q ) Newton-Raphson
* Q.SIN ( q -- sin_q ) Taylor series, radians, any magnitude
* Q.COS ( q -- cos_q ) Taylor series, radians, any magnitude
* Q.FROM-INT ( n -- q ) integer → Q48.16 (n << 16)
* Q.TO-INT ( q -- n ) Q48.16 → integer (q >> 16, truncate)
* Q.1 ( -- 65536 ) 1.0 in Q48.16
* Q.0 ( -- 0 ) 0.0 in Q48.16
* Q.SCALE ( -- 65536 ) scale factor (alias of Q.1)
* Q.= ( q1 q2 -- flag ) -1 if equal, 0 otherwise
* Q.< ( q1 q2 -- flag ) -1 if q1 < q2
* Q.> ( q1 q2 -- flag ) -1 if q1 > q2
* Q.0= ( q -- flag ) -1 if q = 0
* Q.MAX ( q1 q2 -- q_max )
* Q.MIN ( q1 q2 -- q_min )
* Q.PRINT ( q -- ) print as "integer.frac" to console
*/
#include <stdint.h>
#include <stddef.h>
#include "q48_16.h"
#include "vm.h"
#include "word_registry.h"
#ifdef __STARKERNEL__
#include "starkernel/freestanding/stdio.h"
#include "starkernel/console.h"
#else
#include <stdio.h>
#endif
/* ── helpers ──────────────────────────────────────────────────────────── */
static inline q48_16_t q48_pop(VM *vm) { return (q48_16_t)(uint64_t)VM_POP(vm); }
static inline void q48_push(VM *vm, q48_16_t q) { VM_PUSH(vm, (cell_t)(int64_t)q); }
/* ── arithmetic ──────────────────────────────────────────────────────── */
static void q48_word_add(VM *vm)
{
q48_16_t b = q48_pop(vm);
q48_16_t a = q48_pop(vm);
q48_push(vm, q48_add(a, b));
}
static void q48_word_sub(VM *vm)
{
q48_16_t b = q48_pop(vm);
q48_16_t a = q48_pop(vm);
q48_push(vm, q48_sub(a, b));
}
static void q48_word_mul(VM *vm)
{
q48_16_t b = q48_pop(vm);
q48_16_t a = q48_pop(vm);
q48_push(vm, q48_mul(a, b));
}
static void q48_word_div(VM *vm)
{
q48_16_t b = q48_pop(vm);
q48_16_t a = q48_pop(vm);
q48_push(vm, q48_div(a, b)); /* q48_div returns 0 on b=0 */
}
static void q48_word_abs(VM *vm)
{
q48_push(vm, q48_abs(q48_pop(vm)));
}
static void q48_word_neg(VM *vm)
{
q48_push(vm, (q48_16_t)(0ULL - q48_pop(vm)));
}
/* ── approximations ──────────────────────────────────────────────────── */
static void q48_word_log(VM *vm)
{
q48_16_t a = q48_pop(vm);
q48_push(vm, q48_log_approx((uint64_t)a));
}
static void q48_word_exp(VM *vm)
{
q48_push(vm, q48_exp_approx(q48_pop(vm)));
}
static void q48_word_sqrt(VM *vm)
{
q48_push(vm, q48_sqrt_approx(q48_pop(vm)));
}
static void q48_word_sin(VM *vm)
{
q48_push(vm, q48_sin_approx(q48_pop(vm)));
}
static void q48_word_cos(VM *vm)
{
q48_push(vm, q48_cos_approx(q48_pop(vm)));
}
/* ── conversions ─────────────────────────────────────────────────────── */
static void q48_word_from_int(VM *vm)
{
cell_t n = VM_POP(vm);
q48_push(vm, q48_from_u64((uint64_t)(n < 0 ? 0 : n)));
}
static void q48_word_to_int(VM *vm)
{
VM_PUSH(vm, (cell_t)q48_to_u64(q48_pop(vm)));
}
/* ── constants ───────────────────────────────────────────────────────── */
static void q48_word_one(VM *vm) { q48_push(vm, (q48_16_t)65536ULL); }
static void q48_word_zero(VM *vm) { q48_push(vm, (q48_16_t)0ULL); }
static void q48_word_scale(VM *vm) { q48_push(vm, (q48_16_t)65536ULL); }
/* ── comparisons ─────────────────────────────────────────────────────── */
static void q48_word_eq(VM *vm)
{
q48_16_t b = q48_pop(vm);
q48_16_t a = q48_pop(vm);
VM_PUSH(vm, (a == b) ? (cell_t)-1 : (cell_t)0);
}
static void q48_word_lt(VM *vm)
{
q48_16_t b = q48_pop(vm);
q48_16_t a = q48_pop(vm);
VM_PUSH(vm, (a < b) ? (cell_t)-1 : (cell_t)0);
}
static void q48_word_gt(VM *vm)
{
q48_16_t b = q48_pop(vm);
q48_16_t a = q48_pop(vm);
VM_PUSH(vm, (a > b) ? (cell_t)-1 : (cell_t)0);
}
static void q48_word_zero_eq(VM *vm)
{
q48_16_t a = q48_pop(vm);
VM_PUSH(vm, (a == 0ULL) ? (cell_t)-1 : (cell_t)0);
}
static void q48_word_max(VM *vm)
{
q48_16_t b = q48_pop(vm);
q48_16_t a = q48_pop(vm);
q48_push(vm, (a >= b) ? a : b);
}
static void q48_word_min(VM *vm)
{
q48_16_t b = q48_pop(vm);
q48_16_t a = q48_pop(vm);
q48_push(vm, (a <= b) ? a : b);
}
/* ── output ──────────────────────────────────────────────────────────── */
static void q48_word_print(VM *vm)
{
q48_16_t q = q48_pop(vm);
uint64_t int_part = q >> 16;
uint64_t frac_raw = q & 0xFFFFULL;
uint32_t frac_digits = (uint32_t)((frac_raw * 100000ULL) / 65536ULL);
char buf[32];
snprintf(buf, sizeof(buf), "%lu.%05u ", (unsigned long)int_part, frac_digits);
#ifdef __STARKERNEL__
console_puts(buf);
#else
fputs(buf, stdout);
fflush(stdout);
#endif
}
/* ── registration ────────────────────────────────────────────────────── */
void register_q48_words(VM *vm)
{
register_word(vm, "Q.+", q48_word_add);
register_word(vm, "Q.-", q48_word_sub);
register_word(vm, "Q.*", q48_word_mul);
register_word(vm, "Q./", q48_word_div);
register_word(vm, "Q.ABS", q48_word_abs);
register_word(vm, "Q.NEG", q48_word_neg);
register_word(vm, "Q.LOG", q48_word_log);
register_word(vm, "Q.EXP", q48_word_exp);
register_word(vm, "Q.SQRT", q48_word_sqrt);
register_word(vm, "Q.SIN", q48_word_sin);
register_word(vm, "Q.COS", q48_word_cos);
register_word(vm, "Q.FROM-INT", q48_word_from_int);
register_word(vm, "Q.TO-INT", q48_word_to_int);
register_word(vm, "Q.1", q48_word_one);
register_word(vm, "Q.0", q48_word_zero);
register_word(vm, "Q.SCALE", q48_word_scale);
register_word(vm, "Q.=", q48_word_eq);
register_word(vm, "Q.<", q48_word_lt);
register_word(vm, "Q.>", q48_word_gt);
register_word(vm, "Q.0=", q48_word_zero_eq);
register_word(vm, "Q.MAX", q48_word_max);
register_word(vm, "Q.MIN", q48_word_min);
register_word(vm, "Q.PRINT", q48_word_print);
}
+173
View File
@@ -0,0 +1,173 @@
/*
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 return_stack_words.c
* @brief Implementation of FORTH-79 standard return stack words
*
* StarForth - FORTH-79 Standard and ANSI C99 ONLY
* Last modified - 2025-09-10
* See LICENSE file — No warranty.
*
* FORTH-79 compliance:
* - Provide only >R, R>, R@ for return stack transfer.
* - No direct stack addressing (no RP!, RP@) — forbidden by FORTH-79.
*/
#include "include/return_stack_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
#include "../../include/vm.h"
/**
* @brief Move TOS from data stack to return stack (>R word)
* @param vm Pointer to VM instance
* @details Stack effect: ( x -- )
*/
static void return_stack_word_to_r(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, ">R: DSP underflow");
return;
}
if (vm->rsp + 1 >= STACK_SIZE) {
vm->error = 1;
log_message(LOG_ERROR, ">R: RSP overflow");
return;
}
cell_t x = vm_pop(vm);
if (vm->ecw_nesting > 0) {
/* Inside execute_colon_word: RS[rsp] holds the interpreter's resume IP.
* Insert x below the resume IP so the interpreter sees the correct IP. */
cell_t resume_ip = vm->return_stack[vm->rsp];
vm->return_stack[vm->rsp] = x;
vm->return_stack[vm->rsp + 1] = resume_ip;
vm->rsp++;
} else {
vm_rpush(vm, x);
}
}
/**
* @brief Move TOS from return stack back to data stack (R> word)
* @param vm Pointer to VM instance
* @details Stack effect: ( -- x )
*/
static void return_stack_word_r_from(VM *vm) {
if (vm->ecw_nesting > 0) {
/* Inside execute_colon_word: RS[rsp]=resume_ip, RS[rsp-1]=user value. */
if (vm->rsp < 1) {
vm->error = 1;
log_message(LOG_ERROR, "R>: RSP underflow");
return;
}
if (vm->dsp + 1 >= STACK_SIZE) {
vm->error = 1;
log_message(LOG_ERROR, "R>: DSP overflow");
return;
}
cell_t resume_ip = vm->return_stack[vm->rsp];
cell_t x = vm->return_stack[vm->rsp - 1];
vm->return_stack[vm->rsp - 1] = resume_ip;
vm->rsp--;
vm_push(vm, x);
} else {
if (vm->rsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "R>: RSP underflow");
return;
}
if (vm->dsp + 1 >= STACK_SIZE) {
vm->error = 1;
log_message(LOG_ERROR, "R>: DSP overflow");
return;
}
vm_push(vm, vm_rpop(vm));
}
}
/**
* @brief Copy TOS of return stack to data stack (R@ word)
* @param vm Pointer to VM instance
* @details Stack effect: ( -- x )
*/
static void return_stack_word_r_fetch(VM *vm) {
if (vm->ecw_nesting > 0) {
/* Inside execute_colon_word: RS[rsp]=resume_ip, RS[rsp-1]=user value. */
if (vm->rsp < 1) {
vm->error = 1;
log_message(LOG_ERROR, "R@: RSP underflow");
return;
}
if (vm->dsp + 1 >= STACK_SIZE) {
vm->error = 1;
log_message(LOG_ERROR, "R@: DSP overflow");
return;
}
vm_push(vm, vm->return_stack[vm->rsp - 1]);
} else {
if (vm->rsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "R@: RSP underflow");
return;
}
if (vm->dsp + 1 >= STACK_SIZE) {
vm->error = 1;
log_message(LOG_ERROR, "R@: DSP overflow");
return;
}
vm_push(vm, vm->return_stack[vm->rsp]);
}
}
/**
* @brief Register all FORTH-79 return stack manipulation words
* @param vm Pointer to VM instance
* @details Registers >R, R>, and R@ words. Direct stack addressing words
* (RP!, RP@) are deliberately omitted for FORTH-79 compliance.
*/
void register_return_stack_words(VM *vm) {
if (!vm) return;
register_word(vm, ">R", return_stack_word_to_r);
register_word(vm, "R>", return_stack_word_r_from);
register_word(vm, "R@", return_stack_word_r_fetch);
}
+52
View File
@@ -0,0 +1,52 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/* scroll_words.c — REPL scrollback entry points (FABRIC-0.md item 4.4q).
* Kernel-only; no-op on hosted builds. */
#include "include/scroll_words.h"
#include "../../include/log.h"
#include "../../include/word_registry.h"
#ifdef __STARKERNEL__
#include "starkernel/console.h"
/* SCROLL-BACK ( n -- ) -- move the REPL scrollback view back n lines.
* Relies on vm_pop()'s own underflow guard (sets vm->error, logs, returns
* 0) rather than a separate dsp precheck -- this codebase has more than
* one dsp convention across older word_source files (some treat it as a
* top-of-stack index, some as a raw count); vm_pop() is the one this word
* actually calls, so deferring to its own check sidesteps the mismatch
* entirely instead of risking silently picking the wrong one. */
static void scroll_word_back(VM *vm) {
cell_t n = vm_pop(vm);
if (vm->error) return;
if (n < 0) n = 0;
console_fb_scroll_back((uint32_t)n);
}
/* SCROLL-FWD ( n -- ) -- move the REPL scrollback view forward n lines
* (toward live). Same underflow-handling rationale as SCROLL-BACK above. */
static void scroll_word_fwd(VM *vm) {
cell_t n = vm_pop(vm);
if (vm->error) return;
if (n < 0) n = 0;
console_fb_scroll_fwd((uint32_t)n);
}
#endif /* __STARKERNEL__ */
void register_scroll_words(VM *vm)
{
#ifdef __STARKERNEL__
register_word(vm, "SCROLL-BACK", scroll_word_back);
register_word(vm, "SCROLL-FWD", scroll_word_fwd);
#else
(void) vm;
#endif
}
+341
View File
@@ -0,0 +1,341 @@
/*
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.
*/
/* stack_words.c - FORTH-79 Stack Operation Words - ANSI C99 ONLY */
/* FORTH-79 Stack Operation Words to implement:
* DROP ( n -- ) Remove top stack item
* DUP ( n -- n n ) Duplicate top stack item
* ?DUP ( n -- n n | n -- 0 ) Duplicate if non-zero
* SWAP ( n1 n2 -- n2 n1 ) Exchange top two stack items
* OVER ( n1 n2 -- n1 n2 n1 ) Copy second stack item to top
* ROT ( n1 n2 n3 -- n2 n3 n1 ) Rotate top three stack items
* -ROT ( n1 n2 n3 -- n3 n1 n2 ) Reverse rotate top three items
* DEPTH ( -- n ) Return number of stack items
* PICK ( n -- stack[n] ) Copy nth stack item to top
* ROLL ( n -- ) Move nth stack item to top
*/
#include "include/stack_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
/**
* @brief Implements DROP ( n -- ) - Removes top stack item
* @param vm Pointer to the virtual machine state
* @details Removes the top item from the data stack. Generates an error on stack underflow.
*/
static void stack_word_drop(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "DROP: Stack underflow");
vm->error = 1;
return;
}
vm->dsp--;
log_message(LOG_DEBUG, "DROP: Stack depth now %d", vm->dsp + 1);
}
/**
* @brief Implements DUP ( n -- n n ) - Duplicates top stack item
* @param vm Pointer to the virtual machine state
* @details Duplicates the top item on the data stack. Generates an error on stack underflow/overflow.
*/
static void stack_word_dup(VM *vm) {
#ifdef STARFORTH_PERFORMANCE
/* Fast path - skip bounds checking in performance builds */
if (UNLIKELY(vm->dsp < 0 || vm->dsp >= STACK_SIZE - 1)) {
vm->error = 1;
return;
}
cell_t value = vm->data_stack[vm->dsp];
vm->data_stack[++vm->dsp] = value;
#else
if (vm->dsp < 0) {
log_message(LOG_ERROR, "DUP: Stack underflow");
vm->error = 1;
return;
}
if (vm->dsp >= STACK_SIZE - 1) {
log_message(LOG_ERROR, "DUP: Stack overflow");
vm->error = 1;
return;
}
cell_t value = vm->data_stack[vm->dsp];
vm->data_stack[++vm->dsp] = value;
log_message(LOG_DEBUG, "DUP: Duplicated value");
#endif
}
/**
* @brief Implements ?DUP ( n -- n n | n -- 0 ) - Conditionally duplicates top stack item
* @param vm Pointer to the virtual machine state
* @details Duplicates top stack item if it's non-zero, otherwise leaves it unchanged.
*/
static void stack_word_question_dup(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "?DUP: Stack underflow");
vm->error = 1;
return;
}
cell_t value = vm->data_stack[vm->dsp];
if (value != 0) {
if (vm->dsp >= STACK_SIZE - 1) {
log_message(LOG_ERROR, "?DUP: Stack overflow");
vm->error = 1;
return;
}
vm->data_stack[++vm->dsp] = value;
log_message(LOG_DEBUG, "?DUP: Duplicated non-zero value");
} else {
log_message(LOG_DEBUG, "?DUP: Left zero value unchanged");
}
}
/**
* @brief Implements SWAP ( n1 n2 -- n2 n1 ) - Exchanges top two stack items
* @param vm Pointer to the virtual machine state
* @details Exchanges the positions of the top two items on the data stack.
*/
static void stack_word_swap(VM *vm) {
#ifdef STARFORTH_PERFORMANCE
/* Fast path - minimal checking in performance builds */
if (UNLIKELY(vm->dsp < 1)) {
vm->error = 1;
return;
}
cell_t top = vm->data_stack[vm->dsp];
cell_t second = vm->data_stack[vm->dsp - 1];
vm->data_stack[vm->dsp] = second;
vm->data_stack[vm->dsp - 1] = top;
#else
if (vm->dsp < 1) {
log_message(LOG_ERROR, "SWAP: Insufficient stack items (need 2)");
vm->error = 1;
return;
}
cell_t top = vm->data_stack[vm->dsp];
cell_t second = vm->data_stack[vm->dsp - 1];
vm->data_stack[vm->dsp] = second;
vm->data_stack[vm->dsp - 1] = top;
log_message(LOG_DEBUG, "SWAP: Exchanged top two values");
#endif
}
/**
* @brief Implements OVER ( n1 n2 -- n1 n2 n1 ) - Copies second stack item to top
* @param vm Pointer to the virtual machine state
* @details Copies the second item from the top of the stack to the top.
*/
static void stack_word_over(VM *vm) {
if (vm->dsp < 1) {
log_message(LOG_ERROR, "OVER: Insufficient stack items (need 2)");
vm->error = 1;
return;
}
if (vm->dsp >= STACK_SIZE - 1) {
log_message(LOG_ERROR, "OVER: Stack overflow");
vm->error = 1;
return;
}
cell_t second = vm->data_stack[vm->dsp - 1];
vm->data_stack[++vm->dsp] = second;
log_message(LOG_DEBUG, "OVER: Copied second to top");
}
/**
* @brief Implements ROT ( n1 n2 n3 -- n2 n3 n1 ) - Rotates top three stack items
* @param vm Pointer to the virtual machine state
* @details Rotates the top three items on the stack, moving the third item to the top.
*/
static void stack_word_rot(VM *vm) {
if (vm->dsp < 2) {
log_message(LOG_ERROR, "ROT: Insufficient stack items (need 3)");
vm->error = 1;
return;
}
cell_t n3 = vm->data_stack[vm->dsp]; /* top */
cell_t n2 = vm->data_stack[vm->dsp - 1]; /* second */
cell_t n1 = vm->data_stack[vm->dsp - 2]; /* third */
/* n1 n2 n3 -> n2 n3 n1 */
vm->data_stack[vm->dsp] = n1;
vm->data_stack[vm->dsp - 1] = n3;
vm->data_stack[vm->dsp - 2] = n2;
log_message(LOG_DEBUG, "ROT: Rotated top three items");
}
/**
* @brief Implements -ROT ( n1 n2 n3 -- n3 n1 n2 ) - Reverse rotates top three items
* @param vm Pointer to the virtual machine state
* @details Performs the reverse rotation of the top three stack items.
*/
static void stack_word_minus_rot(VM *vm) {
if (vm->dsp < 2) {
log_message(LOG_ERROR, "-ROT: Insufficient stack items (need 3)");
vm->error = 1;
return;
}
cell_t n3 = vm->data_stack[vm->dsp]; /* top */
cell_t n2 = vm->data_stack[vm->dsp - 1]; /* second */
cell_t n1 = vm->data_stack[vm->dsp - 2]; /* third */
/* n1 n2 n3 -> n3 n1 n2 */
vm->data_stack[vm->dsp] = n2;
vm->data_stack[vm->dsp - 1] = n1;
vm->data_stack[vm->dsp - 2] = n3;
log_message(LOG_DEBUG, "-ROT: Reverse rotated top three items");
}
/**
* @brief Implements DEPTH ( -- n ) - Returns number of stack items
* @param vm Pointer to the virtual machine state
* @details Pushes the current number of items on the data stack onto the stack.
*/
static void stack_word_depth(VM *vm) {
if (vm->dsp >= STACK_SIZE - 1) {
log_message(LOG_ERROR, "DEPTH: Stack overflow");
vm->error = 1;
return;
}
cell_t depth = vm->dsp + 1;
vm->data_stack[++vm->dsp] = depth;
log_message(LOG_DEBUG, "DEPTH: Stack depth returned");
}
/**
* @brief Implements PICK ( n -- stack[n] ) - Copies nth stack item to top
* @param vm Pointer to the virtual machine state
* @details Copies the nth item from the stack to the top, where n is popped from the stack.
*/
static void stack_word_pick(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "PICK: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm_pop(vm); /* pop (consume) the index */
if (n < 0 || n >= vm->dsp + 1) {
log_message(LOG_ERROR, "PICK: Invalid index %ld (stack depth: %d)", (long) n, vm->dsp + 1);
vm->error = 1;
return;
}
cell_t value = vm->data_stack[vm->dsp - n];
vm_push(vm, value);
log_message(LOG_DEBUG, "PICK: Copied item at index %ld to top", (long) n);
}
/**
* @brief Implements ROLL ( n -- ) - Moves nth stack item to top
* @param vm Pointer to the virtual machine state
* @details Moves the nth item from the stack to the top, shifting intermediate items down.
*/
static void stack_word_roll(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, "ROLL: Stack underflow");
vm->error = 1;
return;
}
cell_t n = vm->data_stack[vm->dsp--]; /* Pop n */
if (n < 0 || n > vm->dsp + 1) {
log_message(LOG_ERROR, "ROLL: Invalid index %ld (stack depth after pop: %d)", (long) n, vm->dsp + 1);
vm->error = 1;
return;
}
if (n == 0) {
log_message(LOG_DEBUG, "ROLL: n=0, no operation");
return;
}
/* 1-indexed from bottom: n=1 moves bottom item to top */
int bottom = 0;
int top = vm->dsp;
int target = bottom + (n - 1);
cell_t value = vm->data_stack[target];
for (int i = target; i < top; i++) {
vm->data_stack[i] = vm->data_stack[i + 1];
}
vm->data_stack[top] = value;
log_message(LOG_DEBUG, "ROLL: Moved item at index %ld to top", (long) n);
}
/**
* @brief Registers all stack operation words with the virtual machine
* @param vm Pointer to the virtual machine state
* @details Registers all FORTH-79 standard stack operation words with the word registry.
*/
void register_stack_words(VM *vm) {
register_word(vm, "DROP", stack_word_drop);
register_word(vm, "DUP", stack_word_dup);
register_word(vm, "?DUP", stack_word_question_dup);
register_word(vm, "SWAP", stack_word_swap);
register_word(vm, "OVER", stack_word_over);
register_word(vm, "ROT", stack_word_rot);
register_word(vm, "-ROT", stack_word_minus_rot);
register_word(vm, "DEPTH", stack_word_depth);
register_word(vm, "PICK", stack_word_pick);
register_word(vm, "ROLL", stack_word_roll);
}
+880
View File
@@ -0,0 +1,880 @@
/*
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.
*/
#include "include/starforth_words.h"
#include "include/vocabulary_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
#include "../../include/vm.h"
#ifdef __STARKERNEL__
#include "starkernel/vm/arena.h"
#endif
#include "include/vocabulary_words.h"
#include "../../include/version.h"
#include "../../include/physics_metadata.h"
#include "../../include/platform_time.h"
#include "../../include/profiler.h"
#include "../../include/block_subsystem.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#ifdef __STARKERNEL__
#define STARFORTH_CHECK_ARENA(tag) sk_vm_arena_assert_guards(tag)
#else
#define STARFORTH_CHECK_ARENA(tag) ((void)0)
#endif
/* ============================================================================
* PRNG State - Linear Congruential Generator (Numerical Recipes constants)
* ============================================================================ */
static uint64_t g_prng_state = 1;
/*
* @brief Get execution heat count for word at dictionary address
*
* Stack effect: ( addr -- n )
* Returns the execution frequency (execution_heat counter) for a word.
* Note: Exposed as ENTROPY@ for FORTH compatibility, but measures execution heat.
* @param vm Pointer to the VM instance
*/
void starforth_word_execution_heat_fetch(VM* vm)
{
if (vm->dsp < 0)
{
vm->error = 1;
log_message(LOG_ERROR, "ENTROPY@: data stack underflow");
return;
}
cell_t addr = vm_pop(vm);
DictEntry* entry = (DictEntry*)(uintptr_t)addr;
if (!entry)
{
vm->error = 1;
log_message(LOG_ERROR, "ENTROPY@: null dictionary entry");
return;
}
/* Guardrail: Validate the pointer is actually a DictEntry */
if (!vm_dict_entry_ok(vm, entry))
{
vm->error = 1;
log_message(LOG_ERROR, "ENTROPY@: invalid dictionary entry address %p (not in dictionary)", (void*)entry);
return;
}
vm_push(vm, entry->execution_heat);
log_message(LOG_DEBUG, "ENTROPY@: word execution heat = %ld", (long)entry->execution_heat);
}
/**
* @brief Set execution heat count for word at dictionary address
*
* Stack effect: ( n addr -- )
* Note: Exposed as ENTROPY! for FORTH compatibility, but sets execution heat.
* @param vm Pointer to the VM instance
*/
void starforth_word_execution_heat_store(VM* vm)
{
if (vm->dsp < 1)
{
vm->error = 1;
log_message(LOG_ERROR, "ENTROPY!: data stack underflow");
return;
}
cell_t addr = vm_pop(vm);
cell_t value = vm_pop(vm);
DictEntry* entry = (DictEntry*)(uintptr_t)addr;
if (!entry)
{
vm->error = 1;
log_message(LOG_ERROR, "ENTROPY!: null dictionary entry");
return;
}
/* Guardrail: Validate the pointer is actually a DictEntry */
if (!vm_dict_entry_ok(vm, entry))
{
vm->error = 1;
log_message(LOG_ERROR, "ENTROPY!: invalid dictionary entry address %p (not in dictionary)", (void*)entry);
return;
}
entry->execution_heat = value;
log_message(LOG_DEBUG, "ENTROPY!: set word execution heat to %ld", (long)value);
}
/**
* @brief Display execution heat statistics for all words in dictionary
*
* Stack effect: ( -- )
* Shows execution frequency (execution_heat) for each word.
* @param vm Pointer to the VM instance
*/
void starforth_word_word_execution_heat(VM* vm)
{
printf("Word Usage Statistics (Execution Heat Counts):\n");
printf("=============================================\n");
cell_t total_heat = 0;
int word_count = 0;
for (DictEntry* entry = vm->latest; entry; entry = entry->link)
{
if (entry->execution_heat > 0)
{
printf("%.*s: %ld\n", (int)entry->name_len, entry->name, (long)entry->execution_heat);
total_heat += entry->execution_heat;
}
word_count++;
}
printf("-------------------------------------\n");
printf("Total executions: %ld\n", (long)total_heat);
printf("Total words: %d\n", word_count);
if (total_heat > 0)
{
printf("Average executions per word: %ld\n", (long)(total_heat / word_count));
}
}
/**
* @brief Reset all execution heat counters to zero
*
* Stack effect: ( -- )
* @param vm Pointer to the VM instance
*/
void starforth_word_reset_execution_heat(VM* vm)
{
for (DictEntry* entry = vm->latest; entry; entry = entry->link)
{
if (entry->execution_heat > 0)
{
entry->execution_heat = 0;
entry->physics.temperature_q8 = 0;
entry->physics.avg_latency_ns = 0;
entry->physics.last_active_ns = 0;
}
}
}
/**
* @brief Display the N most frequently used words
*
* Stack effect: ( n -- )
* @param vm Pointer to the VM instance
*/
void starforth_word_top_words(VM* vm)
{
if (vm->dsp < 0)
{
vm->error = 1;
log_message(LOG_ERROR, "TOP-WORDS: data stack underflow");
return;
}
cell_t n = vm_pop(vm);
if (n <= 0)
{
printf("TOP-WORDS: invalid count %ld\n", (long)n);
return;
}
/* Simple implementation: just show words with execution_heat > 0, sorted by execution_heat */
printf("Top %ld most frequently used words:\n", (long)n);
printf("==================================\n");
/* Collect all words with execution_heat > 0 (static: avoids large kernel stack frame) */
static DictEntry* words_with_heat[1000];
int word_count = 0;
for (DictEntry* entry = vm->latest; entry && word_count < 1000; entry = entry->link)
{
if (entry->execution_heat > 0)
{
words_with_heat[word_count++] = entry;
}
}
/* Simple bubble sort by execution_heat (descending) */
for (int i = 0; i < word_count - 1; i++)
{
for (int j = 0; j < word_count - i - 1; j++)
{
if (words_with_heat[j]->execution_heat < words_with_heat[j + 1]->execution_heat)
{
DictEntry* temp = words_with_heat[j];
words_with_heat[j] = words_with_heat[j + 1];
words_with_heat[j + 1] = temp;
}
}
}
/* Display top N */
int display_count = (word_count < n) ? word_count : (int)n;
for (int i = 0; i < display_count; i++)
{
DictEntry* entry = words_with_heat[i];
printf("%d. %.*s: %ld\n", i + 1, (int)entry->name_len, entry->name, (long)entry->execution_heat);
}
}
/**
* @brief Shebang-style comment word for init.4th metadata
*
* Starts with "(- " and consumes input until closing ")"
* Used for marking blocks that should be extracted to init.4th
* Stack effect: ( -- )
* @param vm Pointer to the VM instance
*/
void starforth_word_paren_dash(VM* vm)
{
/* Consume input until we find closing ")" */
int depth = 1;
while (vm->input_pos < vm->input_length && depth > 0)
{
char c = vm->input_buffer[vm->input_pos++];
if (c == '(')
{
depth++;
}
else if (c == ')')
{
depth--;
}
}
if (depth > 0)
{
log_message(LOG_WARN, "(- comment not terminated");
}
/* This is a comment - no stack effect, just consume input */
log_message(LOG_DEBUG, "(- comment parsed (init.4th metadata marker)");
}
/**
* @brief Initialize system from init.4th configuration file
*
* Reads ./capsules/core/init.4th, parses Block headers,
* copies blocks sequentially starting at block 1, then executes them.
* Stack effect: ( -- )
* @param vm Pointer to the VM instance
*/
void starforth_word_init(VM* vm)
{
log_message(LOG_INFO, "INIT: Starting system initialization from init.4th");
/* Read init.4th file */
char* file_content = NULL;
size_t file_size = 0;
/* HISTORICAL: an L4RE_TARGET branch here read init.4th from ROMFS
* (never implemented -- it just logged an error and halted). L4Re
* support has been removed as an active target.
* #ifdef L4RE_TARGET
* log_message(LOG_ERROR, "INIT: L4Re ROMFS not yet implemented");
* vm->error = 1;
* vm->halted = 1;
* return;
* #else
*/
/* Linux/POSIX: read from filesystem */
const char* init_path = "./capsules/core/init.4th";
FILE* fp = fopen(init_path, "r");
if (!fp)
{
if (errno == ENOENT)
{
log_message(LOG_INFO, "INIT: %s not found — skipping", init_path);
return;
}
log_message(LOG_ERROR, "INIT: Failed to open %s: %s", init_path, strerror(errno));
vm->error = 1;
vm->halted = 1;
return;
}
/* Get file size */
fseek(fp, 0, SEEK_END);
file_size = (size_t)ftell(fp);
fseek(fp, 0, SEEK_SET);
/* Allocate buffer */
file_content = (char*)malloc(file_size + 1);
if (!file_content)
{
log_message(LOG_ERROR, "INIT: Failed to allocate memory for init.4th (%zu bytes)", file_size);
fclose(fp);
vm->error = 1;
vm->halted = 1;
return;
}
/* Read entire file */
size_t bytes_read = fread(file_content, 1, file_size, fp);
fclose(fp);
if (bytes_read != file_size)
{
log_message(LOG_ERROR, "INIT: Failed to read init.4th (expected %zu, got %zu bytes)", file_size, bytes_read);
free(file_content);
vm->error = 1;
vm->halted = 1;
return;
}
file_content[file_size] = '\0';
log_message(LOG_DEBUG, "INIT: Read %zu bytes from %s", file_size, init_path);
/* #endif -- matches the commented-out #ifdef L4RE_TARGET above */
/* First pass: build mapping of original block numbers to sequential numbers */
typedef struct
{
int original;
int sequential;
} BlockMapping;
BlockMapping block_map[256]; /* Support up to 256 init blocks */
int block_count = 0;
char* scan_start = file_content;
for (size_t i = 0; i <= file_size; i++)
{
char c = (i < file_size) ? file_content[i] : '\n';
if (c == '\n' || i == file_size)
{
size_t line_len = (size_t)(&file_content[i] - scan_start);
/* Check if this line starts with "Block " */
if (line_len >= 6 && strncmp(scan_start, "Block ", 6) == 0)
{
/* Parse the block number */
int orig_block_num = 0;
if (sscanf(scan_start + 6, "%d", &orig_block_num) == 1)
{
if (block_count < 256)
{
block_map[block_count].original = orig_block_num;
block_map[block_count].sequential = block_count + 1;
log_message(LOG_DEBUG, "INIT: Block mapping: %d -> %d",
orig_block_num, block_count + 1);
block_count++;
}
}
}
scan_start = &file_content[i + 1];
}
}
/* Second pass: copy blocks with LOAD reference rewriting */
int current_dest_block = 1;
char* line_start = file_content;
char* block_content_start = NULL;
int in_block = 0;
for (size_t i = 0; i <= file_size; i++)
{
char c = (i < file_size) ? file_content[i] : '\n';
/* End of line or end of file */
if (c == '\n' || i == file_size)
{
size_t line_len = (size_t)(&file_content[i] - line_start);
/* Check if this line starts with "Block " */
if (line_len >= 6 && strncmp(line_start, "Block ", 6) == 0)
{
/* If we were already in a block, write the previous block */
if (in_block && block_content_start)
{
size_t block_len = (size_t)(line_start - block_content_start);
/* Get buffer for destination block */
uint8_t* buf = blk_get_buffer((uint32_t)current_dest_block, 1);
if (!buf)
{
log_message(LOG_ERROR, "INIT: Failed to get buffer for block %d", current_dest_block);
free(file_content);
vm->error = 1;
vm->halted = 1;
return;
}
/* Clear block and copy with LOAD rewriting */
memset(buf, 0, 1024);
size_t copy_len = (block_len > 1024) ? 1024 : block_len;
/* Simple approach: scan and replace "NNNN LOAD" patterns */
size_t src = 0, dst = 0;
while (src < copy_len && dst < 1024)
{
/* Check for digit start */
if (block_content_start[src] >= '0' && block_content_start[src] <= '9')
{
/* Parse number */
int num = 0;
size_t num_start = src;
while (src < copy_len && block_content_start[src] >= '0' &&
block_content_start[src] <= '9')
{
num = num * 10 + (block_content_start[src++] - '0');
}
/* Check for whitespace + LOAD */
size_t ws_start = src;
while (src < copy_len && (block_content_start[src] == ' ' ||
block_content_start[src] == '\t'))
{
src++;
}
if (src + 4 <= copy_len && strncmp(&block_content_start[src], "LOAD", 4) == 0)
{
/* Remap block number */
int new_num = num;
for (int m = 0; m < block_count; m++)
{
if (block_map[m].original == num)
{
new_num = block_map[m].sequential;
log_message(LOG_INFO, "INIT: Rewrote %d LOAD -> %d LOAD", num, new_num);
break;
}
}
/* Write remapped number + whitespace + LOAD */
char rewrite[32];
int rewrite_len = snprintf(rewrite, sizeof(rewrite), "%d", new_num);
if (dst + rewrite_len < 1024)
{
memcpy(&buf[dst], rewrite, rewrite_len);
dst += rewrite_len;
}
/* Copy whitespace */
size_t ws_len = src - ws_start;
if (dst + ws_len < 1024)
{
memcpy(&buf[dst], &block_content_start[ws_start], ws_len);
dst += ws_len;
}
/* Copy LOAD */
if (dst + 4 < 1024)
{
memcpy(&buf[dst], "LOAD", 4);
dst += 4;
}
src += 4;
}
else
{
/* Not LOAD, copy number as-is */
size_t num_len = src - num_start;
if (dst + num_len < 1024)
{
memcpy(&buf[dst], &block_content_start[num_start], num_len);
dst += num_len;
}
}
}
else
{
/* Regular char */
buf[dst++] = block_content_start[src++];
}
}
log_message(LOG_INFO, "INIT: Copied block content to block %d (%zu bytes)",
current_dest_block, dst);
current_dest_block++;
}
/* Start new block - content begins on next line */
in_block = 1;
block_content_start = &file_content[i + 1];
}
line_start = &file_content[i + 1];
}
}
/* Write final block if we were in one */
if (in_block && block_content_start)
{
size_t block_len = (size_t)(&file_content[file_size] - block_content_start);
uint8_t* buf = blk_get_buffer((uint32_t)current_dest_block, 1);
if (!buf)
{
log_message(LOG_ERROR, "INIT: Failed to get buffer for final block %d", current_dest_block);
free(file_content);
vm->error = 1;
vm->halted = 1;
return;
}
memset(buf, 0, 1024);
size_t copy_len = (block_len > 1024) ? 1024 : block_len;
memcpy(buf, block_content_start, copy_len);
log_message(LOG_INFO, "INIT: Copied block content to block %d (%zu bytes)",
current_dest_block, copy_len);
current_dest_block++;
}
free(file_content);
int total_blocks = current_dest_block - 1;
log_message(LOG_INFO, "INIT: Loaded %d blocks from init.4th", total_blocks);
/* Execute all blocks sequentially */
log_message(LOG_INFO, "INIT: Executing initialization blocks...");
for (int i = 1; i < current_dest_block; i++)
{
log_message(LOG_DEBUG, "INIT: Executing block %d (LOAD)", i);
/* Push block number and execute LOAD */
vm_push(vm, (cell_t)i);
/* Find and execute LOAD word */
DictEntry* load_word = vm_find_word(vm, "LOAD", 4);
if (!load_word)
{
log_message(LOG_ERROR, "INIT: LOAD word not found in dictionary");
vm->error = 1;
vm->halted = 1;
return;
}
vm->current_executing_entry = load_word;
physics_execution_heat_increment(load_word);
profiler_word_count(load_word);
profiler_word_enter(load_word);
load_word->func(vm);
physics_metadata_touch(load_word, load_word->execution_heat, sf_monotonic_ns());
profiler_word_exit(load_word);
vm->current_executing_entry = NULL;
/* Check for errors after each block */
if (vm->error)
{
log_message(LOG_ERROR, "INIT: Error executing block %d - system halted", i);
vm->halted = 1;
return;
}
}
/* Switch back to FORTH vocabulary */
log_message(LOG_INFO, "INIT: Switching to FORTH vocabulary");
vm_interpret(vm, "FORTH DEFINITIONS");
if (vm->error)
{
log_message(LOG_ERROR, "INIT: Failed to switch to FORTH vocabulary - system halted");
vm->halted = 1;
return;
}
/* Zero all init blocks to free them for userspace */
log_message(LOG_INFO, "INIT: Zeroing %d init blocks for userspace use", total_blocks);
for (int i = 1; i < current_dest_block; i++)
{
uint8_t* buf = blk_get_buffer((uint32_t)i, 1);
if (buf)
{
memset(buf, 0, 1024);
log_message(LOG_DEBUG, "INIT: Zeroed block %d", i);
}
else
{
log_message(LOG_WARN, "INIT: Failed to zero block %d (non-critical)", i);
}
}
log_message(LOG_INFO, "INIT: System initialization complete - blocks freed, FORTH context active");
}
/* ============================================================================
* PRNG / Utility Words
* ============================================================================ */
/**
* @brief Internal PRNG step using Linear Congruential Generator
*
* Uses Numerical Recipes LCG constants for good statistical properties.
* @return Next pseudo-random 64-bit value
*/
static uint64_t prng_next(void)
{
/* LCG: state = (a * state + c) mod m, where m = 2^64 (implicit) */
g_prng_state = g_prng_state * 6364136223846793005ULL + 1442695040888963407ULL;
return g_prng_state;
}
/**
* @brief Set the PRNG seed
*
* Stack effect: ( n -- )
* Sets the internal PRNG state for reproducible random sequences.
* @param vm Pointer to the VM instance
*/
void starforth_word_seed(VM* vm)
{
if (vm->dsp < 0)
{
vm->error = 1;
log_message(LOG_ERROR, "SEED: data stack underflow");
return;
}
cell_t seed = vm_pop(vm);
g_prng_state = (uint64_t)seed;
/* Ensure non-zero state (LCG weakness) */
if (g_prng_state == 0)
{
g_prng_state = 1;
}
log_message(LOG_DEBUG, "SEED: PRNG seeded with %lu", (unsigned long)g_prng_state);
}
/*
* @brief Generate bounded random number
*
* Stack effect: ( lo hi -- n )
* Returns a pseudo-random number in the inclusive range [lo, hi].
* @param vm Pointer to the VM instance
*/
void starforth_word_random(VM* vm)
{
if (vm->dsp < 1)
{
vm->error = 1;
log_message(LOG_ERROR, "RANDOM: data stack underflow (need lo hi)");
return;
}
cell_t hi = vm_pop(vm);
cell_t lo = vm_pop(vm);
/* Handle inverted range */
if (lo > hi)
{
cell_t tmp = lo;
lo = hi;
hi = tmp;
}
/* Generate random value in range [lo, hi] inclusive */
uint64_t range = (uint64_t)(hi - lo) + 1;
uint64_t raw = prng_next();
/* Modulo bias reduction: use upper bits which have better randomness in LCG */
cell_t result = lo + (cell_t)((raw >> 16) % range);
vm_push(vm, result);
log_message(LOG_DEBUG, "RANDOM: [%ld, %ld] -> %ld", (long)lo, (long)hi, (long)result);
}
/**
* @brief Wait for specified number of heartbeat ticks
*
* Stack effect: ( n -- )
* Counts n heartbeat ticks by calling vm_tick() n times.
* Time is relative — we count heartbeats, not wall-clock milliseconds.
* This is architecture-independent: works on amd64, aarch64, riscv64
* without depending on any platform timer.
* @param vm Pointer to the VM instance
*/
void starforth_word_wait(VM* vm)
{
if (vm->dsp < 0)
{
vm->error = 1;
log_message(LOG_ERROR, "WAIT: stack underflow");
return;
}
cell_t ticks = vm_pop(vm);
cell_t i;
if (ticks <= 0)
return;
for (i = 0; i < ticks; i++)
vm_tick(vm);
}
/**
* @brief Print StarForth version information (compliance)
*
* Prints: StarForth v<version> <architecture> <variant> <timestamp>
* Stack effect: ( -- )
* @param vm Pointer to the VM instance
*/
void starforth_word_version(VM* vm)
{
(void)vm; /* Unused parameter */
printf("%s\n", STARFORTH_VERSION_FULL);
}
/* ZUSE-AUTHENTICATE ( -- ) Sets zuse_session=1; C-only write; god-mode bypass */
static void starforth_word_zuse_authenticate(VM *vm)
{
vm->zuse_session = 1;
}
/* ZUSE-SESSION? ( -- flag ) Read-only diagnostic (FABRIC-2.md §F.21,
* added 2026-08-28): confirms whether ZUSE-AUTHENTICATE has actually run
* this boot. No corresponding write access -- matches ZUSE-PUBKEY@'s own
* read-only-window convention. */
static void starforth_word_zuse_session(VM *vm)
{
vm_push(vm, vm->zuse_session ? -1 : 0);
}
/* ZUSE-PUBKEY@ ( i -- u ) Read-only: fetch 8-byte little-endian chunk i
* (0..3) of Zuse's 32-byte Ed25519 public key as one cell. Out-of-range i
* pushes 0 and sets vm->error rather than faulting. No FORTH word can
* write these bytes or read the seed -- the cert is written exactly once,
* in C, via vm_zuse_cert_install(); this is a read-only window onto the
* PUBLIC half only. */
static void starforth_word_zuse_pubkey_fetch(VM *vm)
{
if (vm->dsp < 0) {
log_message(LOG_ERROR, "ZUSE-PUBKEY@: stack underflow");
vm->error = 1;
return;
}
cell_t i = vm_pop(vm);
if (i < 0 || i > 3) {
vm_push(vm, 0);
vm->error = 1;
return;
}
const uint8_t *p = &vm->zuse_cert_pubkey[i * 8];
cell_t chunk = 0;
for (int b = 7; b >= 0; b--) {
chunk = (chunk << 8) | (cell_t)p[b];
}
vm_push(vm, chunk);
}
/* ZUSE-CERT-INSTALLED? ( -- flag ) -1 if the one-time cert fuse has been
* blown (vm_zuse_cert_install() has succeeded), 0 otherwise. */
static void starforth_word_zuse_cert_installed_query(VM *vm)
{
vm_push(vm, vm->zuse_cert_installed ? -1 : 0);
}
/**
* @brief Read-only accessor for the canonical heartbeat tick counter
*
* Stack effect: ( -- n )
* Pushes vm->heartbeat.tick_count -- Loop #7 "Adaptive Heartrate", the one
* clock this project's timing measurements are supposed to read, not host
* wall-clock. Read-only: no corresponding store word exists or should exist.
* @param vm Pointer to the VM instance
*/
static void starforth_word_heartbeat_ticks(VM* vm)
{
vm_push(vm, (cell_t)vm->heartbeat.tick_count);
}
/**
* @brief Register StarForth vocabulary words with the VM
*
* Registers all StarForth-specific words and creates the STARFORTH vocabulary
* @param vm Pointer to the VM instance
*/
void register_starforth_words(VM* vm)
{
STARFORTH_CHECK_ARENA("register_starforth_words:entry");
register_word(vm, "WORD-ENTROPY", starforth_word_word_execution_heat);
register_word(vm, "RESET-ENTROPY", starforth_word_reset_execution_heat);
register_word(vm, "TOP-WORDS", starforth_word_top_words);
register_word(vm, "(-", starforth_word_paren_dash);
register_word(vm, "INIT", starforth_word_init);
register_word(vm, "VERSION", starforth_word_version);
register_word(vm, "SEED", starforth_word_seed);
register_word(vm, "RANDOM", starforth_word_random);
register_word(vm, "WAIT", starforth_word_wait);
register_word(vm, "ZUSE-AUTHENTICATE", starforth_word_zuse_authenticate);
register_word(vm, "ZUSE-SESSION?", starforth_word_zuse_session);
register_word(vm, "ZUSE-PUBKEY@", starforth_word_zuse_pubkey_fetch);
register_word(vm, "ZUSE-CERT-INSTALLED?", starforth_word_zuse_cert_installed_query);
register_word(vm, "HEARTBEAT-TICKS@", starforth_word_heartbeat_ticks);
vm_bootstrap_root_vocabulary(vm, "STARFORTH");
STARFORTH_CHECK_ARENA("register_starforth_words:post-root");
/* Re-register the words in the STARFORTH vocabulary context */
register_word(vm, "ENTROPY@", starforth_word_execution_heat_fetch);
register_word(vm, "ENTROPY!", starforth_word_execution_heat_store);
register_word(vm, "WORD-ENTROPY", starforth_word_word_execution_heat);
register_word(vm, "RESET-ENTROPY", starforth_word_reset_execution_heat);
register_word(vm, "TOP-WORDS", starforth_word_top_words);
register_word(vm, "(-", starforth_word_paren_dash);
register_word(vm, "INIT", starforth_word_init);
register_word(vm, "VERSION", starforth_word_version);
register_word(vm, "SEED", starforth_word_seed);
register_word(vm, "RANDOM", starforth_word_random);
register_word(vm, "WAIT", starforth_word_wait);
register_word(vm, "ZUSE-AUTHENTICATE", starforth_word_zuse_authenticate);
register_word(vm, "ZUSE-SESSION?", starforth_word_zuse_session);
register_word(vm, "ZUSE-PUBKEY@", starforth_word_zuse_pubkey_fetch);
register_word(vm, "ZUSE-CERT-INSTALLED?", starforth_word_zuse_cert_installed_query);
register_word(vm, "HEARTBEAT-TICKS@", starforth_word_heartbeat_ticks);
vocabulary_word_forth(vm);
vocabulary_word_definitions(vm);
STARFORTH_CHECK_ARENA("register_starforth_words:exit");
}
File diff suppressed because it is too large Load Diff
+614
View File
@@ -0,0 +1,614 @@
/*
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.
*/
#include "include/system_words.h"
#include "../../include/vm.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "starkernel/kernel_args.h"
#ifdef __STARKERNEL__
#include "starkernel/uefi.h"
#include "starkernel/console.h"
extern EFI_RUNTIME_SERVICES *g_sk_runtime_services;
#endif
/* ───────────────────────── Global system state ───────────────────────── */
/** @brief Flag indicating if the Forth system is currently running */
static int system_running = 1;
/** @brief Flag controlling FORTH-79 standard compliance mode */
static int forth_79_standard = 1;
/* ─────────────────────────── Utilities/helpers ───────────────────────── */
/* Print a word’s name (no newline) */
static void print_word_name(const DictEntry *entry) {
if (!entry) return;
for (size_t i = 0; i < entry->name_len; i++) {
putchar((unsigned char) entry->name[i]);
}
}
/* Reset VM to a known state. If cold_start!=0, also rewinds HERE a bit. */
static void reset_vm_state(VM *vm, int cold_start) {
if (!vm) return;
/* Reset stacks */
vm->dsp = -1;
vm->rsp = -1;
/* Clear error state & enter interpret mode */
vm->error = 0;
vm->mode = MODE_INTERPRET;
if (cold_start) {
/* In a complete system, this would restore a boot image.
For now we keep a minimal fence by not crushing the base dictionary. */
if (vm->here > 1024) vm->here = 1024;
}
system_running = 1;
}
/* ───────────────────────────── Core words ───────────────────────────── */
/* ( ( -- ) : Begin comment; skip to closing ) — IMMEDIATE */
static void forth_paren(VM *vm) {
char word[64];
int paren_depth = 1;
while (paren_depth > 0 && vm_parse_word(vm, word, sizeof(word))) {
if (strcmp(word, "(") == 0) {
paren_depth++;
} else if (strcmp(word, ")") == 0) {
paren_depth--;
}
/* else: ignore */
}
if (paren_depth > 0) {
log_message(LOG_WARN, "( Unterminated comment");
}
}
/* \ ( -- ) : Line comment; skip from current position to end of line — IMMEDIATE */
static void forth_backslash(VM *vm) {
if (!vm) return;
while (vm->input_pos < vm->input_length) {
char c = vm->input_buffer[vm->input_pos++];
if (c == '\n') break;
}
}
/* COLD ( -- ) */
void system_word_cold(VM *vm) {
puts("FORTH-79 Cold Start");
reset_vm_state(vm, 1);
puts("System initialized.");
}
/* WARM ( -- ) */
void system_word_warm(VM *vm) {
puts("FORTH-79 Warm Start");
reset_vm_state(vm, 0);
puts("System restarted.");
}
/* BYE ( -- ) */
void system_word_bye(VM *vm) {
puts("Goodbye!");
vm->halted = 1; /* Signal the REPL to stop */
}
/* SAVE-SYSTEM ( -- ) : trivial snapshot of memory prefix */
void system_word_save_system(VM *vm) {
FILE *save_file = fopen("forth_system.img", "wb");
if (!save_file) {
puts("Error: Cannot create system image file");
vm->error = 1;
return;
}
if (fwrite(&vm->here, sizeof(vm->here), 1, save_file) != 1) {
puts("Error: Failed to save system state");
fclose(save_file);
vm->error = 1;
return;
}
if (fwrite(vm->memory, 1, vm->here, save_file) != (size_t) vm->here) {
puts("Error: Failed to save memory image");
fclose(save_file);
vm->error = 1;
return;
}
fclose(save_file);
puts("System image saved successfully");
}
/* WORDS ( -- ) : list words in current vocabulary */
void system_word_words(VM *vm) {
DictEntry *e = vm->latest;
int count = 0, per_line = 0;
puts("Words in current vocabulary:");
while (e) {
print_word_name(e);
putchar(' ');
count++;
per_line++;
if (per_line >= 8) {
putchar('\n');
per_line = 0;
}
e = e->link;
}
if (per_line) putchar('\n');
printf("Total: %d words\n", count);
}
/* VLIST ( -- ) : detailed listing */
void system_word_vlist(VM *vm) {
DictEntry *e = vm->latest;
int count = 0;
puts("Complete vocabulary listing:");
puts("Name Address Flags");
puts("-------------------- ---------- -----");
while (e) {
/* Name padded to 20 chars */
size_t pad = (e->name_len < 20) ? (20 - e->name_len) : 0;
print_word_name(e);
while (pad--) putchar(' ');
printf(" %p %02lX\n", (void *) e, (unsigned long)e->flags);
count++;
e = e->link;
}
printf("Total: %d words\n", count);
}
/* PAGE ( -- ) */
void system_word_page(VM *vm) {
(void) vm;
printf("\033[2J\033[H");
fflush(stdout);
}
/* NOP ( -- ) */
void system_word_nop(VM *vm) { (void) vm; }
/* 79-STANDARD ( -- flag ) : push -1 if compliance active else 0 */
void system_word_79_standard(VM *vm) {
if (forth_79_standard) {
puts("FORTH-79 Standard compliance: ACTIVE");
puts("System conforms to FORTH-79 specification");
} else {
puts("FORTH-79 Standard compliance: INACTIVE");
puts("System may have extensions or modifications");
}
vm_push(vm, forth_79_standard ? -1 : 0);
}
/* QUIT ( -- ) : FORTH-79 — clear return stack, return to interpreter. IMMEDIATE */
static void system_word_quit(VM *vm) {
if (vm->mode == MODE_COMPILE) {
vm->error = 1;
return;
}
vm->rsp = -1;
vm->mode = MODE_INTERPRET;
vm->error = 0;
}
/* ABORT ( -- ) : Clear both stacks & return to interpreter. NOT an error. */
static void system_word_abort(VM *vm) {
if (!vm) return;
reset_vm_state(vm, 0);
vm->error = 0;
vm->abort_requested = 1; /* Signal immediate termination */
}
/* (ABORT") runtime
Run-time stack: flag addr len --
If flag ≠ 0: print message and perform ABORT semantics (no error). */
static void runtime_abortq(VM *vm) {
if (!vm) return;
if (vm->dsp < 2) {
vm->error = 1;
return;
}
cell_t len = vm_pop(vm);
cell_t addr = vm_pop(vm);
cell_t flag = vm_pop(vm);
if (!flag) return; /* false => no-op */
if (!vm_addr_ok(vm, (vaddr_t) addr, (size_t) len)) {
vm->error = 1;
return;
}
fwrite(vm->memory + (size_t) addr, 1, (size_t) len, stdout);
fputc('\n', stdout);
reset_vm_state(vm, 0); /* control transfer, not a fault */
vm->error = 0;
}
/* ABORT" ( flag -- ) IMMEDIATE
Interpret: parse up to next " ; if flag≠0 print string and ABORT (no error).
Compile: parse string; compile: LIT addr LIT len (ABORT") so at run-time
the stack is: flag addr len and runtime_abortq handles it. */
static void system_word_abort_quote(VM *vm) {
if (!vm) return;
/* Parse message up to closing quote from current input position. */
size_t pos = vm->input_pos, n = vm->input_length;
while (pos < n && (vm->input_buffer[pos] == ' ' || vm->input_buffer[pos] == '\t')) pos++;
size_t start = pos;
while (pos < n && vm->input_buffer[pos] != '"') pos++;
if (pos >= n) {
vm->error = 1;
return;
} /* unmatched quote */
size_t end = pos;
vm->input_pos = pos + 1; /* consume the closing quote */
const size_t msg_len = end - start;
if (vm->mode == MODE_INTERPRET) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t flag = vm_pop(vm);
if (!flag) return;
if (msg_len) fwrite(&vm->input_buffer[start], 1, msg_len, stdout);
fputc('\n', stdout);
reset_vm_state(vm, 0);
vm->error = 0;
return;
}
/* MODE_COMPILE */
void *dst = NULL;
if (msg_len) {
dst = vm_allot(vm, msg_len);
if (!dst) {
vm->error = 1;
return;
}
memcpy(dst, &vm->input_buffer[start], msg_len);
}
vaddr_t msg_addr = (vaddr_t) 0;
if (msg_len) msg_addr = (vaddr_t)((uint8_t *) dst - vm->memory);
vm_compile_literal(vm, (cell_t) msg_addr);
vm_compile_literal(vm, (cell_t) msg_len);
/* Compile call to the runtime helper (must be registered) */
vm_compile_call(vm, (word_func_t) runtime_abortq);
}
/* SEE ( "name" -- ) Decompile and display a word's definition */
static void system_word_see(VM *vm) {
if (!vm) return;
/* Parse word name */
char namebuf[WORD_NAME_MAX + 1];
int nlen = vm_parse_word(vm, namebuf, sizeof(namebuf));
if (nlen <= 0) {
printf("SEE: word name required\n");
vm->error = 1;
return;
}
/* Find the word */
DictEntry *entry = vm_find_word(vm, namebuf, (size_t) nlen);
if (!entry) {
printf("SEE: '%.*s' not found\n", nlen, namebuf);
vm->error = 1;
return;
}
/* Print word name and start of definition */
printf(": ");
print_word_name(entry);
printf("\n");
/* Check if it's a primitive (built-in) or colon word */
cell_t *df = vm_dictionary_get_data_field(entry);
if (!df) {
printf(" <primitive>\n;\n");
return;
}
/* Check if it's a colon word by comparing func pointer */
extern void execute_colon_word(VM * vm); /* Forward declaration */
if (entry->func != execute_colon_word) {
/* It's a primitive with data (like CONSTANT, VARIABLE, CREATE) */
printf(" <primitive with data: %ld>\n;\n", (long) *df);
return;
}
/* It's a colon word - decompile the threaded code */
vaddr_t body_addr = (vaddr_t)(uint64_t)(*df);
if (!vm_addr_ok(vm, body_addr, sizeof(cell_t))) {
printf(" <invalid body address>\n;\n");
return;
}
cell_t *ip = (cell_t *) vm_ptr(vm, body_addr);
int indent = 2;
int word_count = 0;
/* Walk through threaded code until EXIT */
while (word_count < 1000) {
/* Safety limit */
/* Check if we're still within valid VM memory */
vaddr_t current_addr = (vaddr_t)((uint8_t *) ip - vm->memory);
if (!vm_addr_ok(vm, current_addr, sizeof(cell_t))) {
printf("\n <invalid address>\n");
break;
}
DictEntry *w = (DictEntry *) (uintptr_t)(*ip);
ip++;
word_count++;
/* Basic sanity checks on the entry pointer */
if (!w) {
printf("\n <null entry>\n");
break;
}
/* Check if name_len is reasonable (0-32 chars) */
if (w->name_len == 0 || w->name_len > 32) {
printf("\n <invalid name_len=%d>\n", w->name_len);
break;
}
/* Print indentation */
for (int i = 0; i < indent; i++) putchar(' ');
/* Print word name */
print_word_name(w);
/* Check for EXIT - end of definition */
if (w->name_len == 4 && strncmp(w->name, "EXIT", 4) == 0) {
printf("\n");
break;
}
/* Check for special words that have inline data */
if (w->name_len == 3 && strncmp(w->name, "LIT", 3) == 0) {
/* Next cell is the literal value */
cell_t lit_val = *ip++;
word_count++;
printf(" %ld", (long) lit_val);
} else if (w->name_len == 7 && strncmp(w->name, "0BRANCH", 7) == 0) {
/* Conditional branch */
cell_t offset = *ip++;
word_count++;
printf(" (offset=%ld)", (long) offset);
} else if (w->name_len == 6 && strncmp(w->name, "BRANCH", 6) == 0) {
/* Unconditional branch */
cell_t offset = *ip++;
word_count++;
printf(" (offset=%ld)", (long) offset);
}
printf("\n");
}
printf(";\n");
}
/* REBOOT ( addr len -- ) : set boot args and cold-reset. Interpret-only. */
static void system_word_reboot(VM *vm) {
cell_t len;
cell_t addr;
const char *str;
/* Interpret-only guard: -14 = "interpretation semantics only" */
if (vm->mode == MODE_COMPILE) {
vm->error = 1;
log_message(LOG_ERROR, "REBOOT: interpretation semantics only");
return;
}
if (vm->dsp < 1) { vm->error = 1; return; }
len = vm_pop(vm);
addr = vm_pop(vm);
if (len <= 0 || len >= KERNEL_ARGS_CMDLINE_MAX) {
vm->error = 1;
log_message(LOG_ERROR, "REBOOT: arg length out of range");
return;
}
if (!vm_addr_ok(vm, (vaddr_t)addr, (size_t)len)) {
vm->error = 1;
return;
}
str = (const char *)vm_ptr(vm, (vaddr_t)addr);
#ifdef __STARKERNEL__
{
EFI_RUNTIME_SERVICES *rt = g_sk_runtime_services;
EFI_GUID vendor_guid = STARFORTH_VENDOR_GUID;
EFI_GET_VARIABLE GetVariable;
EFI_SET_VARIABLE SetVariable;
EFI_RESET_SYSTEM ResetSystem;
uint32_t tries = 0;
UINTN tries_size = sizeof(tries);
EFI_STATUS sv;
if (!rt) {
console_puts("REBOOT: runtime services unavailable\n");
return;
}
GetVariable = (EFI_GET_VARIABLE)rt->GetVariable;
SetVariable = (EFI_SET_VARIABLE)rt->SetVariable;
ResetSystem = (EFI_RESET_SYSTEM)rt->ResetSystem;
/* Read escape-hatch counter */
GetVariable(
(CHAR16 *)SF_VAR_REBOOT_TRIES,
&vendor_guid, NULL,
&tries_size, &tries);
if (tries >= (uint32_t)REBOOT_MAX_TRIES) {
/* Escape hatch: clear both vars, warn, return to REPL */
SetVariable(
(CHAR16 *)SF_VAR_BOOT_ARGS, &vendor_guid,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
0, NULL);
SetVariable(
(CHAR16 *)SF_VAR_REBOOT_TRIES, &vendor_guid,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
0, NULL);
console_puts("REBOOT: max tries reached — dropping to REPL\n");
return;
}
/* Write new boot args */
sv = SetVariable(
(CHAR16 *)SF_VAR_BOOT_ARGS,
&vendor_guid,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
(UINTN)len, (void *)str);
if (sv != EFI_SUCCESS) {
console_puts("REBOOT: NVRAM write failed — dropping to REPL\n");
return;
}
/* Increment tries counter */
tries++;
SetVariable(
(CHAR16 *)SF_VAR_REBOOT_TRIES,
&vendor_guid,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(tries), &tries);
/* Cold reset — does not return */
ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
/* Unreachable, but keep the compiler happy */
console_puts("REBOOT: ResetSystem returned unexpectedly\n");
}
#else
/* Hosted VM: no reset capability */
fputs("[REBOOT] hosted stub — would reboot with: ", stdout);
fwrite(str, 1, (size_t)len, stdout);
fputc('\n', stdout);
vm->halted = 1;
#endif
}
/* EXECUTE ( xt -- ) FORTH-79: execute the word whose xt is on the stack */
static void system_word_execute(VM *vm) {
if (vm->dsp < 0) { vm->error = 1; return; }
cell_t xt = vm_pop(vm);
DictEntry *entry = (DictEntry *)(uintptr_t)xt;
if (!vm_dict_entry_ok(vm, entry) || !entry->func) { vm->error = 1; return; }
vm->current_executing_entry = entry;
entry->func(vm);
}
/* ───────────────────────────── API helpers ─────────────────────────── */
int system_is_running(void) { return system_running; }
void set_forth_79_compliance(int enabled) { forth_79_standard = enabled; }
/* ──────────────────── System Word Registration ─────────────────────── */
void register_system_words(VM *vm) {
/* Comments */
register_word(vm, "(", forth_paren);
vm_make_immediate(vm); /* '(' is IMMEDIATE */
register_word(vm, "\\", forth_backslash);
vm_make_immediate(vm); /* '\' is IMMEDIATE */
/* Environment/system */
register_word(vm, "COLD", system_word_cold);
register_word(vm, "WARM", system_word_warm);
register_word(vm, "BYE", system_word_bye);
register_word(vm, "REBOOT", system_word_reboot);
register_word(vm, "SAVE-SYSTEM", system_word_save_system);
register_word(vm, "WORDS", system_word_words);
register_word(vm, "VLIST", system_word_vlist);
register_word(vm, "SEE", system_word_see);
register_word(vm, "PAGE", system_word_page);
register_word(vm, "EXECUTE", system_word_execute);
register_word(vm, "NOP", system_word_nop);
register_word(vm, "79-STANDARD", system_word_79_standard);
/* Control transfer */
register_word(vm, "QUIT", system_word_quit);
vm_make_immediate(vm); /* forbid inside defs */
register_word(vm, "ABORT", system_word_abort);
/* Internal helper used by ABORT" compiled form */
register_word(vm, "(ABORT\")", runtime_abortq);
/* Immediate */
register_word(vm, "ABORT\"", system_word_abort_quote);
vm_make_immediate(vm); /* ABORT" is IMMEDIATE */
}
+168
View File
@@ -0,0 +1,168 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/* ttf_words.c — TrueType text entry point (FABRIC-0.md item 4.3.7e).
* Kernel-only; no-op on hosted builds. */
#include "include/ttf_words.h"
#include "../../include/log.h"
#include "../../include/word_registry.h"
#ifdef __STARKERNEL__
#include "starkernel/framebuffer.h"
#include "starkernel/ttf.h"
#include "starkernel/capsule_generated.h"
#define TTF_WORDS_CACHE_SLOTS 32
#define TTF_WORDS_DEFAULT_FONT "fonts:JetBrainsMono-Regular.ttf"
static ttf_font_t g_ttf_font;
static int g_ttf_font_ready = 0;
static ttf_raster_cache_slot_t g_ttf_cache_slots[TTF_WORDS_CACHE_SLOTS];
static ttf_raster_cache_t g_ttf_cache;
static int ttf_words_ensure_font(void) {
if (g_ttf_font_ready) return 1;
if (ttf_load_from_capsule(
capsule_get_directory(), capsule_get_descriptors(),
capsule_get_names(), capsule_get_arena(),
TTF_WORDS_DEFAULT_FONT, &g_ttf_font) != TTF_OK)
return 0;
ttf_raster_cache_init(&g_ttf_cache, g_ttf_cache_slots, TTF_WORDS_CACHE_SLOTS);
g_ttf_font_ready = 1;
return 1;
}
/* Decode one UTF-8 codepoint from VM memory at *a (VM-space address),
* with *len bytes known remaining. Mirrors capsules/fabric.4th's
* DECODE-UTF8 exactly: same lead-byte-length table, same U+FFFD
* fallback (advancing exactly one byte) on an invalid or truncated
* sequence. Advances *a and *len past the consumed bytes. */
static uint32_t ttf_words_decode_utf8(VM *vm, vaddr_t *a, cell_t *len) {
uint8_t lead, b[4];
uint32_t seqlen, i, cp;
if (*len <= 0 || !vm_addr_ok(vm, *a, 1)) {
*len = 0;
return 0xFFFD;
}
lead = vm_load_u8(vm, *a);
if (lead < 0x80) seqlen = 1;
else if ((lead & 0xE0) == 0xC0) seqlen = 2;
else if ((lead & 0xF0) == 0xE0) seqlen = 3;
else if ((lead & 0xF8) == 0xF0) seqlen = 4;
else seqlen = 0;
if (seqlen == 0 || (cell_t) seqlen > *len || !vm_addr_ok(vm, *a, seqlen)) {
*a += 1;
*len -= 1;
return 0xFFFD;
}
for (i = 0; i < seqlen; i++) b[i] = vm_load_u8(vm, *a + i);
switch (seqlen) {
case 1:
cp = b[0];
break;
case 2:
cp = ((uint32_t) (b[0] & 0x1F) << 6) | (b[1] & 0x3F);
break;
case 3:
cp = ((uint32_t) (b[0] & 0x0F) << 12) | ((uint32_t) (b[1] & 0x3F) << 6) | (b[2] & 0x3F);
break;
default:
cp = ((uint32_t) (b[0] & 0x07) << 18) | ((uint32_t) (b[1] & 0x3F) << 12)
| ((uint32_t) (b[2] & 0x3F) << 6) | (b[3] & 0x3F);
break;
}
*a += seqlen;
*len -= (cell_t) seqlen;
return cp;
}
/* TTF-TEXT ( c-addr u x y size color -- ) */
static void ttf_word_text(VM *vm) {
cell_t color, size, y, x, u, addr_cell;
vaddr_t a;
cell_t remaining;
uint32_t pen_x, baseline_y;
q48_16_t scale;
if (vm->dsp < 5) {
log_message(LOG_ERROR, "TTF-TEXT: Stack underflow");
vm->error = 1;
return;
}
color = vm_pop(vm);
size = vm_pop(vm);
y = vm_pop(vm);
x = vm_pop(vm);
u = vm_pop(vm);
addr_cell = vm_pop(vm);
if (!ttf_words_ensure_font()) {
log_message(LOG_ERROR, "TTF-TEXT: font capsule load failed");
vm->error = 1;
return;
}
if (size <= 0 || size > TTF_CACHE_MAX_SIZE_PX) {
log_message(LOG_ERROR, "TTF-TEXT: size out of range");
vm->error = 1;
return;
}
a = VM_ADDR(addr_cell);
remaining = u;
pen_x = (uint32_t) x;
/* Caller-facing (x,y) is Cartesian: origin at the bottom-left of the physical
* framebuffer, y increasing upward. fb_put_pixel() takes framebuffer coordinates
* (origin top-left, y increasing downward), so flip once here per-call rather
* than at every blit site. */
baseline_y = fb_height() - (uint32_t) y;
scale = q48_div(q48_from_u64((uint32_t) size), q48_from_u64(g_ttf_font.units_per_em));
while (remaining > 0) {
uint32_t cp = ttf_words_decode_utf8(vm, &a, &remaining);
ttf_bitmap_t bmp;
int rc, was_hit;
uint32_t gid, adv_units, px, py;
q48_16_t adv_q;
rc = ttf_raster_cache_get(&g_ttf_cache, &g_ttf_font, cp, (uint32_t) size, &bmp, &was_hit);
if (rc == TTF_OK) {
for (py = 0; py < bmp.height; py++) {
for (px = 0; px < bmp.width; px++) {
if (bmp.pixels[py * bmp.width + px]) {
fb_put_pixel(pen_x + px - TTF_CACHE_MARGIN,
baseline_y + py - (uint32_t) size - TTF_CACHE_MARGIN,
(uint32_t) color);
}
}
}
}
gid = ttf_codepoint_to_glyph(&g_ttf_font, cp);
adv_units = ttf_glyph_advance_width(&g_ttf_font, gid);
adv_q = q48_mul(q48_from_u64(adv_units), scale);
pen_x += (uint32_t) q48_to_u64(adv_q);
}
}
#endif /* __STARKERNEL__ */
void register_ttf_words(VM *vm)
{
#ifdef __STARKERNEL__
register_word(vm, "TTF-TEXT", ttf_word_text);
#else
(void) vm;
#endif
}
+582
View File
@@ -0,0 +1,582 @@
/*
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.
*/
#include "include/vocabulary_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
#include "../../include/vm.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef __STARKERNEL__
#include "starkernel/console.h"
#include "starkernel/hal/hal.h"
#include "starkernel/vm/arena.h"
/* Forward declaration for canonical check */
static inline int sf_is_canonical(uint64_t addr) {
int64_t saddr = (int64_t)addr;
return (saddr >> 47) == 0 || (saddr >> 47) == -1;
}
#endif
#if defined(__STARKERNEL__) && SK_PARITY_DEBUG
static void vocabulary_debug_print_hex(uint64_t value) __attribute__((unused));
static void vocabulary_debug_print_hex(uint64_t value)
{
char buf[19];
buf[0] = '0';
buf[1] = 'x';
buf[18] = '\0';
for (int i = 0; i < 16; ++i) {
uint8_t nibble = (uint8_t)((value >> ((15 - i) * 4)) & 0xF);
buf[i + 2] = (nibble < 10) ? (char)('0' + nibble) : (char)('a' + nibble - 10);
}
console_puts(buf);
}
#else
static void vocabulary_debug_print_hex(uint64_t value) __attribute__((unused));
static void vocabulary_debug_print_hex(uint64_t value) {
(void)value;
}
#endif
static size_t vocab_safe_len(const char *text, size_t max_len)
{
size_t len = 0;
if (!text) return 0;
while (len < max_len && text[len] != '\0') {
len++;
}
return len;
}
/* ---- first-character index for vocab chains (lazy rebuild) ----
* Bucket arrays themselves live per-VM (VM.vocab.ctx_fc / .forth_fc,
* declared in include/vm.h as VocabularyState); these two helpers just
* operate on whichever VM's arrays are passed in. */
static void fc_free(DictEntry ***lists, size_t *counts) {
for (size_t i = 0; i < VOCAB_FC_BUCKETS; ++i) {
free(lists[i]);
lists[i] = NULL;
counts[i] = 0;
}
}
static void fc_rebuild(DictEntry *head, DictEntry ***lists, size_t *counts, DictEntry **cached_head) {
fc_free(lists, counts);
/* first pass: counts */
for (DictEntry *e = head; e; e = e->link) {
unsigned c = (unsigned char) e->name[0];
counts[c]++;
}
/* alloc */
for (size_t i = 0; i < VOCAB_FC_BUCKETS; ++i) {
if (counts[i]) {
lists[i] = (DictEntry **) malloc(counts[i] * sizeof(DictEntry *));
if (!lists[i]) counts[i] = 0; /* malloc failed: zero count so fill skips */
}
}
/* second pass: fill oldest→newest (we’ll search newest-first by iterating backwards) */
size_t filled[VOCAB_FC_BUCKETS] = {0};
for (DictEntry *e = head; e; e = e->link) {
unsigned c = (unsigned char) e->name[0];
if (lists[c]) lists[c][filled[c]++] = e; /* skip null buckets */
}
*cached_head = head;
}
/* ===== FORTH-79 vocabulary model =====
- CONTEXT: vocabulary searched first
- then FORTH (system vocabulary) is searched
- CURRENT: where new words are entered (DEFINITIONS: CURRENT := CONTEXT)
- No ALSO/ONLY/PREVIOUS here. Period.
*/
/* Sync host state -> VM cells */
static inline void vocab_sync_vm_vars(VM *vm) {
if (!vm->vocab.context_var_addr || !vm->vocab.current_var_addr) return;
#if defined(__STARKERNEL__) && SK_PARITY_DEBUG
uint64_t cv = (uint64_t)(uintptr_t)vm->vocab.context_vocab;
uint64_t rv = (uint64_t)(uintptr_t)vm->vocab.current_vocab;
uint64_t base = (uintptr_t)vm->memory;
console_puts("[VOC_SYNC] context=");
vocabulary_debug_print_hex(cv);
console_puts(" current=");
vocabulary_debug_print_hex(rv);
console_puts(" context_addr=");
vocabulary_debug_print_hex((uint64_t)vm->vocab.context_var_addr);
console_puts(" current_addr=");
vocabulary_debug_print_hex((uint64_t)vm->vocab.current_var_addr);
console_puts(" vm_base=");
vocabulary_debug_print_hex(base);
console_puts(" context_vocab=");
vocabulary_debug_print_hex((uint64_t)(uintptr_t)vm->vocab.context_vocab);
console_println("");
/* Truncation check */
if (cv > 0xFFFFFFFF && (cv >> 32) == 0) {
console_puts("PANIC: context pointer truncated: ");
vocabulary_debug_print_hex(cv);
console_println("");
sk_hal_panic("context pointer truncated");
}
/* Canonical check */
if (!sf_is_canonical(cv)) {
console_puts("PANIC: context pointer non-canonical: ");
vocabulary_debug_print_hex(cv);
console_println("");
sk_hal_panic("context pointer non-canonical");
}
/* NOTE: DictEntry* (vocabulary pointers) live in the kernel heap, not the
* VM arena. An arena-range check here is always wrong — omitted. */
#endif
vm_store_cell(vm, vm->vocab.context_var_addr, (cell_t)(uintptr_t)vm->vocab.context_vocab);
vm_store_cell(vm, vm->vocab.current_var_addr, (cell_t)(uintptr_t)vm->vocab.current_vocab);
}
/**
* @brief Initialize the FORTH vocabulary system
* @details Sets up FORTH as root vocabulary and allocates VM cells for CONTEXT/CURRENT.
* Per-VM (vm->vocab.initialized): each VM in the fleet seeds its own
* vocabulary roots from its own dictionary, rather than every VM after
* the first silently sharing whichever VM ran this first.
* @param vm Pointer to VM instance
*/
static void init_vocabulary_system(VM *vm) {
if (vm->vocab.initialized) return;
/* Treat vm->latest as the FORTH vocabulary head */
vm->vocab.forth_vocab = vm->latest;
vm->vocab.context_vocab = vm->vocab.forth_vocab;
vm->vocab.current_vocab = vm->vocab.forth_vocab;
/* Allocate VM cells for CONTEXT and CURRENT */
void *p1 = vm_allot(vm, sizeof(cell_t));
if (!p1) {
vm->error = 1;
log_message(LOG_ERROR, "VOCAB: failed CONTEXT cell");
return;
}
vm->vocab.context_var_addr = (vaddr_t)((uint8_t *) p1 - vm->memory);
void *p2 = vm_allot(vm, sizeof(cell_t));
if (!p2) {
vm->error = 1;
log_message(LOG_ERROR, "VOCAB: failed CURRENT cell");
return;
}
vm->vocab.current_var_addr = (vaddr_t)((uint8_t *) p2 - vm->memory);
vocab_sync_vm_vars(vm);
vm->vocab.initialized = 1;
}
/* Finder: search CONTEXT chain first, then FORTH chain; skip hidden/smudged */
/* src/word_source/vocabulary_words.c */
static DictEntry *vocab_find_word(VM *vm, const char *name, size_t len) {
init_vocabulary_system(vm);
if (!name || len == 0) return NULL;
/* rebuild per-vocab indices if heads changed */
if (vm->vocab.ctx_cached_head != vm->vocab.context_vocab)
fc_rebuild(vm->vocab.context_vocab, vm->vocab.ctx_fc, vm->vocab.ctx_n, &vm->vocab.ctx_cached_head);
if (vm->vocab.forth_cached_head != vm->vocab.forth_vocab)
fc_rebuild(vm->vocab.forth_vocab, vm->vocab.forth_fc, vm->vocab.forth_n, &vm->vocab.forth_cached_head);
const unsigned char first = (unsigned char) name[0];
const unsigned char last = (unsigned char) name[len - 1];
/* search CONTEXT bucket (newest-first) */
{
DictEntry **bucket = vm->vocab.ctx_fc[first];
size_t n = vm->vocab.ctx_n[first];
if (UNLIKELY(!bucket || n == 0)) goto skip_ctx;
for (size_t i = n; i-- > 0;) {
DictEntry *e = bucket[i];
if (!e) continue;
if ((size_t) e->name_len != len) continue;
const char *en = e->name;
if ((unsigned char) en[len - 1] != last) continue;
#ifdef WORD_HIDDEN
if (e->flags & WORD_HIDDEN) continue;
#endif
#ifdef WORD_SMUDGED
if (e->flags & WORD_SMUDGED) continue;
#endif
if (memcmp(en, name, len) == 0) return e;
}
}
skip_ctx:;
/* then FORTH bucket (if different) */
if (vm->vocab.context_vocab != vm->vocab.forth_vocab) {
DictEntry **bucket = vm->vocab.forth_fc[first];
size_t n = vm->vocab.forth_n[first];
if (UNLIKELY(!bucket || n == 0)) goto skip_forth;
for (size_t i = n; i-- > 0;) {
DictEntry *e = bucket[i];
if (!e) continue;
if ((size_t) e->name_len != len) continue;
const char *en = e->name;
if ((unsigned char) en[len - 1] != last) continue;
#ifdef WORD_HIDDEN
if (e->flags & WORD_HIDDEN) continue;
#endif
#ifdef WORD_SMUDGED
if (e->flags & WORD_SMUDGED) continue;
#endif
if (memcmp(en, name, len) == 0) return e;
}
}
skip_forth:;
return NULL;
}
/* Executing a vocabulary word makes it the CONTEXT (FORTH-79) */
static void vocabulary_select_runtime(VM *vm) {
init_vocabulary_system(vm);
/* current_executing_entry is set by the interpreter before calling this func,
* so it correctly identifies which vocabulary word is executing (VA vs VB vs VC).
* Fall back to vm->latest only if somehow NULL. */
DictEntry *selected = vm->current_executing_entry;
if (!selected) selected = vm->latest;
vm->vocab.context_vocab = selected;
vocab_sync_vm_vars(vm);
log_message(LOG_DEBUG, "Vocabulary selected (CONTEXT updated)");
}
/* ===== Words ===== */
/* VOCABULARY ( -- ) Create a new vocabulary; executing it selects itself as CONTEXT. */
/**
* @brief VOCABULARY ( -- ) Create a new vocabulary
* @details Creates a vocabulary that when executed makes itself the CONTEXT
* @param vm Pointer to VM instance
*/
void vocabulary_word_vocabulary(VM *vm) {
char name[64];
if (!vm_parse_word(vm, name, sizeof name)) {
vm->error = 1;
log_message(LOG_ERROR, "VOCABULARY: missing name");
return;
}
/* Reject duplicate by scanning the ENTIRE dictionary from vm->latest. */
{
size_t nlen = strlen(name);
for (DictEntry *e = vm->latest; e; e = e->link) {
if (e->name_len == (int) nlen && memcmp(e->name, name, nlen) == 0) {
vm->error = 1;
log_message(LOG_ERROR, "VOCABULARY: duplicate '%s'", name);
return;
}
if (e->link == vm->latest) break; /* safety if dictionary ever becomes circular */
}
}
/* Create header that, when executed, selects this vocabulary as CONTEXT */
DictEntry *entry = vm_create_word(vm, name, (int) strlen(name), vocabulary_select_runtime);
if (!entry) {
vm->error = 1;
return;
}
/* Reveal immediately so it’s discoverable by FIND/INTERPRET */
#ifdef WORD_SMUDGED
entry->flags &= ~WORD_SMUDGED;
#endif
#ifdef WORD_HIDDEN
entry->flags &= ~WORD_HIDDEN;
#endif
/* Give the vocabulary a body cell (anchor); surfaces OOM deterministically */
void *body = vm_allot(vm, sizeof(cell_t));
if (!body) {
vm->error = 1;
log_message(LOG_ERROR, "VOCABULARY: out of dictionary space");
return;
}
vaddr_t body_addr = (vaddr_t)((uint8_t *) body - vm->memory);
vm_store_cell(vm, body_addr, 0);
vocab_sync_vm_vars(vm);
log_message(LOG_DEBUG, "VOCABULARY: created '%s'", name);
}
void vocabulary_create_vocabulary_direct(VM *vm, const char *name)
{
if (!vm || !name) {
return;
}
#if defined(__STARKERNEL__) && SK_PARITY_DEBUG
console_puts("[VOC_DEBUG] create_vocabulary_direct entry: '");
console_puts(name);
console_puts("' HERE=");
vocabulary_debug_print_hex((uint64_t)vm->here);
console_puts(" sizeof(cell)=");
vocabulary_debug_print_hex(sizeof(cell_t));
console_println("");
#endif
char local[64];
size_t len = vocab_safe_len(name, sizeof local - 1);
if (len == 0) {
vm->error = 1;
log_message(LOG_ERROR, "VOCABULARY: missing name");
return;
}
memcpy(local, name, len);
local[len] = '\0';
size_t nlen = len;
for (DictEntry *e = vm->latest; e; e = e->link) {
if (e->name_len == (int)nlen && memcmp(e->name, local, nlen) == 0) {
vm->error = 1;
log_message(LOG_ERROR, "VOCABULARY: duplicate '%s'", local);
return;
}
if (e->link == vm->latest) break;
}
#if defined(__STARKERNEL__) && SK_PARITY_DEBUG
console_println("[VOC_DEBUG] before vm_create_word");
#endif
DictEntry *entry = vm_create_word(vm, local, (int)nlen, vocabulary_select_runtime);
if (!entry) {
vm->error = 1;
return;
}
#if defined(__STARKERNEL__) && SK_PARITY_DEBUG
console_println("[VOC_DEBUG] after vm_create_word");
#endif
#ifdef WORD_SMUDGED
entry->flags &= ~WORD_SMUDGED;
#endif
#ifdef WORD_HIDDEN
entry->flags &= ~WORD_HIDDEN;
#endif
void *body = vm_allot(vm, sizeof(cell_t));
if (!body) {
vm->error = 1;
log_message(LOG_ERROR, "VOCABULARY: out of dictionary space");
return;
}
#if defined(__STARKERNEL__) && SK_PARITY_DEBUG
console_println("[VOC_DEBUG] vm_allot succeeded");
#endif
vaddr_t body_addr = (vaddr_t)((uint8_t *) body - vm->memory);
vm_store_cell(vm, body_addr, 0);
vocab_sync_vm_vars(vm);
#if defined(__STARKERNEL__) && SK_PARITY_DEBUG
console_puts("[VOC_DEBUG] create_vocabulary_direct exit: '");
console_puts(local);
console_println("'");
#endif
log_message(LOG_DEBUG, "VOCABULARY: created '%s'", local);
}
/* DEFINITIONS ( -- ) CURRENT := CONTEXT */
/**
* @brief DEFINITIONS ( -- ) Set CURRENT to CONTEXT
* @details Makes new words be defined in the CONTEXT vocabulary
* @param vm Pointer to VM instance
*/
void vocabulary_word_definitions(VM *vm) {
init_vocabulary_system(vm);
vm->vocab.current_vocab = vm->vocab.context_vocab;
vocab_sync_vm_vars(vm);
log_message(LOG_DEBUG, "DEFINITIONS: CURRENT := CONTEXT");
}
/* CONTEXT ( -- addr ) Return VM address of CONTEXT cell */
/**
* @brief CONTEXT ( -- addr ) Get CONTEXT variable address
* @details Returns VM address of cell containing CONTEXT vocabulary pointer
* @param vm Pointer to VM instance
*/
void vocabulary_word_context(VM *vm) {
init_vocabulary_system(vm);
vm_push(vm, CELL(vm->vocab.context_var_addr));
}
/* CURRENT ( -- addr ) Return VM address of CURRENT cell */
/**
* @brief CURRENT ( -- addr ) Get CURRENT variable address
* @details Returns VM address of cell containing CURRENT vocabulary pointer
* @param vm Pointer to VM instance
*/
void vocabulary_word_current(VM *vm) {
init_vocabulary_system(vm);
vm_push(vm, CELL(vm->vocab.current_var_addr));
}
/* FORTH ( -- ) Make FORTH the CONTEXT vocabulary (and nothing else) */
/**
* @brief FORTH ( -- ) Select FORTH vocabulary
* @details Makes FORTH the CONTEXT vocabulary
* @param vm Pointer to VM instance
*/
void vocabulary_word_forth(VM *vm) {
init_vocabulary_system(vm);
vm->vocab.context_vocab = vm->vocab.forth_vocab;
vocab_sync_vm_vars(vm);
log_message(LOG_DEBUG, "FORTH selected (CONTEXT := FORTH)");
}
/* (FIND) ( addr -- addr flag ) primitive finder on counted string at addr */
/**
* @brief (FIND) ( addr -- addr flag ) Find word in dictionary
* @details Searches for counted string at addr in CONTEXT then FORTH vocabularies
* @param vm Pointer to VM instance
*/
void vocabulary_word_paren_find(VM *vm) {
if (vm->dsp < 0) {
vm->error = 1;
return;
}
cell_t addr = vm->data_stack[vm->dsp]; /* keep addr */
vaddr_t a = VM_ADDR(addr);
if (!vm_addr_ok(vm, a, 1)) {
vm_push(vm, 0);
return;
}
uint8_t *s = vm_ptr(vm, a);
if (!s) {
vm_push(vm, 0);
return;
}
uint8_t n = s[0];
if (!vm_addr_ok(vm, a + 1, n)) {
vm_push(vm, 0);
return;
}
const char *name = (const char *) &s[1];
DictEntry *e = vocab_find_word(vm, name, (size_t) n);
if (e) {
vm->data_stack[vm->dsp] = (cell_t)(uintptr_t)
e;
vm_push(vm, (e->flags & WORD_IMMEDIATE) ? 1 : -1);
} else {
vm_push(vm, 0);
}
}
/* ORDER ( -- ) Display search order (CONTEXT then FORTH) and CURRENT */
/**
* @brief ORDER ( -- ) Display search order
* @details Shows CONTEXT and FORTH vocabularies plus CURRENT
* @param vm Pointer to VM instance
*/
void vocabulary_word_order(VM *vm) {
init_vocabulary_system(vm);
DictEntry *context_vocab = vm->vocab.context_vocab;
DictEntry *forth_vocab = vm->vocab.forth_vocab;
DictEntry *current_vocab = vm->vocab.current_vocab;
printf("Search order: ");
if (context_vocab && context_vocab->name_len > 0) {
fwrite(context_vocab->name, 1, (size_t) context_vocab->name_len, stdout);
if (context_vocab != forth_vocab) printf(" ");
}
if (context_vocab != forth_vocab && forth_vocab && forth_vocab->name_len > 0) {
fwrite(forth_vocab->name, 1, (size_t) forth_vocab->name_len, stdout);
}
if (!context_vocab && forth_vocab && forth_vocab->name_len > 0) {
fwrite(forth_vocab->name, 1, (size_t) forth_vocab->name_len, stdout);
}
printf("\n");
printf("Current: ");
if (current_vocab && current_vocab->name_len > 0) {
fwrite(current_vocab->name, 1, (size_t) current_vocab->name_len, stdout);
} else {
printf("(none)");
}
printf("\n");
}
/* Vocabulary-aware lookup for the interpreter */
/**
* @brief Find word in vocabulary-aware dictionary
* @details Searches CONTEXT then FORTH vocabularies
* @param vm Pointer to VM instance
* @param name Word name to find
* @param len Length of word name
* @return Pointer to dictionary entry if found, NULL if not found
*/
DictEntry *vm_vocabulary_find_word(VM *vm, const char *name, size_t len) {
return vocab_find_word(vm, name, len);
}
/**
* @brief Register all FORTH-79 vocabulary words
* @details Registers VOCABULARY, DEFINITIONS, CONTEXT, CURRENT, FORTH, ORDER, and (FIND)
* @param vm Pointer to VM instance
*/
void register_vocabulary_words(VM *vm) {
register_word(vm, "VOCABULARY", vocabulary_word_vocabulary);
register_word(vm, "DEFINITIONS", vocabulary_word_definitions);
register_word(vm, "CONTEXT", vocabulary_word_context);
register_word(vm, "CURRENT", vocabulary_word_current);
register_word(vm, "FORTH", vocabulary_word_forth);
register_word(vm, "ORDER", vocabulary_word_order);
register_word(vm, "(FIND)", vocabulary_word_paren_find);
init_vocabulary_system(vm);
}