Stage S-1b: dictionary word ids grow with the dictionary; a word that can't get an id is refused whole (FABRIC-3.9.0 §8.1)
- word_id_map / recycled_word_ids are heap tables (one shared capacity, 64 doubling), grown all-or-nothing by allocate-copy-free. Numbering is unchanged, so boot parity is unchanged. - vm_create_word() takes the id before linking; on failure it frees the entry, sets vm->error, logs LOG_ERROR and counts (vm_dictionary_word_id_refusals()). No word runs with WORD_ID_INVALID. - Readers of the id space bound by vm->next_word_id, not DICTIONARY_SIZE. - transition_heat gets its own capacity and grows on record (was sized once -- a heap overflow under a dynamic bound); every reader bounds by it. - Stadium id->cell rows start empty and grow on dispatch; a failed grow is logged, counted and not admitted. S-2 (the cell pool) stays open. Probe (reverted), identical on amd64/aarch64/riscv64: +2000 words get real ids (capacity 4096); a word at id 2738 lands 200x in the rolling window and is Stadium-admitted; FORGET recycles across the old boundary; an injected grow failure refuses the word whole (NULL, error, counter, dictionary unchanged) and the retry succeeds. 3-ISA acceptance passes; boot parity (531, 0x572daac83b797332) and the dict_hash set unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ee3db96ea8
commit
1dbd0c74ed
@@ -52,6 +52,7 @@
|
||||
|
||||
#include "physics_pipelining_metrics.h"
|
||||
#include "vm.h"
|
||||
#include "log.h" /* S-1b: a failed transition_heat grow is logged */
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
@@ -149,6 +150,7 @@ int transition_metrics_init(WordTransitionMetrics *metrics) {
|
||||
if (!metrics) return -1;
|
||||
|
||||
metrics->transition_heat = NULL; /* Lazy allocation on first use */
|
||||
metrics->transition_heat_cap = 0;
|
||||
metrics->total_transitions = 0;
|
||||
metrics->prefetch_attempts = 0;
|
||||
metrics->prefetch_hits = 0;
|
||||
@@ -208,17 +210,41 @@ int transition_metrics_init(WordTransitionMetrics *metrics) {
|
||||
* @return 0 on success; −1 on NULL @p metrics, out-of-bounds
|
||||
* @p next_word_id, or allocation failure.
|
||||
*/
|
||||
static uint64_t g_transition_grow_failures;
|
||||
|
||||
uint64_t transition_metrics_grow_failures(void) { return g_transition_grow_failures; }
|
||||
|
||||
int transition_metrics_record(WordTransitionMetrics *metrics, uint32_t next_word_id, uint32_t dict_size) {
|
||||
if (!metrics || next_word_id >= dict_size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Lazy allocation: only allocate transition_heat array when first transition occurs */
|
||||
if (!metrics->transition_heat) {
|
||||
metrics->transition_heat = (uint64_t *)calloc(dict_size, sizeof(uint64_t));
|
||||
if (!metrics->transition_heat) {
|
||||
return -1; /* Memory allocation failure */
|
||||
/* Lazy allocation on the first transition, and growth when a word id at
|
||||
* or past the current length arrives (S-1b: ids are no longer capped, so
|
||||
* an array sized once can be outgrown). Grown to the VM's whole id bound,
|
||||
* at least doubling, so a steady stream of new words doesn't regrow on
|
||||
* each one. A failed grow drops this one transition -- the word itself
|
||||
* already ran; there is nothing to refuse -- and is logged and counted. */
|
||||
if (next_word_id >= metrics->transition_heat_cap) {
|
||||
uint32_t new_cap = dict_size;
|
||||
uint64_t *grown;
|
||||
if (metrics->transition_heat_cap > new_cap / 2u) {
|
||||
new_cap = (metrics->transition_heat_cap > UINT32_MAX / 2u)
|
||||
? UINT32_MAX : metrics->transition_heat_cap * 2u;
|
||||
}
|
||||
grown = (uint64_t *)calloc(new_cap, sizeof(uint64_t));
|
||||
if (!grown) {
|
||||
g_transition_grow_failures++;
|
||||
log_message(LOG_ERROR, "transition_heat can't grow: dropped");
|
||||
return -1;
|
||||
}
|
||||
if (metrics->transition_heat) {
|
||||
memcpy(grown, metrics->transition_heat,
|
||||
(size_t)metrics->transition_heat_cap * sizeof(uint64_t));
|
||||
free(metrics->transition_heat);
|
||||
}
|
||||
metrics->transition_heat = grown;
|
||||
metrics->transition_heat_cap = new_cap;
|
||||
}
|
||||
|
||||
/* Record the transition */
|
||||
@@ -265,7 +291,7 @@ int64_t transition_metrics_get_probability_q48(const WordTransitionMetrics *metr
|
||||
/* Safety: Bounds check on target_word_id to prevent out-of-bounds read
|
||||
* Note: transition_heat is allocated with dict_size passed to transition_metrics_record()
|
||||
* Callers must ensure target_word_id < dict_size used during allocation */
|
||||
if (!metrics->transition_heat) {
|
||||
if (!metrics->transition_heat || target_word_id >= metrics->transition_heat_cap) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -311,8 +337,10 @@ void transition_metrics_update_cache(WordTransitionMetrics *metrics, uint32_t di
|
||||
int64_t max_prob_q48 = 0;
|
||||
uint32_t max_idx = 0;
|
||||
|
||||
/* Find word with highest transition count */
|
||||
for (uint32_t i = 0; i < dict_size; i++) {
|
||||
/* Find word with highest transition count. Bounded by the array's own
|
||||
* length (S-1b): it may lag dict_size until the next grow. */
|
||||
uint32_t limit = (dict_size < metrics->transition_heat_cap) ? dict_size : metrics->transition_heat_cap;
|
||||
for (uint32_t i = 0; i < limit; i++) {
|
||||
if (metrics->transition_heat[i] > 0) {
|
||||
int64_t prob_q48 = transition_metrics_get_probability_q48(metrics, i);
|
||||
if (prob_q48 > max_prob_q48) {
|
||||
@@ -511,6 +539,7 @@ void transition_metrics_reset(WordTransitionMetrics *metrics) {
|
||||
free(metrics->transition_heat);
|
||||
metrics->transition_heat = NULL;
|
||||
}
|
||||
metrics->transition_heat_cap = 0;
|
||||
|
||||
metrics->total_transitions = 0;
|
||||
metrics->prefetch_attempts = 0;
|
||||
@@ -559,6 +588,7 @@ void transition_metrics_cleanup(WordTransitionMetrics *metrics) {
|
||||
free(metrics->transition_heat);
|
||||
metrics->transition_heat = NULL;
|
||||
}
|
||||
metrics->transition_heat_cap = 0;
|
||||
|
||||
/* Phase 1 Extension: Clean up context window */
|
||||
if (metrics->context_window) {
|
||||
|
||||
Reference in New Issue
Block a user