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
@@ -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);
}