Files
LithosAnanake/src/word_source/physics_freeze_words.c
T
Robert Allan JamesandClaude Sonnet 5 abb858a300 Add POST coverage for physics-freeze words (Module 27), fix two real bugs found in the process
Cluster 4 of the POST-coverage sweep: physics_freeze_words_test.c covers the 6
words proof/StarForth_Physics_Freeze_Words.thy actually gives real lemmas for
(FREEZE-WORD, UNFREEZE-WORD, FROZEN?, HEAT!, HEAT@, DECAY-RATE@), correcting
an earlier fork summary's wrong "5 words" scope.

Writing the tests surfaced two independent, pre-existing bugs in
physics_freeze_words.c, both now fixed:

- Every address-taking word cast the VM's caddr directly to a host pointer
  instead of resolving it through vm_ptr() -- caddr is an offset into
  vm->memory, not a host pointer. Fixed in all 9 call sites (the 5 in-scope
  words plus SHOW-HEAT, which shares the identical pattern).
- Every underflow check used dsp < N (item count) instead of dsp < N-1, since
  this VM's dsp is a 0-indexed top-of-stack pointer. Fixed in all 6 checks.

Together these meant every word in this file taking a stack-supplied name has
been broken for any real caller since the file was written. Verified zero
build warnings and a clean three-arch QEMU boot (amd64/aarch64/riscv64), 1009
passed / 0 failed / 0 errors identically on all three, dict_hash matching
across arches.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 00:12:09 -04:00

471 lines
14 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.
*/
/*
*** StarForth ***
physics_freeze_words.c - Phase 2 Freeze/Decay Control Words
Implements the FORTH interface for Phase 2 physics model enhancements:
- FREEZE-WORD: Lock a word's execution heat (prevents decay)
- UNFREEZE-WORD: Allow heat decay to resume
- FROZEN?: Query freeze status
- HEAT!: Set execution heat manually (diagnostics)
- HEAT@: Read current execution heat
- SHOW-HEAT: Display heat for a single word
- ALL-HEATS: Display heat for all words in dictionary
Semantic Note:
- WORD_FROZEN (0x04) prevents heat decay via physics_metadata_apply_linear_decay()
- Works independently from WORD_PINNED (0x08) which locks heat at maximum
- FROZEN words maintain their heat indefinitely across OS context switches
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "vm.h"
#include "physics_metadata.h"
#include "word_registry.h"
/**
* @brief FREEZE-WORD ( caddr u -- )
*
* Set the @c WORD_FROZEN flag on the named word, preventing @c physics_metadata_apply_linear_decay()
* from reducing its @c execution_heat. Frozen words stay hot across OS context switches indefinitely.
* Silently succeeds if the word is not found (lenient policy).
*
* Stack effect: ( caddr u -- )
*
* @param vm Active VM; @c vm->error is set and returns early on underflow
*/
void forth_FREEZE_WORD(VM *vm) {
if (vm->error) return;
/* Stack: ( caddr u -- ) -- dsp is a 0-indexed top-of-stack pointer,
* so 2 items on stack means dsp >= 1, not dsp >= 2. */
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t len = vm->data_stack[--vm->dsp];
cell_t caddr = vm->data_stack[--vm->dsp];
if (len <= 0 || len > WORD_NAME_MAX) {
return; /* Silently fail on invalid length */
}
/* caddr is a VM memory offset, not a host pointer -- resolve via vm_ptr() */
const char *name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)caddr);
if (!name) return;
/* Look up the word */
DictEntry *entry = vm_find_word(vm, name, (size_t)len);
if (entry) {
entry->flags |= WORD_FROZEN;
}
/* Silently succeed even if word not found (lenient) */
}
/**
* @brief UNFREEZE-WORD ( caddr u -- )
*
* Clears the @c WORD_FROZEN flag on the named word, allowing its @c execution_heat
* to decay normally via Loop #3. Does not alter @c WORD_PINNED. Silently succeeds
* if the word is not found or @c u is out of range.
*
* Stack effect: ( caddr u -- )
*
* @param vm Active VM; @c vm->error is set and returns early on underflow
*/
void forth_UNFREEZE_WORD(VM *vm) {
if (vm->error) return;
/* Stack: ( caddr u -- ) -- dsp is a 0-indexed top-of-stack pointer,
* so 2 items on stack means dsp >= 1, not dsp >= 2. */
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t len = vm->data_stack[--vm->dsp];
cell_t caddr = vm->data_stack[--vm->dsp];
if (len <= 0 || len > WORD_NAME_MAX) {
return;
}
const char *name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)caddr);
if (!name) return;
DictEntry *entry = vm_find_word(vm, name, (size_t)len);
if (entry) {
entry->flags &= ~WORD_FROZEN;
}
}
/**
* @brief FROZEN? ( caddr u -- flag )
*
* Tests whether the @c WORD_FROZEN flag is set on the named word. Pushes
* -1 (FORTH true) if frozen, 0 otherwise. Also pushes 0 when the word is
* not found or @c u is out of range, so callers need not distinguish "not frozen"
* from "not found".
*
* Stack effect: ( caddr u -- flag )
*
* @param vm Active VM; @c vm->error is set and returns early on underflow
*/
void forth_FROZEN_QUERY(VM *vm) {
if (vm->error) return;
/* Stack: ( caddr u -- flag ) -- dsp is a 0-indexed top-of-stack pointer,
* so 2 items on stack means dsp >= 1, not dsp >= 2. */
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t len = vm->data_stack[--vm->dsp];
cell_t caddr = vm->data_stack[--vm->dsp];
if (len <= 0 || len > WORD_NAME_MAX) {
vm->data_stack[vm->dsp++] = 0; /* Not frozen */
return;
}
const char *name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)caddr);
DictEntry *entry = name ? vm_find_word(vm, name, (size_t)len) : NULL;
if (entry && (entry->flags & WORD_FROZEN)) {
vm->data_stack[vm->dsp++] = -1; /* True (frozen) */
} else {
vm->data_stack[vm->dsp++] = 0; /* False (not frozen) */
}
}
/**
* @brief HEAT! ( heat caddr u -- )
*
* Directly writes @c heat into @c entry->execution_heat for the named word.
* Intended for diagnostics and testing; bypasses Loop #1 accumulation and
* Loop #3 decay. Silently succeeds if the word is not found or @c u is out
* of range.
*
* Stack effect: ( heat caddr u -- )
*
* @param vm Active VM; @c vm->error is set and returns early on underflow
*/
void forth_HEAT_STORE(VM *vm) {
if (vm->error) return;
/* Stack: ( heat caddr u -- ) -- dsp is a 0-indexed top-of-stack pointer,
* so 3 items on stack means dsp >= 2, not dsp >= 3. */
if (vm->dsp < 2) {
vm->error = 1;
return;
}
cell_t len = vm->data_stack[--vm->dsp];
cell_t caddr = vm->data_stack[--vm->dsp];
cell_t heat = vm->data_stack[--vm->dsp];
if (len <= 0 || len > WORD_NAME_MAX) {
return;
}
const char *name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)caddr);
if (!name) return;
DictEntry *entry = vm_find_word(vm, name, (size_t)len);
if (entry) {
entry->execution_heat = heat;
}
}
/**
* @brief HEAT@ ( caddr u -- heat )
*
* Pushes the current @c execution_heat of the named word. Pushes 0 when the
* word is not found, @c u is out of range, or the word genuinely has zero heat.
*
* Stack effect: ( caddr u -- heat )
*
* @param vm Active VM; @c vm->error is set and returns early on underflow
*/
void forth_HEAT_FETCH(VM *vm) {
if (vm->error) return;
/* Stack: ( caddr u -- heat ) -- dsp is a 0-indexed top-of-stack pointer,
* so 2 items on stack means dsp >= 1, not dsp >= 2. */
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t len = vm->data_stack[--vm->dsp];
cell_t caddr = vm->data_stack[--vm->dsp];
if (len <= 0 || len > WORD_NAME_MAX) {
vm->data_stack[vm->dsp++] = 0;
return;
}
const char *name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)caddr);
DictEntry *entry = name ? vm_find_word(vm, name, (size_t)len) : NULL;
if (entry) {
vm->data_stack[vm->dsp++] = entry->execution_heat;
} else {
vm->data_stack[vm->dsp++] = 0;
}
}
/**
* @brief SHOW-HEAT ( caddr u -- )
*
* Prints the @c execution_heat of the named word to @c stdout along with its
* freeze and pinned status flags. Output format: @c "NAME: HEAT (frozen) (pinned)".
* Prints @c "Word not found: NAME" when the dictionary lookup fails. Intended for
* interactive diagnostics.
*
* Stack effect: ( caddr u -- )
*
* @param vm Active VM; @c vm->error is set and returns early on underflow
*/
void forth_SHOW_HEAT(VM *vm) {
if (vm->error) return;
/* Stack: ( caddr u -- ) -- dsp is a 0-indexed top-of-stack pointer,
* so 2 items on stack means dsp >= 1, not dsp >= 2. */
if (vm->dsp < 1) {
vm->error = 1;
return;
}
cell_t len = vm->data_stack[--vm->dsp];
cell_t caddr = vm->data_stack[--vm->dsp];
if (len <= 0 || len > WORD_NAME_MAX) {
return;
}
const char *name = (const char *)vm_ptr(vm, (vaddr_t)(uint64_t)caddr);
if (!name) return;
DictEntry *entry = vm_find_word(vm, name, (size_t)len);
if (entry) {
printf("%.*s: %ld", (int)len, name, entry->execution_heat);
if (entry->flags & WORD_FROZEN) {
printf(" (frozen)");
}
if (entry->flags & WORD_PINNED) {
printf(" (pinned)");
}
printf("\n");
} else {
printf("Word not found: %.*s\n", (int)len, name);
}
}
/**
* @brief ALL-HEATS ( -- )
*
* Iterates the entire dictionary, collects up to 1024 entries, sorts them by
* descending @c execution_heat using an O(n²) bubble sort, then prints a
* formatted table to @c stdout with columns for word name, heat value, and
* status (frozen/pinned). Intended for interactive diagnostics; the O(n²) sort
* is acceptable for dictionary sizes below 1000 words.
*
* Stack effect: ( -- )
*
* @param vm Active VM; no-op if @c vm->error is set
*/
void forth_ALL_HEATS(VM *vm) {
if (vm->error) return;
printf("\n=== Execution Heat (Phase 2) ===\n");
printf("%-16s %10s %s\n", "Word", "Heat", "Status");
printf("%-16s %10s %s\n", "----", "----", "------");
/* Simple O(n²) bubble sort by heat (acceptable for <1000 words) */
int count = 0;
DictEntry *entries[1024];
/* Collect all entries */
for (DictEntry *w = vm->latest; w && count < 1024; w = w->link) {
entries[count++] = w;
}
/* Sort by descending heat */
for (int i = 0; i < count - 1; i++) {
for (int j = 0; j < count - i - 1; j++) {
if (entries[j]->execution_heat < entries[j + 1]->execution_heat) {
DictEntry *tmp = entries[j];
entries[j] = entries[j + 1];
entries[j + 1] = tmp;
}
}
}
/* Display sorted by heat */
for (int i = 0; i < count; i++) {
DictEntry *w = entries[i];
printf("%-16.*s %10ld", w->name_len, w->name, w->execution_heat);
const char *status = "";
if (w->flags & WORD_FROZEN) {
status = " (frozen)";
} else if (w->flags & WORD_PINNED) {
status = " (pinned)";
}
printf("%s\n", status);
}
printf("\n");
}
/**
* @brief DECAY-RATE@ ( -- rate )
*
* Pushes the compile-time constant @c DECAY_RATE_PER_US_Q16 as a Q48.16
* fixed-point value. This is the base heat decay rate per microsecond used
* by Loop #3 before adaptive tuning by Loop #6. Intended for introspection
* and diagnostics.
*
* Stack effect: ( -- rate )
*
* @param vm Active VM; sets @c vm->error = 1 on stack overflow
*/
void forth_DECAY_RATE_FETCH(VM *vm) {
if (vm->error) return;
if (vm->dsp >= STACK_SIZE) {
vm->error = 1;
return;
}
/* Stack: ( -- rate ) */
vm->data_stack[vm->dsp++] = (cell_t)DECAY_RATE_PER_US_Q16;
}
/**
* @brief FREEZE-CRITICAL ( -- )
*
* Sets @c WORD_FROZEN on a hard-coded list of 21 system-critical words
* (DUP, DROP, SWAP, OVER, ROT, @, !, C@, C!, EXECUTE, IF, THEN, ELSE, DO,
* LOOP, BEGIN, UNTIL, REPEAT, ., EMIT, CR). Silently skips any word not found
* in the current dictionary. Intended to be called once at startup to prevent
* essential words from decaying out of the hot-words cache.
*
* Stack effect: ( -- )
*
* @param vm Active VM; no-op if @c vm->error is set
*/
void forth_FREEZE_CRITICAL(VM *vm) {
if (vm->error) return;
/* List of critical words that should never decay */
const char *critical_words[] = {
"DUP",
"DROP",
"SWAP",
"OVER",
"ROT",
"@",
"!",
"C@",
"C!",
"EXECUTE",
"IF",
"THEN",
"ELSE",
"DO",
"LOOP",
"BEGIN",
"UNTIL",
"REPEAT",
".",
"EMIT",
"CR",
NULL
};
for (int i = 0; critical_words[i] != NULL; i++) {
const char *name = critical_words[i];
size_t len = strlen(name);
DictEntry *entry = vm_find_word(vm, name, len);
if (entry) {
entry->flags |= WORD_FROZEN;
}
}
}
/**
* @brief Register all Phase 2 freeze/decay control words with the VM dictionary.
*
* Registers: @c FREEZE-WORD, @c UNFREEZE-WORD, @c FROZEN?, @c HEAT!,
* @c HEAT@, @c SHOW-HEAT, @c ALL-HEATS, @c DECAY-RATE@, @c FREEZE-CRITICAL.
* Called during VM bootstrap by the word registration subsystem.
*
* @param vm Active VM to register words into
*/
void register_physics_freeze_words(VM *vm) {
register_word(vm, "FREEZE-WORD", forth_FREEZE_WORD);
register_word(vm, "UNFREEZE-WORD", forth_UNFREEZE_WORD);
register_word(vm, "FROZEN?", forth_FROZEN_QUERY);
register_word(vm, "HEAT!", forth_HEAT_STORE);
register_word(vm, "HEAT@", forth_HEAT_FETCH);
register_word(vm, "SHOW-HEAT", forth_SHOW_HEAT);
register_word(vm, "ALL-HEATS", forth_ALL_HEATS);
register_word(vm, "DECAY-RATE@", forth_DECAY_RATE_FETCH);
register_word(vm, "FREEZE-CRITICAL", forth_FREEZE_CRITICAL);
}