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
368 lines
11 KiB
C
368 lines
11 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.
|
||
|
||
*/
|
||
|
||
/*
|
||
* 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;
|
||
|
||
}
|