Stage MC-0c: shared VM code made per-VM or atomic (FABRIC-3.10.8 §3.4)

- Dictionary lookup index is per VM (vm->fc_index, struct SfFcIndex):
  was one global index rebuilt whenever a different VM was searched, and
  heat reorganization could reorder another VM's buckets. Allocated on
  first lookup, freed by vm_cleanup(); allocation failure counted/logged,
  lookup falls back to a newest-first dictionary walk.
- PRNG: still one stream, stepped by compare-and-swap; SEED one store.
- state_variable removed (write-only; STATE is vm->mode).
- Sweep additions (Captain Bob's ruling): heartbeat_export.c delta
  last-values moved into each VM's HeartbeatState (were statics shared by
  every VM -- wrong across VMs today); TOP-WORDS list heap-sized per call
  (was static [1000]); inference_outputs_to_string() takes a caller
  buffer; three failure counters atomic; crc64 table published with
  release/acquire.
- Confirmed, unchanged: g_runtime compiled out of the kernel,
  g_hotwords_cache unreferenced, ttf cache core-0-only.
- Probe (removed): every VM has its own index; two VMs searched
  alternately 2x5000 lookups: 0 rebuilds, 0 wrong-VM results, all three
  ISAs.
- Standard sequence on amd64/aarch64/riscv64: dict_hash set unchanged,
  G byte-identical, MINT/WIREBIND/USE ok, conservation true, 0 idle growth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-25 21:05:20 -04:00
co-authored by Claude Opus 5.5
parent 33f4090d6d
commit 355909d46f
28 changed files with 55241 additions and 125 deletions
+7 -3
View File
@@ -140,8 +140,12 @@ static inline uint32_t udiv_floor(uint32_t a, uint32_t b) { return a / b; }
static uint64_t crc64_table[256];
static int crc64_inited = 0;
/* FABRIC-3.10.8 MC-0c: compute_crc64() runs outside the block lock too (the
* log and DoE regions, capsules), so the table is published with release/
* acquire ordering -- a core that sees crc64_inited set also sees the whole
* table. Two cores filling it at once write identical values. */
static void crc64_init(void) {
if (crc64_inited) return;
if (__atomic_load_n(&crc64_inited, __ATOMIC_ACQUIRE)) return;
const uint64_t poly = 0x42F0E1EBA9EA3693ULL;
for (int i = 0; i < 256; ++i) {
uint64_t crc = (uint64_t) i;
@@ -149,11 +153,11 @@ static void crc64_init(void) {
crc = (crc & 1) ? ((crc >> 1) ^ poly) : (crc >> 1);
crc64_table[i] = crc;
}
crc64_inited = 1;
__atomic_store_n(&crc64_inited, 1, __ATOMIC_RELEASE);
}
uint64_t compute_crc64(const uint8_t *data, size_t len) {
if (!crc64_inited) crc64_init();
if (!__atomic_load_n(&crc64_inited, __ATOMIC_ACQUIRE)) crc64_init();
uint64_t crc = 0xFFFFFFFFFFFFFFFFULL;
for (size_t i = 0; i < len; i++) {
uint8_t idx = (uint8_t)(crc ^ data[i]);
+23 -13
View File
@@ -60,7 +60,7 @@
#include "log.h"
#include "platform_time.h"
/* sf_fc_list/sf_fc_count/sf_fc_cap are declared in dictionary_heat_optimization.h */
/* struct SfFcIndex (vm->fc_index, per VM since MC-0c) is declared in dictionary_heat_optimization.h */
/* ============================================================================
* Heat Percentile Calculation
@@ -161,7 +161,7 @@ void dict_update_heat_percentiles(VM *vm) {
/**
* @brief Three-pass heat-stratified dictionary lookup, hottest tier first.
*
* Searches the first-character bucket @c sf_fc_list[(unsigned char)name[0]]
* Searches the first-character bucket @c vm->fc_index->list[(unsigned char)name[0]]
* for a word whose @c name and @c name_len match @p name / @p len, scanning
* in three priority tiers ordered by execution heat:
*
@@ -196,9 +196,15 @@ void dict_update_heat_percentiles(VM *vm) {
DictEntry *dict_find_word_heat_aware(VM *vm, const char *name, size_t len) {
if (!vm || !name || len == 0) return NULL;
/* MC-0c: vm's own index. vm_find_word(), the only caller, has already
* brought it up to date (and walks the dictionary itself if there is
* none). */
struct SfFcIndex *ix = vm->fc_index;
if (!ix) return NULL;
const unsigned char first = (unsigned char)name[0];
DictEntry **bucket = sf_fc_list[first];
size_t n = sf_fc_count[first];
DictEntry **bucket = ix->list[first];
size_t n = ix->count[first];
if (!bucket || n == 0) return NULL;
@@ -249,9 +255,9 @@ static int compare_dict_entry_heat_desc(const void *a, const void *b) {
* Holds @c vm->dict_lock for the entire operation to prevent concurrent
* @c DictEntry frees (from word redefinition or GC) from invalidating the
* pointer array while it is being sorted. For each non-empty bucket in
* @c sf_fc_list[0..255]:
* @c vm->fc_index->list[0..255]:
*
* 1. Reads @c sf_fc_count[bucket_id] and @c sf_fc_cap[bucket_id]; skips the
* 1. Reads the bucket's @c count and @c cap; skips the
* bucket if @c count > @c cap (indicates a TOCTOU race — logs a warning).
* 2. Allocates a temporary copy of the bucket pointer array.
* 3. Re-verifies @c count has not changed (TOCTOU double-check); if it has,
@@ -277,14 +283,18 @@ void dict_reorganize_buckets_by_heat(VM *vm) {
* Without this, main thread can free DictEntry objects while we're sorting pointers */
sf_mutex_lock(&vm->dict_lock);
/* MC-0c: this VM's own buckets -- before, whichever VM's dictionary had
* been searched last. */
struct SfFcIndex *ix = vm->fc_index;
/* Reorganize each bucket by heat (hottest words first) */
for (int bucket_id = 0; bucket_id < 256; bucket_id++) {
if (!sf_fc_list[bucket_id] || sf_fc_count[bucket_id] == 0) continue;
for (int bucket_id = 0; ix && bucket_id < SF_FC_BUCKETS; bucket_id++) {
if (!ix->list[bucket_id] || ix->count[bucket_id] == 0) continue;
/* ASan fix: Verify count doesn't exceed capacity before accessing (2025-12-09)
* Race condition: Main thread may have changed count/capacity while we read it */
size_t count = sf_fc_count[bucket_id];
size_t cap = sf_fc_cap[bucket_id];
size_t count = ix->count[bucket_id];
size_t cap = ix->cap[bucket_id];
if (count > cap) {
log_message(LOG_WARN,
"dict_reorganize_buckets_by_heat: skipping bucket %d - count=%zu exceeds capacity=%zu (race condition)",
@@ -297,7 +307,7 @@ void dict_reorganize_buckets_by_heat(VM *vm) {
if (!bucket_copy) continue;
/* Re-verify count hasn't changed (TOCTOU protection) */
size_t count_recheck = sf_fc_count[bucket_id];
size_t count_recheck = ix->count[bucket_id];
if (count_recheck != count || count_recheck > cap) {
log_message(LOG_WARN,
"dict_reorganize_buckets_by_heat: bucket %d count changed (%zu -> %zu), aborting",
@@ -306,14 +316,14 @@ void dict_reorganize_buckets_by_heat(VM *vm) {
continue;
}
memcpy(bucket_copy, sf_fc_list[bucket_id], count * sizeof(DictEntry *));
memcpy(bucket_copy, ix->list[bucket_id], count * sizeof(DictEntry *));
/* Sort by descending heat (use cached count to avoid TOCTOU race) */
qsort(bucket_copy, count, sizeof(DictEntry *),
compare_dict_entry_heat_desc);
/* Copy back to original bucket (still use cached count) */
memcpy(sf_fc_list[bucket_id], bucket_copy, count * sizeof(DictEntry *));
memcpy(ix->list[bucket_id], bucket_copy, count * sizeof(DictEntry *));
free(bucket_copy);
}
+81 -59
View File
@@ -61,19 +61,10 @@
#endif
/* LIKELY/UNLIKELY macros are defined in vm.h */
#ifndef SF_FC_BUCKETS
#define SF_FC_BUCKETS 256
#endif
/* Buckets of entry pointers, sizes, and capacities (we reuse memory). */
/* Note: sf_fc_list, sf_fc_count, and sf_fc_cap are not static to allow access from
* dictionary_heat_optimization.c for heat-aware bucket reorganization with proper
* capacity checks (ASan fix: 2025-12-09). */
DictEntry **sf_fc_list[SF_FC_BUCKETS];
size_t sf_fc_count[SF_FC_BUCKETS];
size_t sf_fc_cap[SF_FC_BUCKETS];
static DictEntry *sf_cached_latest = NULL; /* last head we indexed */
/* The first-character lookup index is per VM (vm->fc_index, struct SfFcIndex
* in dictionary_heat_optimization.h; FABRIC-3.10.8 Stage MC-0c). Buckets
* reuse their memory across rebuilds. */
static uint32_t g_fc_index_alloc_failures; /* atomic: any core can count one */
/* FABRIC-3.9.0.md §8.1 (Stage S-1b): stable word ids are no longer capped at
* DICTIONARY_SIZE. The id tables grow with the dictionary; a word that can't
@@ -84,7 +75,7 @@ static DictEntry *sf_cached_latest = NULL; /* last head we indexed */
static uint32_t g_word_id_refusals;
uint32_t vm_dictionary_word_id_refusals(void) { return g_word_id_refusals; }
uint32_t vm_dictionary_word_id_refusals(void) { return __atomic_load_n(&g_word_id_refusals, __ATOMIC_RELAXED); }
/* Grow both id tables to at least `need` entries, all-or-nothing: both new
* tables are allocated before either old one is touched, so a failure leaves
@@ -160,7 +151,7 @@ int vm_dictionary_track_entry(VM *vm, DictEntry *entry) {
entry->word_id = word_id;
if (word_id == WORD_ID_INVALID) {
g_word_id_refusals++;
__atomic_add_fetch(&g_word_id_refusals, 1, __ATOMIC_RELAXED); /* MC-0c: any core */
log_message(LOG_ERROR, "word id table can't grow: '%.*s' refused",
(int)entry->name_len, entry->name);
return -1;
@@ -248,21 +239,39 @@ static inline void *sf_xrealloc(void *p, size_t nbytes) {
return r;
}
static void sf_fc_clear_all(void) {
for (size_t i = 0; i < SF_FC_BUCKETS; ++i) {
sf_free(sf_fc_list[i]);
sf_fc_list[i] = NULL;
sf_fc_count[i] = 0;
sf_fc_cap[i] = 0;
struct SfFcIndex *vm_dictionary_index(VM *vm) {
if (!vm) return NULL;
if (!vm->fc_index) {
vm->fc_index = (struct SfFcIndex *) sf_calloc(1, sizeof(struct SfFcIndex));
if (!vm->fc_index) {
/* Lookups stay correct without it (vm_find_word() walks the
* dictionary); counted and logged, retried on the next lookup. */
uint32_t n = __atomic_add_fetch(&g_fc_index_alloc_failures, 1, __ATOMIC_RELAXED);
if (n == 1 || (n & (n - 1)) == 0)
log_message(LOG_ERROR, "dictionary index: can't allocate (x%u) -- "
"lookups walk the dictionary", (unsigned) n);
}
}
return vm->fc_index;
}
uint32_t vm_dictionary_index_alloc_failures(void) {
return __atomic_load_n(&g_fc_index_alloc_failures, __ATOMIC_RELAXED);
}
void vm_dictionary_index_free(VM *vm) {
if (!vm || !vm->fc_index) return;
for (size_t i = 0; i < SF_FC_BUCKETS; ++i) sf_free(vm->fc_index->list[i]);
sf_free(vm->fc_index);
vm->fc_index = NULL;
}
/* Ensure bucket i has at least `need` capacity; grow geometrically. */
static int sf_fc_reserve(size_t i, size_t need) {
if (sf_fc_cap[i] >= need) return 1;
size_t newcap = sf_fc_cap[i] ? sf_fc_cap[i] : 8;
static int sf_fc_reserve(struct SfFcIndex *ix, size_t i, size_t need) {
if (ix->cap[i] >= need) return 1;
size_t newcap = ix->cap[i] ? ix->cap[i] : 8;
while (newcap < need) newcap <<= 1;
DictEntry **old = sf_fc_list[i];
DictEntry **old = ix->list[i];
DictEntry **newv = (DictEntry **) sf_xrealloc(old, newcap * sizeof(*newv));
if (!newv) return 0;
/* Kernel sf_realloc is a bump allocator that does not copy; patch that up.
@@ -270,66 +279,67 @@ static int sf_fc_reserve(size_t i, size_t need) {
* from freed memory (UB; glibc overwrites freed block with heap metadata). */
#ifdef __STARKERNEL__
{
size_t old_count = sf_fc_count[i];
size_t old_count = ix->count[i];
if (newv != old && old && old_count > 0)
memcpy(newv, old, old_count * sizeof(*newv));
}
#endif
sf_fc_list[i] = newv;
sf_fc_cap[i] = newcap;
ix->list[i] = newv;
ix->cap[i] = newcap;
return 1;
}
/* Full rebuild: count → reserve → fill oldest→newest (so backward scan = newest-first). */
static void sf_rebuild_fc_index(VM *vm) {
static void sf_rebuild_fc_index(VM *vm, struct SfFcIndex *ix) {
/* Reset counts, keep capacity to avoid churn. */
for (size_t i = 0; i < SF_FC_BUCKETS; ++i) sf_fc_count[i] = 0;
for (size_t i = 0; i < SF_FC_BUCKETS; ++i) ix->count[i] = 0;
/* Count first chars. */
for (DictEntry *e = vm->latest; e; e = e->link) {
unsigned fc = (unsigned char) e->name[0];
sf_fc_count[fc]++;
ix->count[fc]++;
}
/* Reserve memory once per bucket. */
for (size_t i = 0; i < SF_FC_BUCKETS; ++i) {
if (sf_fc_count[i] && !sf_fc_reserve(i, sf_fc_count[i])) {
if (ix->count[i] && !sf_fc_reserve(ix, i, ix->count[i])) {
/* OOM for this bucket → treat as empty; we’ll just miss the fast path. */
sf_fc_count[i] = 0;
ix->count[i] = 0;
}
}
/* Back-fill so [0]=oldest, [count-1]=newest. */
size_t fill_at[SF_FC_BUCKETS];
for (size_t i = 0; i < SF_FC_BUCKETS; ++i) fill_at[i] = sf_fc_count[i];
for (size_t i = 0; i < SF_FC_BUCKETS; ++i) fill_at[i] = ix->count[i];
for (DictEntry *e = vm->latest; e; e = e->link) {
unsigned fc = (unsigned char) e->name[0];
if (sf_fc_count[fc] == 0 || !sf_fc_list[fc]) continue;
sf_fc_list[fc][--fill_at[fc]] = e;
if (ix->count[fc] == 0 || !ix->list[fc]) continue;
ix->list[fc][--fill_at[fc]] = e;
}
sf_cached_latest = vm->latest;
ix->cached_latest = vm->latest;
ix->rebuilds++;
}
/* Fast path: if exactly one new word was pushed, append it without rebuilding. */
static void sf_try_fast_append(VM *vm) {
if (!vm || vm->latest == sf_cached_latest) return;
static void sf_try_fast_append(VM *vm, struct SfFcIndex *ix) {
if (!vm || vm->latest == ix->cached_latest) return;
DictEntry *newest = vm->latest;
/* single push if the new head links to the previously cached head */
if (newest && newest->link == sf_cached_latest) {
if (newest && newest->link == ix->cached_latest) {
unsigned fc = (unsigned char) newest->name[0];
size_t n = sf_fc_count[fc];
if (sf_fc_reserve(fc, n + 1)) {
size_t n = ix->count[fc];
if (sf_fc_reserve(ix, fc, n + 1)) {
/* Keep oldest→…→newest ordering: append at the end. */
sf_fc_list[fc][n] = newest;
sf_fc_count[fc] = n + 1;
sf_cached_latest = newest;
ix->list[fc][n] = newest;
ix->count[fc] = n + 1;
ix->cached_latest = newest;
return;
}
}
/* Otherwise fall back to a full rebuild (multiple changes, deletes, etc.). */
sf_rebuild_fc_index(vm);
sf_rebuild_fc_index(vm, ix);
}
/* --- API ----------------------------------------------------------------- */
@@ -401,12 +411,32 @@ DictEntry *vm_find_word(VM *vm, const char *name, size_t len) {
/* Thread safety: Acquire dict_lock for read access to dictionary */
sf_mutex_lock(&vm->dict_lock);
/* This VM's own index (MC-0c). Without one (allocation refused, counted
* in vm_dictionary_index()), walk the dictionary newest-first: the
* first visible match is the newest definition. */
struct SfFcIndex *ix = vm_dictionary_index(vm);
if (UNLIKELY(!ix)) {
DictEntry *w;
for (w = vm->latest; w; w = w->link) {
if ((size_t) w->name_len != len || memcmp(w->name, name, len) != 0) continue;
#ifdef WORD_HIDDEN
if (w->flags & WORD_HIDDEN) continue;
#endif
#ifdef WORD_SMUDGED
if (w->flags & WORD_SMUDGED) continue;
#endif
break;
}
sf_mutex_unlock(&vm->dict_lock);
return w;
}
/* Keep the index in sync with dictionary head, cheaply if possible. */
if (UNLIKELY(sf_cached_latest != vm->latest)) sf_try_fast_append(vm);
if (UNLIKELY(ix->cached_latest != vm->latest)) sf_try_fast_append(vm, ix);
const unsigned char first = (unsigned char) name[0];
DictEntry **bucket = sf_fc_list[first];
size_t n = sf_fc_count[first];
DictEntry **bucket = ix->list[first];
size_t n = ix->count[first];
if (UNLIKELY(!bucket || n == 0)) {
sf_mutex_unlock(&vm->dict_lock);
return NULL;
@@ -662,13 +692,5 @@ void vm_unpin_execution_heat(VM *vm) {
physics_metadata_refresh_state(vm->latest);
}
/* If you ever need to hard-reset (e.g., switching vocabularies wholesale) */
/**
* @brief Resets the dictionary index
*
* Clears all index data and cached state. Used when switching vocabularies.
*/
void vm_dictionary_index_reset(void) {
sf_cached_latest = NULL;
sf_fc_clear_all();
}
/* MC-0c: vm_dictionary_index_reset() (the global index's hard reset, no
* callers) is gone; a VM's index is freed with the VM, vm_dictionary_index_free(). */
+12 -13
View File
@@ -70,13 +70,14 @@ void heartbeat_capture_tick_snapshot(VM* vm, HeartbeatTickSnapshot* snapshot)
* raw counter on riscv64). Use nominal tick period instead. */
snapshot->elapsed_ns = hb->tick_count * (uint64_t)HEARTBEAT_TICK_NS;
static uint64_t last_tick_count = 0;
uint64_t delta_ticks = hb->tick_count - last_tick_count;
uint64_t delta_ticks = hb->tick_count - hb->export_last_tick_count;
snapshot->tick_interval_ns = delta_ticks * (uint64_t)HEARTBEAT_TICK_NS;
last_tick_count = hb->tick_count;
hb->export_last_tick_count = hb->tick_count;
/* Delta metrics — computed from per-VM counters using static last-value tracking.
* On VM reset (current < last), treat current value as the full delta. */
/* Delta metrics — computed from per-VM counters against this VM's own
* previous values (hb->export_last_*; FABRIC-3.10.8 MC-0c -- were statics
* shared by every VM). On VM reset (current < last), treat current value
* as the full delta. */
/* Cache metrics from hotwords cache */
snapshot->cache_hits_delta = 0; /* TODO: wire hotwords cache hit counter */
@@ -85,12 +86,11 @@ void heartbeat_capture_tick_snapshot(VM* vm, HeartbeatTickSnapshot* snapshot)
/* Word execution delta — words executed since the previous heartbeat tick.
* vm->heartbeat.words_executed is incremented by vm_core.c on every dispatch. */
{
static uint64_t last_words_executed = 0;
uint64_t cur = hb->words_executed;
snapshot->word_executions_delta = (uint32_t)(cur >= last_words_executed
? cur - last_words_executed
snapshot->word_executions_delta = (uint32_t)(cur >= hb->export_last_words_executed
? cur - hb->export_last_words_executed
: cur);
last_words_executed = cur;
hb->export_last_words_executed = cur;
}
/* Hot word count - walk dictionary and count words above threshold */
@@ -141,12 +141,11 @@ void heartbeat_capture_tick_snapshot(VM* vm, HeartbeatTickSnapshot* snapshot)
* High rate = selector chose well and the system settled.
* Low rate = selector is still searching. */
{
static uint64_t last_early_exit_count = 0;
uint64_t cur = hb->early_exit_count;
snapshot->predicted_label_hits = (uint32_t)(cur >= last_early_exit_count
? cur - last_early_exit_count
snapshot->predicted_label_hits = (uint32_t)(cur >= hb->export_last_early_exit_count
? cur - hb->export_last_early_exit_count
: cur);
last_early_exit_count = cur;
hb->export_last_early_exit_count = cur;
}
/* Jitter: tick-based timing has no wall-clock deviation to measure.
+9 -6
View File
@@ -756,18 +756,21 @@ void inference_engine_run(InferenceInputs *inputs, InferenceOutputs *outputs)
/**
* @brief Format inference outputs as a human-readable string.
*
* Writes into a static 256-byte buffer — not thread-safe, for diagnostics only.
* Writes into the caller's buffer (FABRIC-3.10.8 MC-0c: was a static
* 256-byte buffer shared by every VM and core).
* Format: @c "window=N var=F slope=F quality=F (cached|full)".
*
* @param outputs Inference outputs to format (NULL produces @c "(null)")
* @return Pointer to a static string buffer
* @param buf Destination (256 bytes holds any output)
* @param cap Size of @p buf
* @return @p buf, or "" if @p buf is NULL or @p cap is 0
*/
const char* inference_outputs_to_string(const InferenceOutputs *outputs)
const char* inference_outputs_to_string(const InferenceOutputs *outputs, char *buf, size_t cap)
{
static char buf[256];
if (!buf || cap == 0) return "";
if (!outputs) {
snprintf(buf, sizeof(buf), "(null)");
snprintf(buf, cap, "(null)");
return buf;
}
@@ -775,7 +778,7 @@ const char* inference_outputs_to_string(const InferenceOutputs *outputs)
double slope_dbl = q48_to_double(outputs->adaptive_decay_slope);
double quality_dbl = q48_to_double(outputs->slope_fit_quality_q48);
snprintf(buf, sizeof(buf),
snprintf(buf, cap,
"window=%u var=%.6f slope=%.6f quality=%.6f %s",
outputs->adaptive_window_width,
var_dbl,
+2 -2
View File
@@ -212,7 +212,7 @@ int transition_metrics_init(WordTransitionMetrics *metrics) {
*/
static uint64_t g_transition_grow_failures;
uint64_t transition_metrics_grow_failures(void) { return g_transition_grow_failures; }
uint64_t transition_metrics_grow_failures(void) { return __atomic_load_n(&g_transition_grow_failures, __ATOMIC_RELAXED); }
int transition_metrics_record(WordTransitionMetrics *metrics, uint32_t next_word_id, uint32_t dict_size) {
if (!metrics || next_word_id >= dict_size) {
@@ -234,7 +234,7 @@ int transition_metrics_record(WordTransitionMetrics *metrics, uint32_t next_word
}
grown = (uint64_t *)calloc(new_cap, sizeof(uint64_t));
if (!grown) {
g_transition_grow_failures++;
__atomic_add_fetch(&g_transition_grow_failures, 1, __ATOMIC_RELAXED); /* MC-0c: any core */
log_message(LOG_ERROR, "transition_heat can't grow: dropped");
return -1;
}
+3
View File
@@ -312,6 +312,9 @@ void vm_cleanup(VM* vm)
vm->hotwords_cache = NULL;
}
/* This VM's dictionary lookup index (FABRIC-3.10.8 MC-0c) */
vm_dictionary_index_free(vm);
/* Clean up rolling window of truth */
rolling_window_cleanup(&vm->rolling_window);
+3
View File
@@ -394,6 +394,9 @@ void vm_cleanup(VM* vm)
vm->hotwords_cache = NULL;
}
/* This VM's dictionary lookup index (FABRIC-3.10.8 MC-0c) */
vm_dictionary_index_free(vm);
/* Clean up rolling window of truth */
rolling_window_cleanup(&vm->rolling_window);
@@ -45,8 +45,9 @@
#include "../../include/word_registry.h"
#include "../../include/log.h"
/* Global state for dictionary manipulation */
static cell_t state_variable = 0; /* STATE - compilation state */
/* FABRIC-3.10.8 MC-0c: the file-global state_variable ("STATE") that lived
* here is gone. It was written (INTERPRET) and never read -- STATE is
* vm->mode, already per VM. */
/* Helper function to calculate aligned address */
static uintptr_t align_address(uintptr_t addr) {
@@ -360,7 +361,6 @@ void dictionary_m_word_interpret(VM *vm) {
/* Set interpretation mode */
vm->mode = MODE_INTERPRET;
state_variable = 0;
/* The actual interpretation happens through vm_interpret() calls */
/* This word exists mainly for completeness and mode setting */
@@ -60,9 +60,6 @@
#include "platform_time.h"
#include "word_registry.h"
/* Forward declarations */
extern DictEntry **sf_fc_list[256];
extern size_t sf_fc_count[256];
/* ============================================================================
* Benchmark: Dictionary Lookup Performance
+41 -13
View File
@@ -54,6 +54,7 @@
#include "../../include/platform_time.h"
#include "../../include/profiler.h"
#include "../../include/block_subsystem.h"
#include "../../include/platform_alloc.h" /* sf_malloc()/sf_free() -- TOP-WORDS (MC-0c) */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
@@ -228,17 +229,33 @@ void starforth_word_top_words(VM* vm)
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];
/* Collect all words with execution_heat > 0. FABRIC-3.10.8 MC-0c: sized
* to this dictionary and allocated per call -- was a static [1000]
* (shared by every VM, and silently cut at 1000 words). */
int word_count = 0;
for (DictEntry* entry = vm->latest; entry && word_count < 1000; entry = entry->link)
for (DictEntry* entry = vm->latest; entry; entry = entry->link)
{
if (entry->execution_heat > 0)
if (entry->execution_heat > 0) word_count++;
}
DictEntry** words_with_heat = NULL;
if (word_count > 0)
{
words_with_heat = (DictEntry**)sf_malloc((size_t)word_count * sizeof(DictEntry*));
if (!words_with_heat)
{
words_with_heat[word_count++] = entry;
vm->error = 1;
log_message(LOG_ERROR, "TOP-WORDS: can't allocate a list of %d words", word_count);
return;
}
}
{
int k = 0;
for (DictEntry* entry = vm->latest; entry && k < word_count; entry = entry->link)
{
if (entry->execution_heat > 0) words_with_heat[k++] = entry;
}
word_count = k;
}
/* Simple bubble sort by execution_heat (descending) */
for (int i = 0; i < word_count - 1; i++)
@@ -261,6 +278,7 @@ void starforth_word_top_words(VM* vm)
DictEntry* entry = words_with_heat[i];
printf("%d. %.*s: %ld\n", i + 1, (int)entry->name_len, entry->name, (long)entry->execution_heat);
}
sf_free(words_with_heat);
}
/**
@@ -653,9 +671,18 @@ void starforth_word_init(VM* vm)
*/
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;
/* LCG: state = (a * state + c) mod m, where m = 2^64 (implicit).
* FABRIC-3.10.8 MC-0c: one stream for every VM (as accepted at Stage H),
* stepped with compare-and-swap so two cores never draw the same value
* or lose a step. Which core draws which value is ordering, not
* physics (MC-R4). */
uint64_t cur = __atomic_load_n(&g_prng_state, __ATOMIC_RELAXED);
uint64_t next;
do {
next = cur * 6364136223846793005ULL + 1442695040888963407ULL;
} while (!__atomic_compare_exchange_n(&g_prng_state, &cur, next, 0,
__ATOMIC_RELAXED, __ATOMIC_RELAXED));
return next;
}
/**
@@ -675,15 +702,16 @@ void starforth_word_seed(VM* vm)
}
cell_t seed = vm_pop(vm);
g_prng_state = (uint64_t)seed;
uint64_t s = (uint64_t)seed;
/* Ensure non-zero state (LCG weakness) */
if (g_prng_state == 0)
if (s == 0)
{
g_prng_state = 1;
s = 1;
}
__atomic_store_n(&g_prng_state, s, __ATOMIC_RELAXED); /* MC-0c: one store, not two */
log_message(LOG_DEBUG, "SEED: PRNG seeded with %lu", (unsigned long)g_prng_state);
log_message(LOG_DEBUG, "SEED: PRNG seeded with %lu", (unsigned long)s);
}
/*
+2 -2
View File
@@ -80,7 +80,7 @@ static inline int ensure_input(VM *vm) {
* counted here -- never silently truncated. */
static uint32_t g_word_too_long;
uint32_t string_word_word_refusals(void) { return g_word_too_long; }
uint32_t string_word_word_refusals(void) { return __atomic_load_n(&g_word_too_long, __ATOMIC_RELAXED); }
/* Helper function to convert string to number (base-10 only here) */
static int convert_string_to_number(const char *str, size_t len, cell_t *result) {
@@ -254,7 +254,7 @@ void string_word_word(VM *vm) {
if (word_len > WORD_BUFFER_SIZE - 2) {
/* Refused whole: the delimiter goes back on the stack and >IN is
* left where it was, so nothing is consumed. */
g_word_too_long++;
__atomic_add_fetch(&g_word_too_long, 1, __ATOMIC_RELAXED); /* MC-0c: any core */
log_message(LOG_ERROR, "WORD: token over 255 chars refused");
vm_push(vm, c);
vm->error = 1;