224 lines
8.8 KiB
C
224 lines
8.8 KiB
C
/*
|
||
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_execution_hooks.c - All physics machinery in one place
|
||
*
|
||
* This file absorbs ALL the physics-related code that was scattered
|
||
* throughout execute_colon_word and vm_interpret_word.
|
||
*
|
||
* The VM execution loop is now clean; this file is where physics lives.
|
||
*/
|
||
|
||
#include "../include/physics_execution_hooks.h"
|
||
#include "../include/platform_time.h"
|
||
#include "../include/profiler.h"
|
||
#include "../include/physics_metadata.h"
|
||
#include "../include/physics_hotwords_cache.h"
|
||
#include "../include/physics_pipelining_metrics.h"
|
||
#include "../include/rolling_window_of_truth.h"
|
||
#include "../include/ssm_jacquard.h"
|
||
|
||
/**
|
||
* @brief Pre-execution physics hook — called before every word dispatch.
|
||
*
|
||
* Consolidates all physics work that must happen before a word runs:
|
||
* - Loop #3: applies linear decay based on elapsed time since last execution
|
||
* - Loop #1: increments @c execution_heat for the word
|
||
* - Loop #2: records the word-id into the rolling window of truth
|
||
* - Loop #4: detects a pipelining prefetch hit (if @c prev's speculation
|
||
* matched @c word), records the transition from @c prev to @c word,
|
||
* updates the probability cache, and issues a speculative hotwords
|
||
* cache promotion for the most likely next word
|
||
*
|
||
* @param vm Pointer to the VM structure
|
||
* @param word DictEntry being executed
|
||
* @param prev DictEntry executed immediately before @c word (may be NULL)
|
||
*/
|
||
void physics_pre_execute(VM* vm, DictEntry* word, DictEntry* prev)
|
||
{
|
||
if (!vm || !word) return;
|
||
|
||
uint64_t now_ns = sf_monotonic_ns();
|
||
|
||
/* Loop #3: Apply linear decay before accumulating new heat.
|
||
* Tick-based, not wall-clock -- see physics_metadata_apply_linear_decay(). */
|
||
uint64_t elapsed_ticks = vm->heartbeat.tick_count - word->physics.last_decay_tick;
|
||
physics_metadata_apply_linear_decay(word, elapsed_ticks, vm);
|
||
word->physics.last_decay_tick = vm->heartbeat.tick_count;
|
||
word->physics.last_active_ns = now_ns;
|
||
word->physics.last_decay_ns = now_ns;
|
||
|
||
/* Loop #1: Heat accumulation */
|
||
physics_execution_heat_increment(word);
|
||
|
||
uint32_t word_id = word->word_id;
|
||
if (word_id >= DICTIONARY_SIZE) return;
|
||
|
||
/* Loop #2: Rolling Window of Truth */
|
||
rolling_window_record_execution(&vm->rolling_window, word_id);
|
||
|
||
/* Loop #4: Pipelining Transition Metrics. ENABLE_PIPELINING is the
|
||
* compile-time ceiling; ssm_config's L4_pipelining is L8's live
|
||
* runtime toggle within that ceiling (see ssm_jacquard.h). */
|
||
{
|
||
ssm_config_t *ssm_cfg = (ssm_config_t*)vm->ssm_config;
|
||
if (!prev || !prev->transition_metrics || !ENABLE_PIPELINING ||
|
||
!ssm_cfg || !ssm_cfg->L4_pipelining) return;
|
||
}
|
||
|
||
/* Check if current word was speculatively promoted by previous word */
|
||
uint32_t prev_speculation_target = prev->transition_metrics->most_likely_next_word_id;
|
||
if (prev_speculation_target == word_id && prev->transition_metrics->prefetch_attempts > 0)
|
||
{
|
||
/* PREFETCH HIT: Current word matches previous word's speculation! */
|
||
transition_metrics_record_prefetch_hit(prev->transition_metrics, 0);
|
||
vm->pipeline_metrics.prefetch_hits++;
|
||
}
|
||
|
||
/* Record transition from previous word to current word */
|
||
transition_metrics_record(prev->transition_metrics, word_id, DICTIONARY_SIZE);
|
||
|
||
/* Update probability cache to find most likely next word */
|
||
transition_metrics_update_cache(prev->transition_metrics, DICTIONARY_SIZE);
|
||
|
||
uint32_t speculated_word_id = prev->transition_metrics->most_likely_next_word_id;
|
||
|
||
/* Speculative prefetch of most likely next word */
|
||
if (speculated_word_id < DICTIONARY_SIZE &&
|
||
transition_metrics_should_speculate(prev->transition_metrics, speculated_word_id))
|
||
{
|
||
DictEntry* spec_entry = vm_dictionary_lookup_by_word_id(vm, speculated_word_id);
|
||
|
||
/* ENABLE_HOTWORDS_CACHE is the compile-time ceiling; cache->enabled
|
||
* is L8's live runtime toggle within that ceiling, kept in sync
|
||
* with ssm_config's L1_heat_tracking wherever L8 applies a config
|
||
* (see vm_time.c / vm_runtime.c). */
|
||
if (spec_entry && vm->hotwords_cache && ENABLE_HOTWORDS_CACHE &&
|
||
vm->hotwords_cache->enabled)
|
||
{
|
||
spec_entry->execution_heat = HOTWORDS_EXECUTION_HEAT_THRESHOLD + 1;
|
||
hotwords_cache_promote(vm->hotwords_cache, spec_entry);
|
||
|
||
prev->transition_metrics->prefetch_attempts++;
|
||
vm->pipeline_metrics.prefetch_attempts++;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Post-execution physics hook — called after every word dispatch.
|
||
*
|
||
* Consolidates all physics work that must happen after a word completes:
|
||
* - Updates physics metadata (@c temperature_q8, @c last_active_ns) via
|
||
* @c physics_metadata_touch()
|
||
* - Notifies the profiler (@c profiler_word_exit())
|
||
* - Increments @c vm->heartbeat.words_executed
|
||
* - When running without a background heartbeat thread, checks whether
|
||
* enough words have been executed to trigger a heartbeat cycle
|
||
* (@c vm_heartbeat_run_cycle())
|
||
*
|
||
* @param vm Pointer to the VM structure
|
||
* @param word DictEntry that just finished executing
|
||
*/
|
||
void physics_post_execute(VM* vm, DictEntry* word)
|
||
{
|
||
if (!vm || !word) return;
|
||
|
||
physics_metadata_touch(word, word->execution_heat, sf_monotonic_ns());
|
||
profiler_word_exit(word);
|
||
vm->heartbeat.words_executed++;
|
||
|
||
/* Heartbeat: Periodic time-driven tuning (Loop #3 & #5) */
|
||
if (!vm->heartbeat.worker && ++vm->heartbeat.check_counter >= HEARTBEAT_CHECK_FREQUENCY)
|
||
{
|
||
extern void vm_heartbeat_run_cycle(VM* vm);
|
||
vm_heartbeat_run_cycle(vm);
|
||
vm->heartbeat.check_counter = 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Physics hook for the outer interpreter word-lookup path.
|
||
*
|
||
* Called when the outer interpreter successfully finds a word by name,
|
||
* before executing it. Applies linear decay and increments heat on both
|
||
* the found entry and the canonical entry (if different), and updates
|
||
* @c last_active_ns / @c last_decay_ns on both. Acquires @c vm->dict_lock
|
||
* for the entire operation to ensure thread safety.
|
||
*
|
||
* @param vm Pointer to the VM structure
|
||
* @param entry DictEntry found by name search
|
||
* @param canon Canonical DictEntry (may equal @c entry or be the smudge target)
|
||
*/
|
||
void physics_on_lookup(VM* vm, DictEntry* entry, DictEntry* canon)
|
||
{
|
||
if (!vm || !entry) return;
|
||
|
||
uint64_t lookup_ns = sf_monotonic_ns();
|
||
|
||
sf_mutex_lock(&vm->dict_lock);
|
||
|
||
/* Apply decay and heat to found entry. Tick-based, not wall-clock --
|
||
* see physics_metadata_apply_linear_decay(). */
|
||
uint64_t elapsed_entry_ticks = vm->heartbeat.tick_count - entry->physics.last_decay_tick;
|
||
physics_metadata_apply_linear_decay(entry, elapsed_entry_ticks, vm);
|
||
entry->physics.last_decay_tick = vm->heartbeat.tick_count;
|
||
entry->physics.last_active_ns = lookup_ns;
|
||
entry->physics.last_decay_ns = lookup_ns;
|
||
physics_execution_heat_increment(entry);
|
||
|
||
/* Also handle canonical entry if different */
|
||
if (canon && canon != entry)
|
||
{
|
||
uint64_t elapsed_canon_ticks = vm->heartbeat.tick_count - canon->physics.last_decay_tick;
|
||
physics_metadata_apply_linear_decay(canon, elapsed_canon_ticks, vm);
|
||
canon->physics.last_decay_tick = vm->heartbeat.tick_count;
|
||
canon->physics.last_active_ns = lookup_ns;
|
||
canon->physics.last_decay_ns = lookup_ns;
|
||
physics_execution_heat_increment(canon);
|
||
physics_metadata_touch(canon, canon->execution_heat, lookup_ns);
|
||
}
|
||
|
||
sf_mutex_unlock(&vm->dict_lock);
|
||
}
|