/* StarForth — Steady-State Virtual Machine Runtime Copyright (c) 2023–2025 Robert A. James All rights reserved. This file is part of the StarForth project. Licensed under the StarForth License, Version 1.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://github.com/star.4th@proton.me/StarForth/LICENSE.txt This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. See the License for the specific language governing permissions and limitations under the License. StarForth — Steady-State Virtual Machine Runtime Copyright (c) 2023–2025 Robert A. James All rights reserved. This file is part of the StarForth project. Licensed under the StarForth License, Version 1.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://github.com/star.4th@proton.me/StarForth/LICENSE.txt This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. See the License for the specific language governing permissions and limitations under the License. */ #ifndef SSM_JACQUARD_H #define SSM_JACQUARD_H #include #include "compudynamics.h" #include "starforth_config.h" /* ============================================================================ * SSM L8: Jacquard Mode Selector (Data-Driven Architecture) * ============================================================================ * * Based on 2^7 DoE with 300 reps (128 configurations, 38,400 total runs). * Top 5% analysis reveals optimal loop combinations for speed + stability. * * Architecture (Experimentally Validated): * - L1 (heat_tracking): DISABLED (harmful in 86% of top configs) * - L4 (pipelining_metrics): DISABLED (harmful in 100% of top configs) * - L7 (adaptive_heartrate): ALWAYS ON (beneficial in 71% of top configs) * - L2, L3, L5, L6: Runtime-controlled by L8 (workload-dependent) * - L8 (Jacquard): 4-bit selector (16 modes) controlling L2/L3/L5/L6 * * L8 operates as a multi-dimensional classifier: * L2 (window): ON if entropy > 0.75 (diversity tracking) * L3 (decay): ON if temporal_decay > 0.5 (temporal locality) * L5 (window_inf): ON if cv > 0.15 (variance adaptation) * L6 (decay_inf): ON if cv > 0.15 AND temporal_decay > 0.3 * * Top 5% Validated Modes: * C4 (0100): L2=0, L3=1, L5=0, L6=0 - Temporal locality * C7 (0111): L2=0, L3=1, L5=1, L6=1 - Full inference * C9 (1001): L2=1, L3=0, L5=0, L6=1 - Diverse + decay_inf * C11 (1011): L2=1, L3=0, L5=1, L6=1 - Diverse + inference * C12 (1100): L2=1, L3=1, L5=0, L6=0 - Diverse + temporal */ /* ============================================================================ * L8 Mode Definitions (4-bit: L2/L3/L5/L6) * ============================================================================ */ typedef enum { /* Bits: L2 L3 L5 L6 */ SSM_MODE_C0 = 0x0, /* 0000: Minimal (stable/predictable workloads) */ SSM_MODE_C1 = 0x1, /* 0001: L6 only (decay inference) */ SSM_MODE_C2 = 0x2, /* 0010: L5 only (window inference) */ SSM_MODE_C3 = 0x3, /* 0011: L5+L6 (volatile workloads) */ SSM_MODE_C4 = 0x4, /* 0100: L3 only (temporal locality) ✅ TOP 5% */ SSM_MODE_C5 = 0x5, /* 0101: L3+L6 (temporal + decay_inf) */ SSM_MODE_C6 = 0x6, /* 0110: L3+L5 (temporal + window_inf) */ SSM_MODE_C7 = 0x7, /* 0111: L3+L5+L6 (full inference) ✅ TOP 5% */ SSM_MODE_C8 = 0x8, /* 1000: L2 only (high diversity) */ SSM_MODE_C9 = 0x9, /* 1001: L2+L6 (diverse + decay_inf) ✅ TOP 5% */ SSM_MODE_C10 = 0xA, /* 1010: L2+L5 (diverse + window_inf) */ SSM_MODE_C11 = 0xB, /* 1011: L2+L5+L6 (diverse + inference) ✅ TOP 5% */ SSM_MODE_C12 = 0xC, /* 1100: L2+L3 (diverse + temporal) ✅ TOP 5% */ SSM_MODE_C13 = 0xD, /* 1101: L2+L3+L6 (complex workload) */ SSM_MODE_C14 = 0xE, /* 1110: L2+L3+L5 (full adaptive, no decay_inf) */ SSM_MODE_C15 = 0xF /* 1111: L2+L3+L5+L6 (full adaptive, all on) */ } ssm_l8_mode_t; /* ============================================================================ * L8 Metrics (Input to Mode Selection) * ============================================================================ */ typedef struct { double entropy; /* Rolling-window entropy/diversity (0.0-1.0) */ double cv; /* Coefficient of variation (short-term volatility) */ double temporal_decay; /* Temporal locality strength (0.0-1.0) */ double stability_score; /* Combined stability metric for hysteresis */ int inference_ran_this_tick; /* 1 if inference engine ran this tick */ int inference_early_exited; /* 1 if inference early-exited (ANOVA stable) */ } ssm_l8_metrics_t; /* ============================================================================ * SSM Configuration (L1-L7 Mode Bits) * ============================================================================ * * L1/L4/L7 were runtime-inert until this field expansion: the adaptive * table (SsmConfigTable, below) always selected across the full 128-config * (7-bit) space, but ssm_apply_mode_from_table() and its per-tick scoring * counterpart only ever wrote 4 of those 7 bits out here, so L1/L4/L7 were computed by * the bandit's UCB selection and then silently discarded before reaching * any real gate -- those three loops were governed purely by compile-time * macros (ENABLE_HOTWORDS_CACHE, ENABLE_PIPELINING, HEARTBEAT_THREAD_ENABLED) * for the whole process lifetime. Adding these fields, and wiring them at * their four call sites alongside (not instead of) the compile-time macros, * makes the bandit's full selection actually take effect. */ typedef struct { int L1_heat_tracking; /* 1 = hotwords cache active, 0 = off */ int L2_rolling_window; /* 1 = window tracking active, 0 = off */ int L3_linear_decay; /* 1 = decay active, 0 = off */ int L4_pipelining; /* 1 = pipelining metrics active, 0 = off */ int L5_window_inference; /* 1 = window inference active, 0 = off */ int L6_decay_inference; /* 1 = decay inference active, 0 = off */ int L7_adaptive_heartrate; /* 1 = heartbeat thread active, 0 = off */ } ssm_config_t; /* ============================================================================ * L8 Configuration Thresholds (Data-Driven from DoE) * ============================================================================ * Defaults live in starforth_config.h (included above), the single source * of truth for VM build-time defaults; overridable via -D exactly as before. */ /* ============================================================================ * Adaptive Table Constants (L8 Heat-Ranked 128-Config Selector) * One clock only: the heartbeat tick (rev r). Scoring and UCB reselection * both run every tick -- no derived multi-tick batching unit exists * anywhere in this mechanism. The score/UCB/reward/weight constants live * in cd_tuning_word() (compudynamics.h/.c) now, not here -- this is the * word-level instantiation of the generic compudynamics module (rev s). * ============================================================================ */ /* ============================================================================ * 7-bit Config Bit Positions (b6=L1, b5=L2, b4=L3, b3=L4, b2=L5, b1=L6, b0=L7) * ============================================================================ */ #define SSM_CFG_L1 0x40u #define SSM_CFG_L2 0x20u #define SSM_CFG_L3 0x10u #define SSM_CFG_L4 0x08u #define SSM_CFG_L5 0x04u #define SSM_CFG_L6 0x02u #define SSM_CFG_L7 0x01u /* ============================================================================ * L8 State (extended with adaptive table pointer) * ============================================================================ * * The adaptive table itself (CDConfigEntry/CDConfigTable) is the generic * compudynamics bandit (compudynamics.h), sized to 128 configs x 8 regimes * via cd_tuning_word() -- see ssm_l8_init_table(). */ typedef struct { ssm_l8_mode_t current_mode; /* Mirrored from table for diagnostics */ uint32_t hysteresis_counter; /* Legacy threshold mode hysteresis */ ssm_l8_mode_t pending_mode; /* Legacy threshold mode pending */ CDConfigTable *table; /* NULL = legacy threshold; non-NULL = adaptive */ } ssm_l8_state_t; /* ============================================================================ * L8 API * ============================================================================ */ /** * @brief Initialize L8 state (legacy fields only; call ssm_l8_init_table separately) */ void ssm_l8_init(ssm_l8_state_t *state, ssm_l8_mode_t initial_mode); /** * @brief Allocate and seed the adaptive config table * Sets state->table; must be called after ssm_l8_init(). * On allocation failure, state->table remains NULL (legacy mode is used). */ void ssm_l8_init_table(ssm_l8_state_t *state); /** * @brief Free the adaptive config table */ void ssm_l8_free_table(ssm_l8_state_t *state); /* * @brief Per-tick update for the adaptive table path * * Classifies the current regime, scores the current config from this * tick's outcome, and UCB-reselects -- every call, no batching -- and * applies the selected config to ssm_config_t. * * The locality component of the regime classification (and of the * joint-convergence reward signal) is computed here from recent_word_ids * via cd_classify_ids() -- purely execution-count-derived, no wall-clock * input. This replaced a decay-slope-derived temporal_decay signal (rev * r) that made L8's regime classification wall-clock-tainted once it ran * every tick instead of once per ~2000 ticks, breaking cross-architecture * dict_hash determinism under QEMU/TCG's differing real emulation speed * per ISA (rev s). * * @param state L8 state (must have state->table != NULL) * @param metrics Current runtime metrics (entropy, cv, inference status) * @param config SSM config updated this tick * @param current_window VM's effective_window_size this tick * @param recent_word_ids Recent word-execution-ID history, oldest first * (e.g. from rolling_window_get_recent_sequence()) * @param recent_word_ids_count Number of valid entries in recent_word_ids */ void ssm_l8_update_table(ssm_l8_state_t *state, const ssm_l8_metrics_t *metrics, ssm_config_t *config, uint32_t current_window, const uint32_t *recent_word_ids, uint32_t recent_word_ids_count); /** * @brief Apply the current table config to ssm_config_t * Used on first tick to set the initial DoE-seeded config immediately. */ void ssm_apply_mode_from_table(const ssm_l8_state_t *state, ssm_config_t *config); /** * @brief Force the adaptive table onto an externally-chosen config * (e.g. a DoE campaign driving a specific tick's config rather than * letting the bandit's own UCB selection choose it). See definition for * details on why this stays coherent with the bandit's own reward loop. */ void ssm_l8_force_config(ssm_l8_state_t *state, ssm_config_t *config, uint8_t config_idx); /** * @brief Update L8 state based on current metrics (legacy threshold path) */ void ssm_l8_update(const ssm_l8_metrics_t *metrics, ssm_l8_state_t *state); /** * @brief Apply L8 mode to SSM configuration (legacy threshold path) */ void ssm_apply_mode(const ssm_l8_state_t *state, ssm_config_t *config); /** * @brief Get human-readable mode name */ const char* ssm_l8_mode_name(ssm_l8_mode_t mode); #endif /* SSM_JACQUARD_H */