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:
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
*** StarForth ***
|
||||
|
||||
physics_metadata.c - Phase 1 physics metadata helpers
|
||||
|
||||
Captures thermodynamic-style signals for dictionary entries without
|
||||
introducing heavy runtime cost. Future phases can build on these helpers
|
||||
to drive physics-aware scheduling and storage placement.
|
||||
*/
|
||||
|
||||
#include "../include/physics_metadata.h"
|
||||
#include "../include/ssm_jacquard.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* L8 FINAL INTEGRATION: L1 heat tracking is always-on (internal physics signal) */
|
||||
|
||||
/**
|
||||
* @brief Clamp a uint64_t value to the uint32_t range.
|
||||
*
|
||||
* @param value Input value to clamp
|
||||
* @return @c UINT32_MAX if @c value > UINT32_MAX, else @c (uint32_t)value
|
||||
*/
|
||||
static uint32_t clamp_u32(uint64_t value) {
|
||||
return (value > UINT32_MAX) ? UINT32_MAX : (uint32_t) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Derive a smoothed Q8 temperature from raw execution heat.
|
||||
*
|
||||
* Converts @c execution_heat to Q8 by left-shifting 8 bits, clamps to
|
||||
* UINT16_MAX, then applies a 1/4 EMA blend with @c prior_q8 to suppress
|
||||
* jitter: result = (3*prior + target) / 4. Returns 0 for non-positive heat.
|
||||
*
|
||||
* @param execution_heat Raw execution heat counter for the word
|
||||
* @param prior_q8 Previous smoothed temperature in Q8 format
|
||||
* @return Updated smoothed temperature in Q8 format
|
||||
*/
|
||||
static uint16_t temperature_from_execution_heat(cell_t execution_heat, uint16_t prior_q8) {
|
||||
if (execution_heat <= 0) return 0;
|
||||
uint64_t scaled = ((uint64_t) execution_heat) << 8; /* convert to Q8 */
|
||||
uint16_t target = (scaled > UINT16_MAX) ? UINT16_MAX : (uint16_t) scaled;
|
||||
/* Smooth with a simple EMA to avoid jitter */
|
||||
return (uint16_t)((3u * prior_q8 + target) / 4u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Derive the physics state flags bitmask from a DictEntry's word flags.
|
||||
*
|
||||
* Maps @c WORD_IMMEDIATE → @c PHYSICS_STATE_IMMEDIATE,
|
||||
* @c WORD_PINNED → @c PHYSICS_STATE_PINNED,
|
||||
* @c WORD_HIDDEN → @c PHYSICS_STATE_HIDDEN, and
|
||||
* @c WORD_COMPILED → @c PHYSICS_STATE_COMPILED.
|
||||
*
|
||||
* @param entry DictEntry to inspect (NULL returns 0)
|
||||
* @return Bitmask of @c PHYSICS_STATE_* flags
|
||||
*/
|
||||
static uint8_t derive_state_flags(const DictEntry *entry) {
|
||||
if (!entry) return 0;
|
||||
uint8_t flags = 0;
|
||||
if (entry->flags & WORD_IMMEDIATE) flags |= PHYSICS_STATE_IMMEDIATE;
|
||||
if (entry->flags & WORD_PINNED) flags |= PHYSICS_STATE_PINNED;
|
||||
if (entry->flags & WORD_HIDDEN) flags |= PHYSICS_STATE_HIDDEN;
|
||||
if (entry->flags & WORD_COMPILED) flags |= PHYSICS_STATE_COMPILED;
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialise physics metadata for a newly registered DictEntry.
|
||||
*
|
||||
* Zeroes all @c DictPhysics fields, sets @c mass_bytes to @c header_bytes
|
||||
* (the byte size of the entry header), and derives initial @c state_flags
|
||||
* from the entry's word flags. Called from @c register_word() immediately
|
||||
* after the entry is linked into the dictionary.
|
||||
*
|
||||
* @param entry DictEntry to initialise
|
||||
* @param header_bytes Byte size of the entry's header region (for mass)
|
||||
*/
|
||||
void physics_metadata_init(DictEntry *entry, uint32_t header_bytes) {
|
||||
if (!entry) return;
|
||||
entry->physics.temperature_q8 = 0;
|
||||
entry->physics.last_active_ns = 0;
|
||||
entry->physics.last_decay_ns = 0;
|
||||
entry->physics.last_decay_tick = 0;
|
||||
entry->physics.mass_bytes = header_bytes;
|
||||
entry->physics.avg_latency_ns = 0;
|
||||
entry->physics.state_flags = derive_state_flags(entry);
|
||||
entry->physics.acl_hint = 0;
|
||||
entry->physics.pubsub_mask = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the mass (byte footprint) of a DictEntry's physics metadata.
|
||||
*
|
||||
* Called after a colon definition or CREATE body is fully compiled so the
|
||||
* mass reflects the total compiled size, not just the header.
|
||||
*
|
||||
* @param entry DictEntry to update
|
||||
* @param total_bytes Total compiled byte size (header + body)
|
||||
*/
|
||||
void physics_metadata_set_mass(DictEntry *entry, uint32_t total_bytes) {
|
||||
if (!entry) return;
|
||||
entry->physics.mass_bytes = total_bytes;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* INTENT: Update word's physics metadata after execution
|
||||
* FL1: Heat accumulation feedback loop - converts raw execution_heat counter
|
||||
* to smoothed temperature_q8 metric and updates activity timestamps
|
||||
* WHY: Atomic heat increment (FL1) happens in execution loop; this function
|
||||
* derives secondary metrics (temperature, timestamps) for observability
|
||||
* ============================================================================ */
|
||||
/**
|
||||
* @brief Update physics metadata after a word execution completes.
|
||||
*
|
||||
* Derives and stores a new smoothed @c temperature_q8 from @c execution_heat,
|
||||
* and updates both @c last_active_ns and @c last_decay_ns to @c now_ns.
|
||||
* Called by @c physics_post_execute() on the post-execution path.
|
||||
*
|
||||
* @param entry DictEntry that was just executed
|
||||
* @param execution_heat Current raw execution heat counter
|
||||
* @param now_ns Current monotonic timestamp in nanoseconds
|
||||
*/
|
||||
void physics_metadata_touch(DictEntry *entry, cell_t execution_heat, uint64_t now_ns) {
|
||||
if (!entry) return;
|
||||
entry->physics.temperature_q8 = temperature_from_execution_heat(execution_heat, entry->physics.temperature_q8);
|
||||
entry->physics.last_active_ns = now_ns;
|
||||
entry->physics.last_decay_ns = now_ns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Re-derive and update @c state_flags for a DictEntry.
|
||||
*
|
||||
* Called after word flags change (e.g. IMMEDIATE is set during compilation)
|
||||
* to keep the physics state flags in sync with the word flags.
|
||||
*
|
||||
* @param entry DictEntry whose state flags are to be refreshed
|
||||
*/
|
||||
void physics_metadata_refresh_state(DictEntry *entry) {
|
||||
if (!entry) return;
|
||||
entry->physics.state_flags = derive_state_flags(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Record an execution latency sample into the per-word EMA.
|
||||
*
|
||||
* Clamps @c sample_ns to uint32_t, then blends it into @c avg_latency_ns
|
||||
* with a 1/4 EMA: new = (3*prior + sample) / 4. Seeds @c avg_latency_ns
|
||||
* directly from the first sample.
|
||||
*
|
||||
* @param entry DictEntry whose latency EMA is updated
|
||||
* @param sample_ns Measured execution duration in nanoseconds
|
||||
*/
|
||||
void physics_metadata_record_latency(DictEntry *entry, uint64_t sample_ns) {
|
||||
if (!entry) return;
|
||||
uint32_t sample = clamp_u32(sample_ns);
|
||||
uint32_t prior = entry->physics.avg_latency_ns;
|
||||
if (prior == 0) {
|
||||
entry->physics.avg_latency_ns = sample;
|
||||
} else {
|
||||
entry->physics.avg_latency_ns = (uint32_t)((3ull * prior + sample) / 4ull);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct physics_seed_definition {
|
||||
const char *name;
|
||||
uint16_t temperature_q8;
|
||||
uint32_t avg_latency_ns;
|
||||
uint8_t acl_hint;
|
||||
uint8_t pubsub_mask;
|
||||
} physics_seed_definition_t;
|
||||
|
||||
static const physics_seed_definition_t physics_seed_table[] = {
|
||||
{"IF", 0x0100u, 400u, 0, 0},
|
||||
{"ELSE", 0x0100u, 400u, 0, 0},
|
||||
{"THEN", 0x0100u, 350u, 0, 0},
|
||||
{"BEGIN", 0x0120u, 450u, 0, 0},
|
||||
{"WHILE", 0x0120u, 450u, 0, 0},
|
||||
{"REPEAT", 0x0120u, 450u, 0, 0},
|
||||
{"DO", 0x0180u, 500u, 0, 0},
|
||||
{"LOOP", 0x0180u, 500u, 0, 0},
|
||||
{"+LOOP", 0x0200u, 520u, 0, 0},
|
||||
{"LEAVE", 0x0200u, 520u, 0, 0},
|
||||
{".", 0x0080u, 300u, 0, 0},
|
||||
{"EMIT", 0x0180u, 800u, 0, 0x02u},
|
||||
{"TYPE", 0x0180u, 900u, 0, 0x02u},
|
||||
{"BLOCK", 0x0200u, 950u, 0, 0x04u},
|
||||
{"BUFFER", 0x01C0u, 900u, 0, 0x04u},
|
||||
{"SAVE-BUFFERS", 0x0260u, 1400u, 0, 0x04u},
|
||||
{"FLUSH", 0x0240u, 1200u, 0, 0x04u},
|
||||
{"LOAD", 0x0220u, 1100u, 0, 0x04u},
|
||||
{"LIST", 0x0200u, 1000u, 0, 0x04u},
|
||||
{"SAVE-SYSTEM", 0x0280u, 2000u, 0x80u, 0x08u},
|
||||
{NULL, 0, 0, 0, 0}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Apply DoE-derived prior physics values to a named DictEntry.
|
||||
*
|
||||
* Searches the static @c physics_seed_table for an entry matching
|
||||
* @c entry->name. If found, seeds @c temperature_q8, @c avg_latency_ns,
|
||||
* @c acl_hint, and @c pubsub_mask with values derived from the 2^7
|
||||
* factorial DoE analysis. Only non-zero seed fields overwrite the current
|
||||
* value. Called from @c register_word() for built-in primitives.
|
||||
*
|
||||
* @param entry DictEntry to seed (no-op if NULL or no matching table entry)
|
||||
*/
|
||||
void physics_metadata_apply_seed(DictEntry *entry) {
|
||||
if (!entry || !entry->name_len) return;
|
||||
for (const physics_seed_definition_t *seed = physics_seed_table; seed->name; ++seed) {
|
||||
if ((int) strlen(seed->name) != entry->name_len) continue;
|
||||
if (strncmp(seed->name, entry->name, entry->name_len) != 0) continue;
|
||||
if (seed->temperature_q8) entry->physics.temperature_q8 = seed->temperature_q8;
|
||||
if (seed->avg_latency_ns) entry->physics.avg_latency_ns = seed->avg_latency_ns;
|
||||
if (seed->acl_hint) entry->physics.acl_hint = seed->acl_hint;
|
||||
if (seed->pubsub_mask) entry->physics.pubsub_mask = seed->pubsub_mask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================================================================
|
||||
* Phase 2: Linear Decay Mechanism
|
||||
* ============================================================================
|
||||
*
|
||||
* Reduces execution_heat over time to model:
|
||||
* - OS context switches (old task's heat becomes stale)
|
||||
* - Temporal locality (recent access patterns > distant past)
|
||||
* - Dissipation (physical analogy: thermal equilibrium)
|
||||
*
|
||||
* Mathematical Model (Discrete Linear):
|
||||
* H(t) = max(0, H_0 - d*t)
|
||||
*
|
||||
* Where:
|
||||
* H(t) = heat at time t
|
||||
* H_0 = initial heat
|
||||
* d = decay rate (heat units per heartbeat tick)
|
||||
* t = heartbeat ticks elapsed since last execution -- never wall-clock
|
||||
* time; see physics_metadata_apply_linear_decay()'s doc comment
|
||||
*
|
||||
* Frozen words (WORD_FROZEN flag) are exempt from decay.
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Apply Loop #3 linear heat decay to a DictEntry.
|
||||
*
|
||||
* Implements the thermodynamic dissipation model: H(t) = max(0, H₀ - d*t),
|
||||
* where t is measured in heartbeat ticks (@c vm->heartbeat.tick_count),
|
||||
* never in wall-clock time. The decay amount is computed as
|
||||
* (elapsed_ticks * slope_q48) >> 16 using the Q48.16 adaptive decay slope
|
||||
* stored in the VM's heartbeat state. Uses a GCC CAS loop for atomic heat
|
||||
* update when @c __GNUC__ is defined, falling back to a plain subtraction
|
||||
* otherwise.
|
||||
*
|
||||
* Ticks, not nanoseconds: this entry's @c execution_heat feeds directly
|
||||
* into @c dict_hash (see @c capsule_dict_hash_hook()), so any wall-clock
|
||||
* dependency here makes dictionary state -- and therefore dict_hash --
|
||||
* architecture-variable under QEMU/TCG's differing real emulation speed,
|
||||
* or whenever real elapsed time between two touches of the same entry
|
||||
* varies for reasons unrelated to execution (e.g. a blocking I/O wait
|
||||
* between them). @c vm->heartbeat.tick_count only advances from
|
||||
* @c vm_tick(), called synchronously from the interpreter's own dispatch
|
||||
* path -- a purely execution-count-driven clock, immune to how long any
|
||||
* intervening real-time work (including blocking I/O) actually took.
|
||||
*
|
||||
* No-op when:
|
||||
* - @c WORD_FROZEN flag is set on the entry
|
||||
* - @c elapsed_ticks == 0 (no heartbeat tick has fired since last decay)
|
||||
* - @c vm->ssm_config has L3 disabled by the Jacquard mode selector
|
||||
*
|
||||
* @param entry DictEntry whose heat is decayed
|
||||
* @param elapsed_ticks Heartbeat ticks elapsed since last decay application
|
||||
* @param vm VM providing the adaptive decay slope and L3 gate flag
|
||||
*/
|
||||
void physics_metadata_apply_linear_decay(DictEntry *entry, uint64_t elapsed_ticks, VM *vm) {
|
||||
if (!entry || !vm) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Frozen words do not decay */
|
||||
if (entry->flags & WORD_FROZEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Don't bother when no tick has fired since the last application */
|
||||
if (elapsed_ticks == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* L8 GATE: Check if L3 (linear decay) is enabled by Jacquard mode selector */
|
||||
if (vm->ssm_config) {
|
||||
ssm_config_t *config = (ssm_config_t*)vm->ssm_config;
|
||||
if (!config->L3_linear_decay) {
|
||||
return; /* L3 disabled by L8 mode selector */
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* INTENT: Calculate decay amount using adaptive Q48.16 fixed-point math
|
||||
* FL1: Heat decay - counterbalances FL1 accumulation to prevent runaway
|
||||
* FL3: Adaptive slope tuning - inference engine adjusts decay_slope_q48
|
||||
* based on statistical analysis of heat trajectory effectiveness
|
||||
* WHY: decay_slope_q48 is expressed as heat units per tick, not per
|
||||
* microsecond -- the inference engine (Loop #6) adaptively
|
||||
* calibrates it against whatever units it's actually applied in,
|
||||
* so no conversion constant is needed here, just a consistent unit.
|
||||
* ======================================================================== */
|
||||
uint64_t slope_q48 = physics_decay_slope_load(vm);
|
||||
uint64_t decay_amount_raw = (elapsed_ticks * slope_q48) >> 16;
|
||||
|
||||
/* Clamp to cell_t range (avoid overflow) */
|
||||
cell_t decay_amount = (decay_amount_raw > (uint64_t)0x7FFFFFFFFFFFFFFF)
|
||||
? 0x7FFFFFFFFFFFFFFF
|
||||
: (cell_t)decay_amount_raw;
|
||||
|
||||
if (decay_amount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(__GNUC__)
|
||||
cell_t old_heat;
|
||||
cell_t new_heat;
|
||||
do {
|
||||
old_heat = __atomic_load_n(&entry->execution_heat, __ATOMIC_RELAXED);
|
||||
if (old_heat == 0) {
|
||||
return;
|
||||
}
|
||||
new_heat = (decay_amount >= old_heat) ? 0 : (old_heat - decay_amount);
|
||||
} while (!__atomic_compare_exchange_n(&entry->execution_heat,
|
||||
&old_heat,
|
||||
new_heat,
|
||||
0,
|
||||
__ATOMIC_RELAXED,
|
||||
__ATOMIC_RELAXED));
|
||||
#else
|
||||
if (decay_amount >= entry->execution_heat) {
|
||||
entry->execution_heat = 0;
|
||||
} else {
|
||||
entry->execution_heat -= decay_amount;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user