Stage S-1c: WORD's buffer is per VM; input buffers allotted at VM init, below all compiled code (FABRIC-3.9.0 §8.1)
- The static word_scratch_addr shared by every VM is gone: WORD builds in vm->word_buf (257 bytes: count + 255 + NUL). A token over 255 chars is refused whole (delimiter restored, >IN unconsumed, vm->error), logged at LOG_ERROR and counted -- no more silent cut to 62 chars. - Pre-existing corruption found by the probe: TIB/>IN/SPAN (and WORD's scratch) were allotted lazily at HERE; POST's restore_dict_state(), FORGET and COLD rolled HERE below them and compiled the dictionary over them (Hera's TIB lay under ACL-RECHECK's body; QUERY/WORD wrote into code). Per Captain Bob, all four are now allotted first in both vm_init()s (offsets 0-1305); COLD's clamp never goes below them. Probe (reverted), identical on amd64/aarch64/riscv64: 0 colon bodies inside the buffers; per-VM isolation; 62/100/255-char tokens full length; 300 refused and counted; interpreter sane after full-width TIB writes. 3-ISA acceptance passes; M7.1a hash and dict_hash set unchanged (here 0xd8 -> 0x5f8). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a2e3356b2e
commit
316be9fc4b
@@ -52,6 +52,7 @@
|
||||
#include "../include/rolling_window_of_truth.h"
|
||||
#include "../include/dictionary_heat_optimization.h"
|
||||
#include "../include/ssm_jacquard.h"
|
||||
#include "../include/vm_api.h" /* vm_input_ensure() -- S-1c */
|
||||
#include "word_source/include/vocabulary_words.h"
|
||||
#include "vm_internal.h"
|
||||
#ifdef __STARKERNEL__
|
||||
@@ -209,6 +210,20 @@ void vm_init_with_host(VM* vm, const VMHostServices *host)
|
||||
|
||||
log_message(LOG_DEBUG, "vm_init: memory=%p here=%zu", (void*)vm->memory, vm->here);
|
||||
|
||||
/* FABRIC-3.9.8 Stage S-1c (Captain Bob, 2026-09-24: "allot at VM init"):
|
||||
* the input buffers -- TIB, >IN, SPAN and WORD's buffer -- are allotted
|
||||
* first, at HERE = 0, before POST, capsules or any definition. They used
|
||||
* to be allotted lazily wherever HERE stood; POST's restore_dict_state(),
|
||||
* FORGET and COLD then rolled HERE back below them and compiled the
|
||||
* dictionary over them (Hera's TIB sat under ACL-RECHECK's body). No
|
||||
* HERE rollback can reach below them now. */
|
||||
if (vm_input_ensure(vm) != 0)
|
||||
{
|
||||
log_message(LOG_ERROR, "vm_init: input buffers could not be allotted");
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
vm_bootstrap_scr(vm);
|
||||
|
||||
/* STATE (0=interpret, -1=compile) */
|
||||
|
||||
@@ -142,6 +142,18 @@ int vm_input_ensure(VM *vm) {
|
||||
}
|
||||
*vm->span_var = 0;
|
||||
}
|
||||
if (!vm->word_buf) {
|
||||
/* S-1c: WORD's buffer, per VM, in this VM's own space, placed with
|
||||
* the other input buffers -- never mid-dictionary, never inside a
|
||||
* definition being compiled. After SPAN so TIB/>IN/SPAN keep their
|
||||
* addresses. */
|
||||
vm->word_buf = (unsigned char *) vm_allot(vm, WORD_BUFFER_SIZE);
|
||||
if (!vm->word_buf) {
|
||||
vm->error = 1;
|
||||
return 1;
|
||||
}
|
||||
memset(vm->word_buf, 0, WORD_BUFFER_SIZE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
#include "../include/dictionary_heat_optimization.h"
|
||||
#include "../include/ssm_jacquard.h"
|
||||
#include "../include/platform_alloc.h"
|
||||
#include "../include/vm_api.h" /* vm_input_ensure() -- S-1c */
|
||||
#include "word_source/include/vocabulary_words.h"
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
@@ -245,6 +246,17 @@ void vm_init(VM* vm)
|
||||
memset(p, 0, 64);
|
||||
}
|
||||
|
||||
/* FABRIC-3.9.8 Stage S-1c: the input buffers (TIB, >IN, SPAN, WORD's
|
||||
* buffer) are allotted here, before any definition, so no HERE rollback
|
||||
* (test harness restore, FORGET, COLD) can reach below them. Same as the
|
||||
* kernel's vm_init(). */
|
||||
if (vm_input_ensure(vm) != 0)
|
||||
{
|
||||
vm->error = 1;
|
||||
log_message(LOG_ERROR, "vm_init: input buffers could not be allotted");
|
||||
return;
|
||||
}
|
||||
|
||||
vm_bootstrap_scr(vm);
|
||||
|
||||
vm->mode = MODE_INTERPRET;
|
||||
|
||||
@@ -149,4 +149,7 @@
|
||||
*/
|
||||
void register_string_words(VM * vm);
|
||||
|
||||
/* S-1c: WORD tokens refused for exceeding a counted string's 255 chars. */
|
||||
uint32_t string_word_word_refusals(void);
|
||||
|
||||
#endif /* STRING_WORDS_H */
|
||||
@@ -75,9 +75,12 @@ static inline int ensure_input(VM *vm) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Static VM-backed scratch buffer for WORD (allocated on first use) */
|
||||
#define WORD_SCRATCH_CAP 64
|
||||
static vaddr_t word_scratch_addr = 0;
|
||||
/* S-1c: WORD's buffer is per VM now (vm->word_buf, vm_input_ensure()). A
|
||||
* token too long for a counted string (> 255 chars) is refused, logged and
|
||||
* counted here -- never silently truncated. */
|
||||
static uint32_t g_word_too_long;
|
||||
|
||||
uint32_t string_word_word_refusals(void) { return g_word_too_long; }
|
||||
|
||||
/* 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) {
|
||||
@@ -248,33 +251,31 @@ void string_word_word(VM *vm) {
|
||||
while ((size_t) ip < tib_len && tib[ip] != (unsigned char) delimiter) ip++;
|
||||
size_t end = (size_t) ip;
|
||||
size_t word_len = end - start;
|
||||
if (word_len > WORD_SCRATCH_CAP - 2) word_len = WORD_SCRATCH_CAP - 2;
|
||||
|
||||
/* Allocate scratch once */
|
||||
if (word_scratch_addr == 0) {
|
||||
void *p = vm_allot(vm, WORD_SCRATCH_CAP);
|
||||
if (!p) {
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
word_scratch_addr = vaddr_from_ptr(vm, p);
|
||||
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++;
|
||||
log_message(LOG_ERROR, "WORD: token over 255 chars refused");
|
||||
vm_push(vm, c);
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Build counted string at scratch */
|
||||
uint8_t *wb = vm_ptr(vm, word_scratch_addr);
|
||||
/* Build the counted string in this VM's own WORD buffer */
|
||||
uint8_t *wb = vm->word_buf;
|
||||
if (!wb) {
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
wb[0] = (uint8_t) word_len;
|
||||
if (word_len) memcpy(&wb[1], &tib[start], word_len);
|
||||
if (word_len + 1 < WORD_SCRATCH_CAP) wb[word_len + 1] = '\0'; /* safety */
|
||||
wb[word_len + 1] = '\0'; /* safety */
|
||||
|
||||
/* Update >IN to char after the delimiter (if any) */
|
||||
while ((size_t) ip < tib_len && tib[ip] == (unsigned char) delimiter) ip++;
|
||||
*inp = ip;
|
||||
|
||||
vm_push(vm, CELL(word_scratch_addr));
|
||||
vm_push(vm, CELL(vaddr_from_ptr(vm, wb)));
|
||||
}
|
||||
|
||||
/* >IN ( -- addr ) Input stream pointer cell (VM address) */
|
||||
|
||||
@@ -90,7 +90,16 @@ static void reset_vm_state(VM *vm, int cold_start) {
|
||||
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) vm->here = 1024;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
system_running = 1;
|
||||
|
||||
Reference in New Issue
Block a user