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
+76
-1
@@ -1,6 +1,6 @@
|
||||
# FABRIC-3.9.8.md — Observers and participants: execution plan and log
|
||||
|
||||
**Status: IN PROGRESS.** Done: O-A, the stack-convention fix, S-1a and S-1b (§2). The design and
|
||||
**Status: IN PROGRESS.** Done: O-A, the stack-convention fix, S-1a, S-1b and S-1c (§2). The design and
|
||||
its rulings (O-1 to O-4, the discard, and §7/§8's M- and S- rulings) are in `FABRIC-3.9.0.md`.
|
||||
|
||||
Each stage:
|
||||
@@ -338,3 +338,78 @@ Evidence is named **before** each stage runs.
|
||||
callers.
|
||||
- No word-id field narrower than 32 bits.
|
||||
- Nothing else found.
|
||||
- [x] **Stage S-1c — `WORD`'s buffer is per VM, and the input buffers sit below all compiled
|
||||
code (`FABRIC-3.9.0.md` §8.1, class C).** 2026-09-24. Captain Bob: *"go ahead with S-1c"*,
|
||||
then, on the defect below, *"Allot at VM init"*.
|
||||
|
||||
**The defect as scoped.** `WORD` built its counted string in one `static vaddr_t
|
||||
word_scratch_addr` shared by every VM:
|
||||
- It was `vm_allot`'d once, in whichever VM called first, at wherever that VM's `HERE` stood.
|
||||
- A second VM got an address meant for someone else's space.
|
||||
- Tokens were silently cut to 62 characters (a 64-byte buffer).
|
||||
|
||||
**The deeper defect, found by the first probe.** The input buffers were allotted lazily by
|
||||
`vm_input_ensure()` at the current `HERE`: the TIB (`INPUT_BUFFER_SIZE`), `>IN`, `SPAN`, and
|
||||
`WORD`'s scratch.
|
||||
- Three things later roll `HERE` back below them and compile the dictionary over them: POST's
|
||||
test harness (`restore_dict_state()`), `FORGET`, and `COLD` (which clamps `HERE` to 1024).
|
||||
- On Hera, POST allotted the TIB at offset 120 and then restored `HERE`. **`ACL-RECHECK`'s
|
||||
colon body lived at offset 824, inside the TIB.**
|
||||
- The first S-1c probe wrote the TIB, as `QUERY` does, which zeroed that body. Hera hit
|
||||
`execute_colon_word: NULL cell in 'ACL-RECHECK'` and never reached her prompt
|
||||
(`logs/20260924-151033/amd64/`; layout diagnostic `-152047/amd64/`).
|
||||
- So before this stage, `QUERY` wrote into Hera's compiled code, and so did `WORD` through
|
||||
the old static scratch. Nothing in a normal boot calls either afterwards, which is why
|
||||
acceptance never saw it. The first draft of the per-VM buffer, allotted the same lazy way,
|
||||
inherited the defect.
|
||||
|
||||
**The change:**
|
||||
- **`vm.h`:**
|
||||
- `VM.word_buf`, one per VM.
|
||||
- `WORD_BUFFER_SIZE 257`: a count byte, 255 characters (the most a counted string can
|
||||
hold, a format limit, not a chosen bound), and a NUL.
|
||||
- **`vm_api.c`:** `vm_input_ensure()` also allots `word_buf`, after `SPAN`, so TIB/`>IN`/
|
||||
`SPAN` keep their relative layout.
|
||||
- **Both `vm_init()`s** (`src/vm_bootstrap.c`, `src/starkernel/vm/vm_bootstrap.c`) call
|
||||
`vm_input_ensure()` first, before POST, capsules or any definition.
|
||||
- The buffers take offsets 0–1305 in every VM.
|
||||
- Every later `HERE` rollback (test restore, `FORGET`) lands above them.
|
||||
- If the allotment fails, `vm_init` fails with an error.
|
||||
- **`COLD`** (`system_words.c`) still clamps `HERE` to 1024, but never below the end of the
|
||||
input buffers.
|
||||
- **`string_words.c`:** the static is gone. `WORD` builds into `vm->word_buf`.
|
||||
- A token over 255 characters is **refused whole**: the delimiter goes back on the stack,
|
||||
`>IN` is not consumed, and `vm->error` is set.
|
||||
- The refusal is logged at `LOG_ERROR` ("WORD: token over 255 chars refused") and counted
|
||||
(`string_word_word_refusals()`).
|
||||
|
||||
**Evidence** (probe, reverted; byte-identical on all three ISAs, 37 lines;
|
||||
`logs/20260924-152935/amd64/`, `-153409/aarch64/`, `-153842/riscv64/`):
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| Layout, Hera and Hestia | TIB offset 0, `>IN` 1032, `SPAN` 1040, `word_buf` 1048, end 1305. Colon bodies inside the buffers: **0** (Hera 26 checked, Hestia 179). |
|
||||
| Isolation | Hera's `WORD` returns her own `word_buf`, in her own memory. Hestia's returns hers, in hers, and the two are distinct. Hera's `HERAWORD` is intact after Hestia parses `HESTIAWORD`. |
|
||||
| Length | 62, 100 and 255 characters come back at full length with the correct last character. It used to be 62 at most. |
|
||||
| 300-char token | Refused, `vm->error=1`, refusals 1, the ERROR line logged, the delimiter restored, `>IN` 0. |
|
||||
| After the probe | The probe wrote the TIB full-width, and `1 2 + .` still prints `3`. Hera reaches `[zuse@Hera] ok>`. |
|
||||
|
||||
**Acceptance** on the clean tree (`logs/20260924-154256/amd64/`, `-155542/aarch64/`,
|
||||
`-160845/riscv64/`):
|
||||
- Boot M7.1a `word_count=531`, hash `0x572daac83b797332`, unchanged. Only its `here` moves,
|
||||
from `0xd8` to `0x5f8`, the 1,305 buffer bytes now allotted first.
|
||||
- The `dict_hash` set is unchanged (Hera `0xda9742ce9c3376a2`, Hestia `0x413e7d086f342d8b`,
|
||||
Artemis `0xdfd68316d5f5ba85`). The canonical hash walks word ids and body cells, and no
|
||||
colon body carries an absolute address that the shift would change.
|
||||
- The O-A checks pass: G interleave 40/40/48, `HHH` precedence, and a typed `1 2 + .` gives
|
||||
`3` mid-stream.
|
||||
- Mint → WIREBIND → `USE` → `3`. Idle log growth 0. No faults. QEMU exits cleanly.
|
||||
- Clean kernel builds on all three ISAs, plus the hosted build.
|
||||
|
||||
**Found, reported, not fixed:**
|
||||
1. **`COLD` rewinds `HERE` to 1024 without rewinding `latest`.** Every word defined past 1024
|
||||
stays linked while new definitions are compiled over its body. S-1c only keeps the clamp
|
||||
off the input buffers.
|
||||
2. **`PAD` overlaps the block I/O window.** It returns `VM_MEMORY_SIZE - 512`, which is inside
|
||||
the top block slot (`BLK_VM_WINDOW_BASE` = `VM_MEMORY_SIZE - 4096`), so `PAD` and the
|
||||
4th `BLOCK` buffer share bytes.
|
||||
|
||||
Reference in New Issue
Block a user