Files
LithosAnanake/src/physics_metadata.c
T

388 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 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) 20232025 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
}