837 lines
27 KiB
C
837 lines
27 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.
|
||
|
||
*/
|
||
|
||
#include "include/starforth_words.h"
|
||
#include "include/vocabulary_words.h"
|
||
#include "../../include/word_registry.h"
|
||
#include "../../include/log.h"
|
||
#include "../../include/vm.h"
|
||
#ifdef __STARKERNEL__
|
||
#include "starkernel/vm/arena.h"
|
||
#endif
|
||
#include "include/vocabulary_words.h"
|
||
#include "../../include/version.h"
|
||
#include "../../include/physics_metadata.h"
|
||
#include "../../include/platform_time.h"
|
||
#include "../../include/profiler.h"
|
||
#include "../../include/block_subsystem.h"
|
||
#include <string.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <errno.h>
|
||
|
||
#ifdef __STARKERNEL__
|
||
#define STARFORTH_CHECK_ARENA(tag) sk_vm_arena_assert_guards(tag)
|
||
#else
|
||
#define STARFORTH_CHECK_ARENA(tag) ((void)0)
|
||
#endif
|
||
|
||
/* ============================================================================
|
||
* PRNG State - Linear Congruential Generator (Numerical Recipes constants)
|
||
* ============================================================================ */
|
||
static uint64_t g_prng_state = 1;
|
||
|
||
/*
|
||
* @brief Get execution heat count for word at dictionary address
|
||
*
|
||
* Stack effect: ( addr -- n )
|
||
* Returns the execution frequency (execution_heat counter) for a word.
|
||
* Note: Exposed as ENTROPY@ for FORTH compatibility, but measures execution heat.
|
||
* @param vm Pointer to the VM instance
|
||
*/
|
||
/**
|
||
* @brief Validate that an address is a valid DictEntry pointer
|
||
* @param vm The VM instance
|
||
* @param candidate The address to validate
|
||
* @return 1 if valid, 0 if not
|
||
*/
|
||
static int is_valid_dict_entry(VM* vm, DictEntry* candidate)
|
||
{
|
||
if (!candidate) return 0;
|
||
|
||
/* Walk dictionary to verify this is a real entry - must hold dict_lock */
|
||
int found = 0;
|
||
sf_mutex_lock(&vm->dict_lock);
|
||
for (DictEntry* e = vm->latest; e != NULL; e = e->link)
|
||
{
|
||
if (e == candidate) { found = 1; break; }
|
||
}
|
||
sf_mutex_unlock(&vm->dict_lock);
|
||
return found;
|
||
}
|
||
|
||
void starforth_word_execution_heat_fetch(VM* vm)
|
||
{
|
||
if (vm->dsp < 0)
|
||
{
|
||
vm->error = 1;
|
||
log_message(LOG_ERROR, "ENTROPY@: data stack underflow");
|
||
return;
|
||
}
|
||
|
||
cell_t addr = vm_pop(vm);
|
||
DictEntry* entry = (DictEntry*)(uintptr_t)addr;
|
||
|
||
if (!entry)
|
||
{
|
||
vm->error = 1;
|
||
log_message(LOG_ERROR, "ENTROPY@: null dictionary entry");
|
||
return;
|
||
}
|
||
|
||
/* Guardrail: Validate the pointer is actually a DictEntry */
|
||
if (!is_valid_dict_entry(vm, entry))
|
||
{
|
||
vm->error = 1;
|
||
log_message(LOG_ERROR, "ENTROPY@: invalid dictionary entry address %p (not in dictionary)", (void*)entry);
|
||
return;
|
||
}
|
||
|
||
vm_push(vm, entry->execution_heat);
|
||
log_message(LOG_DEBUG, "ENTROPY@: word execution heat = %ld", (long)entry->execution_heat);
|
||
}
|
||
|
||
/**
|
||
* @brief Set execution heat count for word at dictionary address
|
||
*
|
||
* Stack effect: ( n addr -- )
|
||
* Note: Exposed as ENTROPY! for FORTH compatibility, but sets execution heat.
|
||
* @param vm Pointer to the VM instance
|
||
*/
|
||
void starforth_word_execution_heat_store(VM* vm)
|
||
{
|
||
if (vm->dsp < 1)
|
||
{
|
||
vm->error = 1;
|
||
log_message(LOG_ERROR, "ENTROPY!: data stack underflow");
|
||
return;
|
||
}
|
||
|
||
cell_t addr = vm_pop(vm);
|
||
cell_t value = vm_pop(vm);
|
||
DictEntry* entry = (DictEntry*)(uintptr_t)addr;
|
||
|
||
if (!entry)
|
||
{
|
||
vm->error = 1;
|
||
log_message(LOG_ERROR, "ENTROPY!: null dictionary entry");
|
||
return;
|
||
}
|
||
|
||
/* Guardrail: Validate the pointer is actually a DictEntry */
|
||
if (!is_valid_dict_entry(vm, entry))
|
||
{
|
||
vm->error = 1;
|
||
log_message(LOG_ERROR, "ENTROPY!: invalid dictionary entry address %p (not in dictionary)", (void*)entry);
|
||
return;
|
||
}
|
||
|
||
entry->execution_heat = value;
|
||
log_message(LOG_DEBUG, "ENTROPY!: set word execution heat to %ld", (long)value);
|
||
}
|
||
|
||
/**
|
||
* @brief Display execution heat statistics for all words in dictionary
|
||
*
|
||
* Stack effect: ( -- )
|
||
* Shows execution frequency (execution_heat) for each word.
|
||
* @param vm Pointer to the VM instance
|
||
*/
|
||
void starforth_word_word_execution_heat(VM* vm)
|
||
{
|
||
printf("Word Usage Statistics (Execution Heat Counts):\n");
|
||
printf("=============================================\n");
|
||
|
||
cell_t total_heat = 0;
|
||
int word_count = 0;
|
||
|
||
for (DictEntry* entry = vm->latest; entry; entry = entry->link)
|
||
{
|
||
if (entry->execution_heat > 0)
|
||
{
|
||
printf("%.*s: %ld\n", (int)entry->name_len, entry->name, (long)entry->execution_heat);
|
||
total_heat += entry->execution_heat;
|
||
}
|
||
word_count++;
|
||
}
|
||
|
||
printf("-------------------------------------\n");
|
||
printf("Total executions: %ld\n", (long)total_heat);
|
||
printf("Total words: %d\n", word_count);
|
||
if (total_heat > 0)
|
||
{
|
||
printf("Average executions per word: %ld\n", (long)(total_heat / word_count));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Reset all execution heat counters to zero
|
||
*
|
||
* Stack effect: ( -- )
|
||
* @param vm Pointer to the VM instance
|
||
*/
|
||
void starforth_word_reset_execution_heat(VM* vm)
|
||
{
|
||
for (DictEntry* entry = vm->latest; entry; entry = entry->link)
|
||
{
|
||
if (entry->execution_heat > 0)
|
||
{
|
||
entry->execution_heat = 0;
|
||
entry->physics.temperature_q8 = 0;
|
||
entry->physics.avg_latency_ns = 0;
|
||
entry->physics.last_active_ns = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Display the N most frequently used words
|
||
*
|
||
* Stack effect: ( n -- )
|
||
* @param vm Pointer to the VM instance
|
||
*/
|
||
void starforth_word_top_words(VM* vm)
|
||
{
|
||
if (vm->dsp < 0)
|
||
{
|
||
vm->error = 1;
|
||
log_message(LOG_ERROR, "TOP-WORDS: data stack underflow");
|
||
return;
|
||
}
|
||
|
||
cell_t n = vm_pop(vm);
|
||
if (n <= 0)
|
||
{
|
||
printf("TOP-WORDS: invalid count %ld\n", (long)n);
|
||
return;
|
||
}
|
||
|
||
/* Simple implementation: just show words with execution_heat > 0, sorted by execution_heat */
|
||
printf("Top %ld most frequently used words:\n", (long)n);
|
||
printf("==================================\n");
|
||
|
||
/* Collect all words with execution_heat > 0 (static: avoids large kernel stack frame) */
|
||
static DictEntry* words_with_heat[1000];
|
||
int word_count = 0;
|
||
|
||
for (DictEntry* entry = vm->latest; entry && word_count < 1000; entry = entry->link)
|
||
{
|
||
if (entry->execution_heat > 0)
|
||
{
|
||
words_with_heat[word_count++] = entry;
|
||
}
|
||
}
|
||
|
||
/* Simple bubble sort by execution_heat (descending) */
|
||
for (int i = 0; i < word_count - 1; i++)
|
||
{
|
||
for (int j = 0; j < word_count - i - 1; j++)
|
||
{
|
||
if (words_with_heat[j]->execution_heat < words_with_heat[j + 1]->execution_heat)
|
||
{
|
||
DictEntry* temp = words_with_heat[j];
|
||
words_with_heat[j] = words_with_heat[j + 1];
|
||
words_with_heat[j + 1] = temp;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Display top N */
|
||
int display_count = (word_count < n) ? word_count : (int)n;
|
||
for (int i = 0; i < display_count; i++)
|
||
{
|
||
DictEntry* entry = words_with_heat[i];
|
||
printf("%d. %.*s: %ld\n", i + 1, (int)entry->name_len, entry->name, (long)entry->execution_heat);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Shebang-style comment word for init.4th metadata
|
||
*
|
||
* Starts with "(- " and consumes input until closing ")"
|
||
* Used for marking blocks that should be extracted to init.4th
|
||
* Stack effect: ( -- )
|
||
* @param vm Pointer to the VM instance
|
||
*/
|
||
void starforth_word_paren_dash(VM* vm)
|
||
{
|
||
/* Consume input until we find closing ")" */
|
||
int depth = 1;
|
||
|
||
while (vm->input_pos < vm->input_length && depth > 0)
|
||
{
|
||
char c = vm->input_buffer[vm->input_pos++];
|
||
if (c == '(')
|
||
{
|
||
depth++;
|
||
}
|
||
else if (c == ')')
|
||
{
|
||
depth--;
|
||
}
|
||
}
|
||
|
||
if (depth > 0)
|
||
{
|
||
log_message(LOG_WARN, "(- comment not terminated");
|
||
}
|
||
|
||
/* This is a comment - no stack effect, just consume input */
|
||
log_message(LOG_DEBUG, "(- comment parsed (init.4th metadata marker)");
|
||
}
|
||
|
||
/**
|
||
* @brief Initialize system from init.4th configuration file
|
||
*
|
||
* Reads ./capsules/core/init.4th, parses Block headers,
|
||
* copies blocks sequentially starting at block 1, then executes them.
|
||
* Stack effect: ( -- )
|
||
* @param vm Pointer to the VM instance
|
||
*/
|
||
void starforth_word_init(VM* vm)
|
||
{
|
||
log_message(LOG_INFO, "INIT: Starting system initialization from init.4th");
|
||
|
||
/* Read init.4th file */
|
||
char* file_content = NULL;
|
||
size_t file_size = 0;
|
||
|
||
/* HISTORICAL: an L4RE_TARGET branch here read init.4th from ROMFS
|
||
* (never implemented -- it just logged an error and halted). L4Re
|
||
* support has been removed as an active target.
|
||
* #ifdef L4RE_TARGET
|
||
* log_message(LOG_ERROR, "INIT: L4Re ROMFS not yet implemented");
|
||
* vm->error = 1;
|
||
* vm->halted = 1;
|
||
* return;
|
||
* #else
|
||
*/
|
||
/* Linux/POSIX: read from filesystem */
|
||
const char* init_path = "./capsules/core/init.4th";
|
||
FILE* fp = fopen(init_path, "r");
|
||
if (!fp)
|
||
{
|
||
if (errno == ENOENT)
|
||
{
|
||
log_message(LOG_INFO, "INIT: %s not found — skipping", init_path);
|
||
return;
|
||
}
|
||
log_message(LOG_ERROR, "INIT: Failed to open %s: %s", init_path, strerror(errno));
|
||
vm->error = 1;
|
||
vm->halted = 1;
|
||
return;
|
||
}
|
||
|
||
/* Get file size */
|
||
fseek(fp, 0, SEEK_END);
|
||
file_size = (size_t)ftell(fp);
|
||
fseek(fp, 0, SEEK_SET);
|
||
|
||
/* Allocate buffer */
|
||
file_content = (char*)malloc(file_size + 1);
|
||
if (!file_content)
|
||
{
|
||
log_message(LOG_ERROR, "INIT: Failed to allocate memory for init.4th (%zu bytes)", file_size);
|
||
fclose(fp);
|
||
vm->error = 1;
|
||
vm->halted = 1;
|
||
return;
|
||
}
|
||
|
||
/* Read entire file */
|
||
size_t bytes_read = fread(file_content, 1, file_size, fp);
|
||
fclose(fp);
|
||
|
||
if (bytes_read != file_size)
|
||
{
|
||
log_message(LOG_ERROR, "INIT: Failed to read init.4th (expected %zu, got %zu bytes)", file_size, bytes_read);
|
||
free(file_content);
|
||
vm->error = 1;
|
||
vm->halted = 1;
|
||
return;
|
||
}
|
||
file_content[file_size] = '\0';
|
||
log_message(LOG_DEBUG, "INIT: Read %zu bytes from %s", file_size, init_path);
|
||
/* #endif -- matches the commented-out #ifdef L4RE_TARGET above */
|
||
|
||
/* First pass: build mapping of original block numbers to sequential numbers */
|
||
typedef struct
|
||
{
|
||
int original;
|
||
int sequential;
|
||
} BlockMapping;
|
||
|
||
BlockMapping block_map[256]; /* Support up to 256 init blocks */
|
||
int block_count = 0;
|
||
|
||
char* scan_start = file_content;
|
||
for (size_t i = 0; i <= file_size; i++)
|
||
{
|
||
char c = (i < file_size) ? file_content[i] : '\n';
|
||
|
||
if (c == '\n' || i == file_size)
|
||
{
|
||
size_t line_len = (size_t)(&file_content[i] - scan_start);
|
||
|
||
/* Check if this line starts with "Block " */
|
||
if (line_len >= 6 && strncmp(scan_start, "Block ", 6) == 0)
|
||
{
|
||
/* Parse the block number */
|
||
int orig_block_num = 0;
|
||
if (sscanf(scan_start + 6, "%d", &orig_block_num) == 1)
|
||
{
|
||
if (block_count < 256)
|
||
{
|
||
block_map[block_count].original = orig_block_num;
|
||
block_map[block_count].sequential = block_count + 1;
|
||
log_message(LOG_DEBUG, "INIT: Block mapping: %d -> %d",
|
||
orig_block_num, block_count + 1);
|
||
block_count++;
|
||
}
|
||
}
|
||
}
|
||
|
||
scan_start = &file_content[i + 1];
|
||
}
|
||
}
|
||
|
||
/* Second pass: copy blocks with LOAD reference rewriting */
|
||
int current_dest_block = 1;
|
||
char* line_start = file_content;
|
||
char* block_content_start = NULL;
|
||
int in_block = 0;
|
||
|
||
for (size_t i = 0; i <= file_size; i++)
|
||
{
|
||
char c = (i < file_size) ? file_content[i] : '\n';
|
||
|
||
/* End of line or end of file */
|
||
if (c == '\n' || i == file_size)
|
||
{
|
||
size_t line_len = (size_t)(&file_content[i] - line_start);
|
||
|
||
/* Check if this line starts with "Block " */
|
||
if (line_len >= 6 && strncmp(line_start, "Block ", 6) == 0)
|
||
{
|
||
/* If we were already in a block, write the previous block */
|
||
if (in_block && block_content_start)
|
||
{
|
||
size_t block_len = (size_t)(line_start - block_content_start);
|
||
|
||
/* Get buffer for destination block */
|
||
uint8_t* buf = blk_get_buffer((uint32_t)current_dest_block, 1);
|
||
if (!buf)
|
||
{
|
||
log_message(LOG_ERROR, "INIT: Failed to get buffer for block %d", current_dest_block);
|
||
free(file_content);
|
||
vm->error = 1;
|
||
vm->halted = 1;
|
||
return;
|
||
}
|
||
|
||
/* Clear block and copy with LOAD rewriting */
|
||
memset(buf, 0, 1024);
|
||
size_t copy_len = (block_len > 1024) ? 1024 : block_len;
|
||
|
||
/* Simple approach: scan and replace "NNNN LOAD" patterns */
|
||
size_t src = 0, dst = 0;
|
||
while (src < copy_len && dst < 1024)
|
||
{
|
||
/* Check for digit start */
|
||
if (block_content_start[src] >= '0' && block_content_start[src] <= '9')
|
||
{
|
||
/* Parse number */
|
||
int num = 0;
|
||
size_t num_start = src;
|
||
while (src < copy_len && block_content_start[src] >= '0' &&
|
||
block_content_start[src] <= '9')
|
||
{
|
||
num = num * 10 + (block_content_start[src++] - '0');
|
||
}
|
||
|
||
/* Check for whitespace + LOAD */
|
||
size_t ws_start = src;
|
||
while (src < copy_len && (block_content_start[src] == ' ' ||
|
||
block_content_start[src] == '\t'))
|
||
{
|
||
src++;
|
||
}
|
||
|
||
if (src + 4 <= copy_len && strncmp(&block_content_start[src], "LOAD", 4) == 0)
|
||
{
|
||
/* Remap block number */
|
||
int new_num = num;
|
||
for (int m = 0; m < block_count; m++)
|
||
{
|
||
if (block_map[m].original == num)
|
||
{
|
||
new_num = block_map[m].sequential;
|
||
log_message(LOG_INFO, "INIT: Rewrote %d LOAD -> %d LOAD", num, new_num);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Write remapped number + whitespace + LOAD */
|
||
char rewrite[32];
|
||
int rewrite_len = snprintf(rewrite, sizeof(rewrite), "%d", new_num);
|
||
if (dst + rewrite_len < 1024)
|
||
{
|
||
memcpy(&buf[dst], rewrite, rewrite_len);
|
||
dst += rewrite_len;
|
||
}
|
||
|
||
/* Copy whitespace */
|
||
size_t ws_len = src - ws_start;
|
||
if (dst + ws_len < 1024)
|
||
{
|
||
memcpy(&buf[dst], &block_content_start[ws_start], ws_len);
|
||
dst += ws_len;
|
||
}
|
||
|
||
/* Copy LOAD */
|
||
if (dst + 4 < 1024)
|
||
{
|
||
memcpy(&buf[dst], "LOAD", 4);
|
||
dst += 4;
|
||
}
|
||
src += 4;
|
||
}
|
||
else
|
||
{
|
||
/* Not LOAD, copy number as-is */
|
||
size_t num_len = src - num_start;
|
||
if (dst + num_len < 1024)
|
||
{
|
||
memcpy(&buf[dst], &block_content_start[num_start], num_len);
|
||
dst += num_len;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Regular char */
|
||
buf[dst++] = block_content_start[src++];
|
||
}
|
||
}
|
||
|
||
log_message(LOG_INFO, "INIT: Copied block content to block %d (%zu bytes)",
|
||
current_dest_block, dst);
|
||
current_dest_block++;
|
||
}
|
||
|
||
/* Start new block - content begins on next line */
|
||
in_block = 1;
|
||
block_content_start = &file_content[i + 1];
|
||
}
|
||
|
||
line_start = &file_content[i + 1];
|
||
}
|
||
}
|
||
|
||
/* Write final block if we were in one */
|
||
if (in_block && block_content_start)
|
||
{
|
||
size_t block_len = (size_t)(&file_content[file_size] - block_content_start);
|
||
|
||
uint8_t* buf = blk_get_buffer((uint32_t)current_dest_block, 1);
|
||
if (!buf)
|
||
{
|
||
log_message(LOG_ERROR, "INIT: Failed to get buffer for final block %d", current_dest_block);
|
||
free(file_content);
|
||
vm->error = 1;
|
||
vm->halted = 1;
|
||
return;
|
||
}
|
||
|
||
memset(buf, 0, 1024);
|
||
size_t copy_len = (block_len > 1024) ? 1024 : block_len;
|
||
memcpy(buf, block_content_start, copy_len);
|
||
|
||
log_message(LOG_INFO, "INIT: Copied block content to block %d (%zu bytes)",
|
||
current_dest_block, copy_len);
|
||
current_dest_block++;
|
||
}
|
||
|
||
free(file_content);
|
||
|
||
int total_blocks = current_dest_block - 1;
|
||
log_message(LOG_INFO, "INIT: Loaded %d blocks from init.4th", total_blocks);
|
||
|
||
/* Execute all blocks sequentially */
|
||
log_message(LOG_INFO, "INIT: Executing initialization blocks...");
|
||
for (int i = 1; i < current_dest_block; i++)
|
||
{
|
||
log_message(LOG_DEBUG, "INIT: Executing block %d (LOAD)", i);
|
||
|
||
/* Push block number and execute LOAD */
|
||
vm_push(vm, (cell_t)i);
|
||
|
||
/* Find and execute LOAD word */
|
||
DictEntry* load_word = vm_find_word(vm, "LOAD", 4);
|
||
if (!load_word)
|
||
{
|
||
log_message(LOG_ERROR, "INIT: LOAD word not found in dictionary");
|
||
vm->error = 1;
|
||
vm->halted = 1;
|
||
return;
|
||
}
|
||
|
||
vm->current_executing_entry = load_word;
|
||
physics_execution_heat_increment(load_word);
|
||
profiler_word_count(load_word);
|
||
profiler_word_enter(load_word);
|
||
load_word->func(vm);
|
||
physics_metadata_touch(load_word, load_word->execution_heat, sf_monotonic_ns());
|
||
profiler_word_exit(load_word);
|
||
vm->current_executing_entry = NULL;
|
||
|
||
/* Check for errors after each block */
|
||
if (vm->error)
|
||
{
|
||
log_message(LOG_ERROR, "INIT: Error executing block %d - system halted", i);
|
||
vm->halted = 1;
|
||
return;
|
||
}
|
||
}
|
||
|
||
/* Switch back to FORTH vocabulary */
|
||
log_message(LOG_INFO, "INIT: Switching to FORTH vocabulary");
|
||
vm_interpret(vm, "FORTH DEFINITIONS");
|
||
if (vm->error)
|
||
{
|
||
log_message(LOG_ERROR, "INIT: Failed to switch to FORTH vocabulary - system halted");
|
||
vm->halted = 1;
|
||
return;
|
||
}
|
||
|
||
/* Zero all init blocks to free them for userspace */
|
||
log_message(LOG_INFO, "INIT: Zeroing %d init blocks for userspace use", total_blocks);
|
||
for (int i = 1; i < current_dest_block; i++)
|
||
{
|
||
uint8_t* buf = blk_get_buffer((uint32_t)i, 1);
|
||
if (buf)
|
||
{
|
||
memset(buf, 0, 1024);
|
||
log_message(LOG_DEBUG, "INIT: Zeroed block %d", i);
|
||
}
|
||
else
|
||
{
|
||
log_message(LOG_WARN, "INIT: Failed to zero block %d (non-critical)", i);
|
||
}
|
||
}
|
||
|
||
log_message(LOG_INFO, "INIT: System initialization complete - blocks freed, FORTH context active");
|
||
}
|
||
|
||
/* ============================================================================
|
||
* PRNG / Utility Words
|
||
* ============================================================================ */
|
||
|
||
/**
|
||
* @brief Internal PRNG step using Linear Congruential Generator
|
||
*
|
||
* Uses Numerical Recipes LCG constants for good statistical properties.
|
||
* @return Next pseudo-random 64-bit value
|
||
*/
|
||
static uint64_t prng_next(void)
|
||
{
|
||
/* LCG: state = (a * state + c) mod m, where m = 2^64 (implicit) */
|
||
g_prng_state = g_prng_state * 6364136223846793005ULL + 1442695040888963407ULL;
|
||
return g_prng_state;
|
||
}
|
||
|
||
/**
|
||
* @brief Set the PRNG seed
|
||
*
|
||
* Stack effect: ( n -- )
|
||
* Sets the internal PRNG state for reproducible random sequences.
|
||
* @param vm Pointer to the VM instance
|
||
*/
|
||
void starforth_word_seed(VM* vm)
|
||
{
|
||
if (vm->dsp < 0)
|
||
{
|
||
vm->error = 1;
|
||
log_message(LOG_ERROR, "SEED: data stack underflow");
|
||
return;
|
||
}
|
||
|
||
cell_t seed = vm_pop(vm);
|
||
g_prng_state = (uint64_t)seed;
|
||
|
||
/* Ensure non-zero state (LCG weakness) */
|
||
if (g_prng_state == 0)
|
||
{
|
||
g_prng_state = 1;
|
||
}
|
||
|
||
log_message(LOG_DEBUG, "SEED: PRNG seeded with %lu", (unsigned long)g_prng_state);
|
||
}
|
||
|
||
/*
|
||
* @brief Generate bounded random number
|
||
*
|
||
* Stack effect: ( lo hi -- n )
|
||
* Returns a pseudo-random number in the inclusive range [lo, hi].
|
||
* @param vm Pointer to the VM instance
|
||
*/
|
||
void starforth_word_random(VM* vm)
|
||
{
|
||
if (vm->dsp < 1)
|
||
{
|
||
vm->error = 1;
|
||
log_message(LOG_ERROR, "RANDOM: data stack underflow (need lo hi)");
|
||
return;
|
||
}
|
||
|
||
cell_t hi = vm_pop(vm);
|
||
cell_t lo = vm_pop(vm);
|
||
|
||
/* Handle inverted range */
|
||
if (lo > hi)
|
||
{
|
||
cell_t tmp = lo;
|
||
lo = hi;
|
||
hi = tmp;
|
||
}
|
||
|
||
/* Generate random value in range [lo, hi] inclusive */
|
||
uint64_t range = (uint64_t)(hi - lo) + 1;
|
||
uint64_t raw = prng_next();
|
||
|
||
/* Modulo bias reduction: use upper bits which have better randomness in LCG */
|
||
cell_t result = lo + (cell_t)((raw >> 16) % range);
|
||
|
||
vm_push(vm, result);
|
||
log_message(LOG_DEBUG, "RANDOM: [%ld, %ld] -> %ld", (long)lo, (long)hi, (long)result);
|
||
}
|
||
|
||
/**
|
||
* @brief Wait for specified number of heartbeat ticks
|
||
*
|
||
* Stack effect: ( n -- )
|
||
* Counts n heartbeat ticks by calling vm_tick() n times.
|
||
* Time is relative — we count heartbeats, not wall-clock milliseconds.
|
||
* This is architecture-independent: works on amd64, aarch64, riscv64
|
||
* without depending on any platform timer.
|
||
* @param vm Pointer to the VM instance
|
||
*/
|
||
void starforth_word_wait(VM* vm)
|
||
{
|
||
if (vm->dsp < 0)
|
||
{
|
||
vm->error = 1;
|
||
log_message(LOG_ERROR, "WAIT: stack underflow");
|
||
return;
|
||
}
|
||
|
||
cell_t ticks = vm_pop(vm);
|
||
cell_t i;
|
||
|
||
if (ticks <= 0)
|
||
return;
|
||
|
||
for (i = 0; i < ticks; i++)
|
||
vm_tick(vm);
|
||
}
|
||
|
||
/**
|
||
* @brief Print StarForth version information (compliance)
|
||
*
|
||
* Prints: StarForth v<version> <architecture> <variant> <timestamp>
|
||
* Stack effect: ( -- )
|
||
* @param vm Pointer to the VM instance
|
||
*/
|
||
void starforth_word_version(VM* vm)
|
||
{
|
||
(void)vm; /* Unused parameter */
|
||
printf("%s\n", STARFORTH_VERSION_FULL);
|
||
}
|
||
|
||
/* ZUSE-AUTHENTICATE ( -- ) Sets zuse_session=1; C-only write; god-mode bypass */
|
||
static void starforth_word_zuse_authenticate(VM *vm)
|
||
{
|
||
vm->zuse_session = 1;
|
||
}
|
||
|
||
/**
|
||
* @brief Register StarForth vocabulary words with the VM
|
||
*
|
||
* Registers all StarForth-specific words and creates the STARFORTH vocabulary
|
||
* @param vm Pointer to the VM instance
|
||
*/
|
||
void register_starforth_words(VM* vm)
|
||
{
|
||
STARFORTH_CHECK_ARENA("register_starforth_words:entry");
|
||
register_word(vm, "WORD-ENTROPY", starforth_word_word_execution_heat);
|
||
register_word(vm, "RESET-ENTROPY", starforth_word_reset_execution_heat);
|
||
register_word(vm, "TOP-WORDS", starforth_word_top_words);
|
||
register_word(vm, "(-", starforth_word_paren_dash);
|
||
register_word(vm, "INIT", starforth_word_init);
|
||
register_word(vm, "VERSION", starforth_word_version);
|
||
register_word(vm, "SEED", starforth_word_seed);
|
||
register_word(vm, "RANDOM", starforth_word_random);
|
||
register_word(vm, "WAIT", starforth_word_wait);
|
||
register_word(vm, "ZUSE-AUTHENTICATE", starforth_word_zuse_authenticate);
|
||
|
||
vm_bootstrap_root_vocabulary(vm, "STARFORTH");
|
||
STARFORTH_CHECK_ARENA("register_starforth_words:post-root");
|
||
|
||
/* Re-register the words in the STARFORTH vocabulary context */
|
||
register_word(vm, "ENTROPY@", starforth_word_execution_heat_fetch);
|
||
register_word(vm, "ENTROPY!", starforth_word_execution_heat_store);
|
||
register_word(vm, "WORD-ENTROPY", starforth_word_word_execution_heat);
|
||
register_word(vm, "RESET-ENTROPY", starforth_word_reset_execution_heat);
|
||
register_word(vm, "TOP-WORDS", starforth_word_top_words);
|
||
register_word(vm, "(-", starforth_word_paren_dash);
|
||
register_word(vm, "INIT", starforth_word_init);
|
||
register_word(vm, "VERSION", starforth_word_version);
|
||
register_word(vm, "SEED", starforth_word_seed);
|
||
register_word(vm, "RANDOM", starforth_word_random);
|
||
register_word(vm, "WAIT", starforth_word_wait);
|
||
register_word(vm, "ZUSE-AUTHENTICATE", starforth_word_zuse_authenticate);
|
||
|
||
vocabulary_word_forth(vm);
|
||
vocabulary_word_definitions(vm);
|
||
STARFORTH_CHECK_ARENA("register_starforth_words:exit");
|
||
}
|