Stage K-2: COLD forgets to the fence; PAD moved below the block window (FABRIC-3.10.8 §4.2)

- Defect 1: COLD now forgets every word since the fence exactly as FORGET
  does (new vm_dictionary_forget_to_fence(): untrack, metrics, entry) and
  rewinds HERE to the fence. It rewound HERE alone to 1024, leaving live
  words to be compiled over.
- Defect 2: PAD = BLK_VM_WINDOW_BASE - 512 (was inside the window's top
  slot, shared with BLOCK/BUFFER).
- Probe (plain FORTH, all three ISAs identical): PAD..PAD+511 =
  5238272..5238783 below the window (5238784); on a worker KA/KC found,
  COLD, then KA/KB/KC not found, HERE at the fence (1424), new word works.
- Standard sequence on amd64/aarch64/riscv64: hash and G unchanged,
  MINT/WIREBIND/USE ok, conserved, 0 idle growth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-26 03:13:21 -04:00
co-authored by Claude Opus 5.5
parent 27c430e0ca
commit c9c97cf769
11 changed files with 27609 additions and 14 deletions
+30
View File
@@ -692,3 +692,33 @@ void vm_unpin_execution_heat(VM *vm) {
/* 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(). */
/* FABRIC-3.10.8 K-2: forget everything defined since the fence, exactly as
* FORGET removes words (untrack -- id recycled, Stadium cell reclaimed --
* then metrics, then the entry), and rewind HERE to the fence. COLD's
* dictionary half: it used to rewind HERE alone, leaving `latest` pointing
* at words whose code HERE would then compile over. Returns the number of
* words forgotten. */
int vm_dictionary_forget_to_fence(VM *vm) {
int n = 0;
DictEntry *e;
if (!vm) return 0;
sf_mutex_lock(&vm->dict_lock);
e = vm->latest;
while (e && e != vm->dict_fence_latest) {
DictEntry *next = e->link;
vm_dictionary_untrack_entry(vm, e);
if (e->transition_metrics) {
transition_metrics_cleanup(e->transition_metrics);
sf_free(e->transition_metrics);
e->transition_metrics = NULL;
}
sf_free(e);
n++;
e = next;
}
vm->latest = vm->dict_fence_latest;
vm->here = vm->dict_fence_here;
sf_mutex_unlock(&vm->dict_lock);
return n;
}
+6 -2
View File
@@ -161,10 +161,14 @@ void dictionary_word_2comma(VM *vm) {
/**
* @brief PAD ( -- addr ) Get scratch pad buffer address
* @param vm Pointer to the VM structure
* @details Returns VM address of 512-byte temporary text buffer at top of memory
* @details Returns VM address of the 512-byte temporary text buffer: the
* 512 bytes just below the block I/O window. FABRIC-3.10.8 K-2: it used to
* be VM_MEMORY_SIZE - 512, inside the window's top slot, so BLOCK/BUFFER
* and PAD overwrote each other. The span between the dictionary limit
* (DICTIONARY_MEMORY_SIZE) and the window is otherwise unused.
*/
void dictionary_word_pad(VM *vm) {
size_t pad_offset = VM_MEMORY_SIZE - 512;
size_t pad_offset = BLK_VM_WINDOW_BASE - 512;
vm_push(vm, (cell_t) pad_offset);
}
+8 -12
View File
@@ -88,18 +88,14 @@ static void reset_vm_state(VM *vm, int cold_start) {
vm->mode = MODE_INTERPRET;
if (cold_start) {
/* In a complete system, this would restore a boot image.
For now we keep a minimal fence by not crushing the base dictionary. */
if (vm->here > 1024) {
/* S-1c: never below the input buffers allotted at vm_init()
* (TIB/>IN/SPAN/WORD buffer end past 1024). */
size_t floor = 1024;
if (vm->word_buf) {
size_t buf_end = (size_t)(vm->word_buf - vm->memory) + WORD_BUFFER_SIZE;
if (buf_end > floor) floor = buf_end;
}
if (vm->here > floor) vm->here = floor;
}
/* FABRIC-3.10.8 K-2: back to the post-boot dictionary -- every word
* defined since the fence forgotten (as FORGET does it) and HERE at
* the fence. This used to rewind HERE alone, to 1024, leaving
* `latest` pointing at live words that the next definitions then
* compiled over. The fence is set after vm_init() allots the input
* buffers (S-1c), so HERE never lands below them. */
int n = vm_dictionary_forget_to_fence(vm);
log_message(LOG_INFO, "COLD: %d word(s) forgotten, HERE=%ld", n, (long) vm->here);
}
system_running = 1;