Files
StarForth/src/vm_bootstrap.c
T
Claude d6895bdd15 Move vocabulary and control-flow state off file-scope statics onto VM
proof/FINDINGS.md's Isabelle/HOL word-source sweep (§1) found the two
defects severe enough to actively corrupt the live Tripod multi-VM fleet:
file-scope C statics standing in for state that belongs on struct VM.

- vocabulary_words.c (highest severity in the sweep): forth_vocab/
  context_vocab/current_vocab, context_var_addr/current_var_addr, the
  ctx_fc/forth_fc first-char search index, and the `initialized` guard
  were all process-wide statics. Only the first VM to touch any
  vocabulary word ever ran setup; every VM after that silently shared
  VM #1's dictionary-chain pointers and reused VM #1's byte-offset
  addresses as if valid in its own vm->memory. One VM's VOCABULARY/
  DEFINITIONS/FORTH silently changed where every other VM looked up and
  defined words.

- control_words.c: cf_stack/cf_sp/cf_last_mode (IF/THEN/BEGIN/DO/CASE
  compile-time nesting) and the LEAVE/ENDOF patch-site bookkeeping
  (leave_addrs/leave_sp/leave_mark_*, endof_addrs/endof_sp/endof_mark_*)
  were also process-wide statics. Two VMs compiling colon definitions at
  overlapping times would corrupt each other's nesting state.

Both moved onto struct VM, following the existing hold_addr/hold_pos
precedent in include/vm.h ("lives in each VM's own memory... so child
VMs never alias Hera's buffer"):

- New VocabularyState struct (vm->vocab): chain heads, VM-cell addresses,
  first-char index, initialized flag.
- New ControlFlowState struct (vm->cf): cf_stack/cf_sp/cf_last_mode plus
  the LEAVE/ENDOF patch-site stacks. cf_tag_t/cf_item_t/CF_STACK_MAX
  moved from control_words.c into include/vm.h since they're now part of
  the struct VM field's type.
- Sentinel fields (-1/-999, meaning "empty") explicitly initialized in
  both vm_init_with_host() implementations (hosted src/vm_bootstrap.c and
  kernel src/starkernel/vm/vm_bootstrap.c) alongside the existing
  dsp/rsp = -1 initialization, since the preceding zero-init leaves them
  at 0 rather than their empty sentinel.

Every word function in both files already took VM *vm, so no call sites
outside these two files needed to change; cf_push_item/cf_pop_item/
cf_peek_item gained a VM* parameter to reach vm->cf.

Verified: hosted (amd64) and kernel (amd64, __STARKERNEL__) both build
clean with -Wall -Werror after a full clean rebuild (struct VM's layout
changed size, and this Makefile has no header-dependency tracking, so a
stale incremental build would have linked mismatched object layouts).
Hosted POST suite 1012/1012 passing (0 regressions). Manually exercised
VOCABULARY/DEFINITIONS/FORTH/ORDER, and IF/ELSE, DO/LOOP/LEAVE,
BEGIN/WHILE/REPEAT, and CASE/OF/ENDOF/ENDCASE (including nested DO with
I/J) in the REPL -- all correct and unchanged from pre-refactor behavior.

Note: a pre-existing CASE/ENDCASE default-clause bug (the code after the
last OF...ENDOF pair does not correctly become the "default" value once
DROP runs) was found while testing this refactor and confirmed present
on unmodified master too -- not touched here, out of scope for this pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Qf6YcnHgaEtEygq3knx19
2026-09-05 14:07:41 +00:00

410 lines
12 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) 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.
*/
/*
* vm_bootstrap.c - How a VM comes into existence
*
* Contains:
* - dictionary seeding
* - primitive registration
* - initial vocabularies
* - host vs kernel bootstrap branching
* - early init code paths
*
* Rule: If it runs before the VM executes a single word, it belongs here.
*/
#include "vm_internal.h"
#include "../include/log.h"
#include "../include/word_registry.h"
#include "../include/vm_debug.h"
#include "../include/physics_hotwords_cache.h"
#include "../include/rolling_window_of_truth.h"
#include "../include/dictionary_heat_optimization.h"
#include "../include/ssm_jacquard.h"
#include "../include/platform_alloc.h"
#include "word_source/include/vocabulary_words.h"
#ifdef __STARKERNEL__
#include "../include/starkernel/capsule_birth.h"
#include "../include/starkernel/capsule_run.h"
#endif
#include <string.h>
/* ====================== Bootstrap helpers ======================= */
/**
* @brief Ensure the SCR (Screen) system variable cell is allocated and zeroed.
*
* If @c vm->scr_addr does not point to a valid, aligned cell inside the VM
* memory array, aligns the allocation pointer and allots a new cell.
* Always writes 0 to the SCR cell on exit. Called at the end of @c vm_init()
* to guarantee a clean SCR state regardless of how the initial allot
* sequence progressed.
*
* @param vm Active VM; sets @c vm->error = 1 and logs if allot fails
*/
static void vm_bootstrap_scr(VM* vm)
{
if (!vm) return;
/* Ensure SCR cell exists and is zero */
if (!vm_addr_ok(vm, vm->scr_addr, sizeof(cell_t)) ||
(vm->scr_addr % sizeof(cell_t)) != 0)
{
vm_align(vm);
void* p = vm_allot(vm, sizeof(cell_t));
if (!p)
{
vm->error = 1;
log_message(LOG_ERROR, "bootstrap SCR allot failed");
return;
}
vm->scr_addr = (vaddr_t)((uint8_t*)p - vm->memory);
}
vm_store_cell(vm, vm->scr_addr, 0);
}
/**
* @brief Bootstrap a root vocabulary for the VM
*
* Creates a vocabulary by name, finds its dictionary entry, executes
* it to make it current, then calls DEFINITIONS.
*
* @param vm Pointer to VM structure
* @param name Vocabulary name to bootstrap
*/
void vm_bootstrap_root_vocabulary(VM *vm, const char *name)
{
if (!vm || !name) {
return;
}
vocabulary_create_vocabulary_direct(vm, name);
size_t len = strlen(name);
DictEntry *entry = vm_find_word(vm, name, len);
if (!entry || !entry->func) {
vm->error = 1;
log_message(LOG_ERROR, "bootstrap: missing vocabulary '%s'", name);
return;
}
entry->func(vm);
vocabulary_word_definitions(vm);
}
/* ====================== VM init / teardown ======================= */
/**
* @brief Initialize a new virtual machine instance
*
* Allocates memory and initializes all VM structures including:
* - Memory array
* - Data and return stacks
* - System variables (SCR, STATE, BASE)
* - Dictionary
* - Forth-79 wordset
*
* @param vm Pointer to VM structure to initialize
*/
void vm_init(VM* vm)
{
if (!vm) return;
memset(vm, 0, sizeof(*vm));
vm->next_word_id = 0;
vm->recycled_word_id_count = 0;
if (sf_mutex_init(&vm->dict_lock) != 0)
{
log_message(LOG_ERROR, "vm_init: dict_lock init failed");
vm->error = 1;
return;
}
if (sf_mutex_init(&vm->tuning_lock) != 0)
{
log_message(LOG_ERROR, "vm_init: tuning_lock init failed");
sf_mutex_destroy(&vm->dict_lock);
vm->error = 1;
return;
}
vm->memory = (uint8_t*)sf_malloc(VM_MEMORY_SIZE);
if (!vm->memory)
{
log_message(LOG_ERROR, "vm_init: out of memory");
vm->error = 1;
return;
}
vm->dsp = -1;
vm->rsp = -1;
vm->here = 0;
vm->exit_colon = 0;
/* Control-flow bookkeeping sentinels (-1/-999 mean "empty"; memset above
* already zeroed the arrays themselves) */
vm->cf.cf_sp = -1;
vm->cf.cf_last_mode = -999;
vm->cf.leave_sp = -1;
vm->cf.leave_mark_sp = -1;
vm->cf.endof_sp = -1;
vm->cf.endof_mark_sp = -1;
vm->abort_requested = 0;
vm_align(vm);
/* SCR */
{
void* p = vm_allot(vm, sizeof(cell_t));
if (!p)
{
vm->error = 1;
log_message(LOG_ERROR, "vm_init: SCR allot failed");
return;
}
vm->scr_addr = (vaddr_t)((uint8_t*)p - vm->memory);
vm_store_cell(vm, vm->scr_addr, 0);
}
/* STATE (0=interpret, -1=compile) */
{
void* p = vm_allot(vm, sizeof(cell_t));
if (!p)
{
vm->error = 1;
log_message(LOG_ERROR, "vm_init: STATE allot failed");
return;
}
vm->state_addr = (vaddr_t)((uint8_t*)p - vm->memory);
vm_store_cell(vm, vm->state_addr, 0);
vm->state_var = 0;
}
/* BASE (default 10) */
{
void* p = vm_allot(vm, sizeof(cell_t));
if (!p)
{
vm->error = 1;
log_message(LOG_ERROR, "vm_init: BASE allot failed");
return;
}
vm->base_addr = (vaddr_t)((uint8_t*)p - vm->memory);
vm_set_base(vm, 10);
}
/* Pictured-number hold buffer (64 bytes, per-VM so child VMs are independent) */
{
void* p = vm_allot(vm, 64);
if (!p)
{
vm->error = 1;
log_message(LOG_ERROR, "vm_init: hold buffer allot failed");
return;
}
vm->hold_addr = (vaddr_t)((uint8_t*)p - vm->memory);
vm->hold_pos = 0;
memset(p, 0, 64);
}
vm_bootstrap_scr(vm);
vm->mode = MODE_INTERPRET;
vm->compiling_word = NULL;
vm->latest = NULL;
vm->error = 0;
vm->halted = 0;
vm->input_length = 0;
vm->input_pos = 0;
vm->current_executing_entry = NULL;
vm_debug_set_current_vm(vm);
vm_debug_install_signal_handlers();
/* Register Forth-79 wordset */
register_forth79_words(vm);
/* Set FORGET fence to post-boot */
vm->dict_fence_latest = vm->latest;
vm->dict_fence_here = vm->here;
/* Initialize hot-words cache (physics frequency-driven acceleration) */
vm->hotwords_cache = (HotwordsCache*)sf_malloc(sizeof(HotwordsCache));
if (!vm->hotwords_cache)
{
log_message(LOG_ERROR, "vm_init: hotwords cache alloc failed");
vm->error = 1;
return;
}
hotwords_cache_init(vm->hotwords_cache);
/* Initialize rolling window of truth (deterministic execution history) */
if (rolling_window_init(&vm->rolling_window) != 0)
{
log_message(LOG_ERROR, "vm_init: rolling window alloc failed");
vm->error = 1;
return;
}
/* Initialize VM heartbeat (centralized time-driven tuning) */
vm->heartbeat.tick_count = 0;
vm->heartbeat.last_inference_tick = 0;
vm->heartbeat.check_counter = 0;
vm->heartbeat.heartbeat_enabled = 1; /* Enabled by default */
vm->heartbeat.tick_target_ns = HEARTBEAT_TICK_NS;
vm->heartbeat.snapshot_index = 0;
vm->heartbeat.worker = NULL;
vm->heartbeat_decay_cursor_id = WORD_ID_INVALID;
/* Initialize pipelining global metrics (aggregated prefetch tracking) */
vm->pipeline_metrics.prefetch_attempts = 0;
vm->pipeline_metrics.prefetch_hits = 0;
vm->pipeline_metrics.window_tuning_checks = 0;
vm->pipeline_metrics.last_checked_window_size = vm->rolling_window.effective_window_size;
vm->pipeline_metrics.last_checked_accuracy = 0.0;
vm->pipeline_metrics.suggested_next_size = vm->rolling_window.effective_window_size;
vm_heartbeat_publish_snapshot(vm);
/* Start heartbeat thread (if enabled) */
vm_heartbeat_start_thread(vm);
/* Initialize adaptive heat decay tuning (Loop #3) */
/* Start with 2:1 ratio in Q48.16 format: 2.0 << 16 = 131072 */
vm->decay_slope_q48 = (1ULL << 16) / 3; /* 1/3 starting slope (Q48.16) */
vm->last_decay_check_ns = 0;
vm->total_heat_at_last_check = 0;
vm->stale_word_count_at_check = 0;
vm->decay_slope_direction = 0; /* Start neutral */
/* Phase 2: Initialize heat-aware dictionary optimization */
vm->lookup_strategy = 0; /* Start with naive lookup, will adapt based on patterns */
vm->last_bucket_reorg_ns = 0; /* Force first reorg quickly */
dict_update_heat_percentiles(vm); /* Calculate initial percentiles */
/* SSM L8: Jacquard Mode Selector initialization */
vm->ssm_l8_state = sf_malloc(sizeof(ssm_l8_state_t));
if (!vm->ssm_l8_state)
{
log_message(LOG_ERROR, "vm_init: SSM L8 state alloc failed");
vm->error = 1;
return;
}
ssm_l8_init((ssm_l8_state_t*)vm->ssm_l8_state, SSM_MODE_C0);
ssm_l8_init_table((ssm_l8_state_t*)vm->ssm_l8_state);
vm->ssm_config = sf_malloc(sizeof(ssm_config_t));
if (!vm->ssm_config)
{
log_message(LOG_ERROR, "vm_init: SSM config alloc failed");
vm->error = 1;
return;
}
/* Initialize with C0 (minimal) mode: all 7 loops off. The adaptive
* table's first trial-end (or an explicit ssm_apply_mode_from_table()
* call) overwrites this with the real DoE-seeded starting config. */
((ssm_config_t*)vm->ssm_config)->L1_heat_tracking = 0;
((ssm_config_t*)vm->ssm_config)->L2_rolling_window = 0;
((ssm_config_t*)vm->ssm_config)->L3_linear_decay = 0;
((ssm_config_t*)vm->ssm_config)->L4_pipelining = 0;
((ssm_config_t*)vm->ssm_config)->L5_window_inference = 0;
((ssm_config_t*)vm->ssm_config)->L6_decay_inference = 0;
((ssm_config_t*)vm->ssm_config)->L7_adaptive_heartrate = 0;
#ifdef __STARKERNEL__
/* Capsule subsystem: register VM hooks, init registry and run log.
* capsule_birth_mama() is called separately after vm_init() returns,
* once the caller has access to the compiled-in capsule directory. */
capsule_vm_hooks_register();
capsule_vm_registry_init();
capsule_run_log_init();
#endif
}
/**
* @brief Clean up and free VM resources
*
* Frees allocated memory and resets VM state.
*
* @param vm Pointer to VM structure to clean up
*/
void vm_cleanup(VM* vm)
{
if (!vm) return;
/* Stop heartbeat thread (if running) */
vm_heartbeat_stop_thread(vm);
/* Clean up hot-words cache */
if (vm->hotwords_cache)
{
hotwords_cache_cleanup(vm->hotwords_cache);
sf_free(vm->hotwords_cache);
vm->hotwords_cache = NULL;
}
/* Clean up rolling window of truth */
rolling_window_cleanup(&vm->rolling_window);
/* Clean up SSM L8 state */
if (vm->ssm_l8_state)
{
ssm_l8_free_table((ssm_l8_state_t*)vm->ssm_l8_state);
sf_free(vm->ssm_l8_state);
vm->ssm_l8_state = NULL;
}
if (vm->ssm_config)
{
sf_free(vm->ssm_config);
vm->ssm_config = NULL;
}
if (vm->memory)
{
sf_free(vm->memory);
vm->memory = NULL;
}
vm->here = 0;
sf_mutex_destroy(&vm->tuning_lock);
sf_mutex_destroy(&vm->dict_lock);
}