Files
LithosAnanake/src/starkernel/vm/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

368 lines
11 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.
*/
/*
* vm_bootstrap.c - VM initialization and bootstrap logic
*/
#include "../include/vm.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 "word_source/include/vocabulary_words.h"
#include "vm_internal.h"
#ifdef __STARKERNEL__
#include "starkernel/vm/arena.h"
#include "starkernel/console.h"
#endif
#include <string.h>
#include <errno.h>
#if defined(__STARKERNEL__) && SK_PARITY_DEBUG
static void vm_bootstrap_print_hex(uint64_t value)
{
char buf[19];
buf[0] = '0';
buf[1] = 'x';
buf[18] = '\0';
for (int i = 0; i < 16; ++i) {
uint8_t nibble = (uint8_t)((value >> ((15 - i) * 4)) & 0xF);
buf[i + 2] = (nibble < 10) ? (char)('0' + nibble) : (char)('a' + nibble - 10);
}
console_puts(buf);
}
#endif
static void vm_bootstrap_scr(VM *vm)
{
vm_align(vm);
void* p = vm_allot(vm, sizeof(cell_t));
if (!p)
{
vm->error = 1;
log_message(LOG_ERROR, "vm_bootstrap_scr: SCR allot failed");
return;
}
vm->scr_addr = (vaddr_t)((uint8_t*)p - vm->memory);
vm_store_cell(vm, vm->scr_addr, 0);
}
void vm_bootstrap_root_vocabulary(VM *vm, const char *name)
{
if (!vm || !name) {
return;
}
#if defined(__STARKERNEL__) && SK_PARITY_DEBUG
console_println("[VM_BOOTSTRAP] root vocabulary entry");
#endif
#ifdef __STARKERNEL__
sk_vm_arena_assert_guards("vm_bootstrap_root_vocabulary:entry");
#endif
vocabulary_create_vocabulary_direct(vm, name);
size_t len = strlen(name);
#if defined(__STARKERNEL__) && SK_PARITY_DEBUG
console_println("[VM_BOOTSTRAP] before vm_find_word");
#endif
DictEntry *entry = vm_find_word(vm, name, len);
#if defined(__STARKERNEL__) && SK_PARITY_DEBUG
console_println("[VM_BOOTSTRAP] after vm_find_word");
#endif
if (!entry || !entry->func) {
vm->error = 1;
log_message(LOG_ERROR, "bootstrap: missing vocabulary '%s'", name);
return;
}
#if defined(__STARKERNEL__) && SK_PARITY_DEBUG
console_puts("[VOC] ");
console_puts(name);
console_puts(" entry=");
vm_bootstrap_print_hex((uint64_t)(uintptr_t)entry);
console_puts(" xt=");
vm_bootstrap_print_hex((uint64_t)(uintptr_t)entry->func);
console_println("");
const VMHostServices *host = vm_host(vm);
if (host && host->is_executable_ptr &&
!host->is_executable_ptr((const void *)entry->func)) {
console_puts("[VOC] invalid XT for ");
console_puts(name);
console_puts(" func=");
vm_bootstrap_print_hex((uint64_t)(uintptr_t)entry->func);
console_println("");
if (host->panic) {
host->panic("bootstrap vocabulary XT invalid");
}
}
#endif
entry->func(vm);
vocabulary_word_definitions(vm);
#ifdef __STARKERNEL__
sk_vm_arena_assert_guards("vm_bootstrap_root_vocabulary:exit");
#endif
}
void vm_init(VM* vm)
{
vm_init_with_host(vm, NULL);
}
void vm_init_with_host(VM* vm, const VMHostServices *host)
{
if (!vm) return;
*vm = (VM){0};
vm->host = host ? host : vm_default_host_services();
#ifndef __STARKERNEL__
vm_reset_hosted_fake_ns(vm);
#endif
vm->interpreter_enabled = 0;
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*)vm_host_alloc(vm, VM_MEMORY_SIZE, sizeof(void*));
if (!vm->memory)
{
log_message(LOG_ERROR, "vm_init: out of host 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"; the
* `*vm = (VM){0}` 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;
log_message(LOG_DEBUG, "vm_init: memory=%p here=%zu", (void*)vm->memory, vm->here);
vm_bootstrap_scr(vm);
/* 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->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);
#ifdef __STARKERNEL__
#if SK_PARITY_DEBUG
console_println("[VM_BOOTSTRAP] register_forth79_words complete");
#endif
#endif
/* Interpreter may be enabled only after parity check passes (in sk_vm_bootstrap.c) */
/* Set FORGET fence to post-boot */
vm->dict_fence_latest = vm->latest;
vm->dict_fence_here = vm->here;
/* Initialize hot-words cache */
vm->hotwords_cache = (HotwordsCache*)vm_host_alloc(vm, sizeof(HotwordsCache), sizeof(void*));
if (!vm->hotwords_cache)
{
log_message(LOG_ERROR, "vm_init: hotwords cache malloc failed");
vm->error = 1;
return;
}
hotwords_cache_init(vm->hotwords_cache);
/* Initialize rolling window */
if (rolling_window_init(&vm->rolling_window) != 0)
{
log_message(LOG_ERROR, "vm_init: rolling window malloc failed");
vm->error = 1;
return;
}
/* Initialize VM heartbeat */
vm->heartbeat.tick_count = 0;
vm->heartbeat.last_inference_tick = 0;
vm->heartbeat.check_counter = 0;
vm->heartbeat.heartbeat_enabled = 1;
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;
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;
heartbeat_publish_snapshot(vm);
#if HEARTBEAT_HAS_THREADS
vm->heartbeat.worker = vm_host_calloc(vm, 1, sizeof(HeartbeatWorker));
if (vm->heartbeat.worker)
{
vm->heartbeat.worker->tick_ns = HEARTBEAT_TICK_NS;
if (pthread_create(&vm->heartbeat.worker->thread, NULL, heartbeat_thread_main, vm) != 0)
{
log_message(LOG_WARN, "heartbeat: pthread_create failed (%d), falling back to inline mode", errno);
vm_host_free(vm, vm->heartbeat.worker);
vm->heartbeat.worker = NULL;
}
}
else
{
log_message(LOG_WARN, "heartbeat: worker allocation failed, using inline heartbeat");
}
#endif
vm->decay_slope_q48 = (1ULL << 16) / 3;
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;
vm->lookup_strategy = 0;
vm->last_bucket_reorg_ns = 0;
dict_update_heat_percentiles(vm);
vm->ssm_l8_state = vm_host_alloc(vm, sizeof(ssm_l8_state_t), sizeof(void*));
if (!vm->ssm_l8_state)
{
log_message(LOG_ERROR, "vm_init: SSM L8 state malloc 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 = vm_host_alloc(vm, sizeof(ssm_config_t), sizeof(void*));
if (!vm->ssm_config)
{
log_message(LOG_ERROR, "vm_init: SSM config malloc failed");
vm->error = 1;
return;
}
((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;
}