Fix out-of-bounds stack pushes/pops in four vendored word files (found during S-1a)
physics_freeze_words.c, dictionary_heat_diagnostic_words.c, physics_diagnostic_words.c, physics_benchmark_words.c treated dsp as a count (data_stack[dsp++] push, data_stack[--dsp] pop) while the VM's dsp is the top index. On an empty stack a push wrote data_stack[-1], 8 bytes before the VM struct -- POST's DECAY-RATE@ test did it every boot (on aarch64 it hit parity.c's counter, elsewhere the POST block RAM tail). All 28 sites now use vm_pop/vm_push; four one-item guards fixed (dsp<0). Evidence on amd64/aarch64/riscv64: bytes before the VM struct unchanged across POST; DECAY-RATE@ leaves 1 item; LOOKUP-STRATEGY! now works; G interleave, precedence, console under load, relay 3, dict_hash unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d6c90a6839
commit
9bf2e8ec95
@@ -87,3 +87,61 @@ Evidence is named **before** each stage runs.
|
||||
|
||||
**Not run, by design:** the busy-wait DoE driver, which would deadlock under O-1 (§1).
|
||||
Don't run `DOE` until O-C.
|
||||
- [x] **Stack-convention fix: out-of-bounds stack writes in four vendored word files** (found
|
||||
during S-1a, 2026-09-24). Captain Bob: *"Fix now, own commit."*
|
||||
|
||||
**The defect.** The VM's `dsp` is the index of the top item (−1 = empty):
|
||||
`vm_pop` reads `data_stack[dsp--]`. Four vendored files instead treated `dsp` as a count,
|
||||
pushing with `data_stack[vm->dsp++] = x` (11 sites) and popping with
|
||||
`data_stack[--vm->dsp]` (17 sites):
|
||||
- `physics_freeze_words.c`: 7 pushes, 12 pops;
|
||||
- `dictionary_heat_diagnostic_words.c`: 4 pushes, 2 pops;
|
||||
- `physics_diagnostic_words.c`: 1 pop;
|
||||
- `physics_benchmark_words.c`: 1 pop.
|
||||
|
||||
**The consequences:**
|
||||
- On an empty stack a push wrote `data_stack[-1]`, **8 bytes before the VM struct**. POST's
|
||||
own `DECAY-RATE@.basic` test does exactly that on every boot.
|
||||
- Otherwise a push overwrote the top item and left `dsp` one slot off.
|
||||
- The pops read the item *below* the top, and on the last pop, `data_stack[-1]`.
|
||||
- Four one-item words also guarded with `dsp < 1`, which demands two items, so
|
||||
`LOOKUP-STRATEGY!`, `COMPARE-LOOKUPS`, `PHYSICS-BURN` and `BENCH-DICT-LOOKUP` always
|
||||
errored on valid input.
|
||||
|
||||
**How it was found.** The S-1a parity probe's incomplete counter read one too many on
|
||||
aarch64 only.
|
||||
- A per-test hook in the test runner named `DECAY-RATE@.basic` as the test after which
|
||||
the counter moved (`logs/20260924-104424/aarch64/`).
|
||||
- Writing a marker into `mama->data_stack[-1]` changed the counter to the marker
|
||||
(`-105120`).
|
||||
- Printing runtime addresses (`-105729`) showed why aarch64 only: the aarch64 kernel runs
|
||||
inside the `lld-link` PE, whose `.bss` puts `parity.c`'s counter directly before
|
||||
`sk_mama_vm`.
|
||||
- On amd64 and riscv64 the same write lands in the last cell of the POST block-RAM buffer,
|
||||
silently.
|
||||
- An interim hypothesis of memory aliasing, drawn from ELF symbol addresses that don't
|
||||
describe the aarch64 runtime image, was wrong and is withdrawn.
|
||||
|
||||
**The fix:**
|
||||
- Every site now uses the VM's own `vm_pop(vm)` / `vm_push(vm, x)`, which check bounds.
|
||||
- The four one-item guards are now `dsp < 0`.
|
||||
- The two- and three-item guards (`dsp < 1`, `dsp < 2`) were already correct for the
|
||||
top-index convention and are unchanged.
|
||||
|
||||
**Evidence.** A probe (reverted) compared the 8 bytes before the VM struct across POST,
|
||||
plus console checks, on all three ISAs (`logs/20260924-111124/amd64/`, `-112508/aarch64/`,
|
||||
`-113949/riscv64/`):
|
||||
- `STACKFIX PROBE: … UNCHANGED across POST`;
|
||||
- `DECAY-RATE@ DEPTH . .` gives `1 1`;
|
||||
- `S" DUP" FROZEN? DEPTH . .` gives `1 0`;
|
||||
- `HEAT@` leaves 1 item, and `HEAT-PERCENTILES` leaves 3;
|
||||
- `1 LOOKUP-STRATEGY! LOOKUP-STRATEGY@ .` gives `1`, depth 0, where it used to error;
|
||||
and `0 …` gives `0`.
|
||||
|
||||
Acceptance in the same boots:
|
||||
- the O-A checks: G interleave A = 40/B = 40/48 runs, `HHH` precedence, and the typed line
|
||||
running mid-stream;
|
||||
- relay `3`, idle growth 0;
|
||||
- `dict_hash` unchanged (531, `0x572daac83b797332`);
|
||||
- POST passes;
|
||||
- clean kernel and hosted builds.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Capsule Block Manifest — Auto-generated
|
||||
<!-- Generated by mkcapsule --manifest 2026-09-24T13:13:49Z -->
|
||||
<!-- Generated by mkcapsule --manifest 2026-09-24T15:39:14Z -->
|
||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -77,9 +77,9 @@ void forth_HEAT_PERCENTILES(VM *vm) {
|
||||
return;
|
||||
}
|
||||
|
||||
vm->data_stack[vm->dsp++] = vm->heat_threshold_25th;
|
||||
vm->data_stack[vm->dsp++] = vm->heat_threshold_50th;
|
||||
vm->data_stack[vm->dsp++] = vm->heat_threshold_75th;
|
||||
vm_push(vm, vm->heat_threshold_25th);
|
||||
vm_push(vm, vm->heat_threshold_50th);
|
||||
vm_push(vm, vm->heat_threshold_75th);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,7 +100,7 @@ void forth_LOOKUP_STRATEGY_FETCH(VM *vm) {
|
||||
return;
|
||||
}
|
||||
|
||||
vm->data_stack[vm->dsp++] = (cell_t)vm->lookup_strategy;
|
||||
vm_push(vm, (cell_t)vm->lookup_strategy);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,12 +116,12 @@ void forth_LOOKUP_STRATEGY_FETCH(VM *vm) {
|
||||
void forth_LOOKUP_STRATEGY_STORE(VM *vm) {
|
||||
if (vm->error) return;
|
||||
|
||||
if (vm->dsp < 1) {
|
||||
if (vm->dsp < 0) { /* one item: dsp is the top index, -1 = empty */
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
cell_t strategy = vm->data_stack[--vm->dsp];
|
||||
cell_t strategy = vm_pop(vm);
|
||||
if (strategy == 0 || strategy == 1) {
|
||||
vm->lookup_strategy = (int)strategy;
|
||||
}
|
||||
@@ -204,12 +204,12 @@ void forth_SHOW_HEAT_OPTIMIZATION(VM *vm) {
|
||||
void forth_COMPARE_LOOKUPS(VM *vm) {
|
||||
if (vm->error) return;
|
||||
|
||||
if (vm->dsp < 1) {
|
||||
if (vm->dsp < 0) { /* one item: dsp is the top index, -1 = empty */
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
cell_t iterations = vm->data_stack[--vm->dsp];
|
||||
cell_t iterations = vm_pop(vm);
|
||||
if (iterations <= 0) {
|
||||
printf("Need positive iteration count\n");
|
||||
return;
|
||||
|
||||
@@ -91,13 +91,13 @@ extern size_t sf_fc_count[256];
|
||||
void forth_BENCH_DICT_LOOKUP(VM *vm) {
|
||||
if (vm->error) return;
|
||||
|
||||
if (vm->dsp < 1) {
|
||||
if (vm->dsp < 0) { /* one item: dsp is the top index, -1 = empty */
|
||||
vm->error = 1;
|
||||
printf("BENCH-DICT-LOOKUP: Need iteration count on stack\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cell_t iterations = vm->data_stack[--vm->dsp];
|
||||
cell_t iterations = vm_pop(vm);
|
||||
if (iterations < 1) {
|
||||
printf("BENCH-DICT-LOOKUP: iterations must be >= 1\n");
|
||||
return;
|
||||
|
||||
@@ -251,13 +251,13 @@ void forth_PHYSICS_CALC_KNOBS(VM *vm) {
|
||||
void forth_PHYSICS_BURN(VM *vm) {
|
||||
if (vm->error) return;
|
||||
|
||||
if (vm->dsp < 1) {
|
||||
if (vm->dsp < 0) { /* one item: dsp is the top index, -1 = empty */
|
||||
vm->error = 1;
|
||||
printf("PHYSICS-BURN: Need count on stack\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cell_t burn_count = vm->data_stack[--vm->dsp];
|
||||
cell_t burn_count = vm_pop(vm);
|
||||
|
||||
if (burn_count < 1) {
|
||||
printf("PHYSICS-BURN: count must be >= 1\n");
|
||||
|
||||
@@ -89,8 +89,8 @@ void forth_FREEZE_WORD(VM *vm) {
|
||||
return;
|
||||
}
|
||||
|
||||
cell_t len = vm->data_stack[--vm->dsp];
|
||||
cell_t caddr = vm->data_stack[--vm->dsp];
|
||||
cell_t len = vm_pop(vm);
|
||||
cell_t caddr = vm_pop(vm);
|
||||
|
||||
if (len <= 0 || len > WORD_NAME_MAX) {
|
||||
return; /* Silently fail on invalid length */
|
||||
@@ -130,8 +130,8 @@ void forth_UNFREEZE_WORD(VM *vm) {
|
||||
return;
|
||||
}
|
||||
|
||||
cell_t len = vm->data_stack[--vm->dsp];
|
||||
cell_t caddr = vm->data_stack[--vm->dsp];
|
||||
cell_t len = vm_pop(vm);
|
||||
cell_t caddr = vm_pop(vm);
|
||||
|
||||
if (len <= 0 || len > WORD_NAME_MAX) {
|
||||
return;
|
||||
@@ -168,11 +168,11 @@ void forth_FROZEN_QUERY(VM *vm) {
|
||||
return;
|
||||
}
|
||||
|
||||
cell_t len = vm->data_stack[--vm->dsp];
|
||||
cell_t caddr = vm->data_stack[--vm->dsp];
|
||||
cell_t len = vm_pop(vm);
|
||||
cell_t caddr = vm_pop(vm);
|
||||
|
||||
if (len <= 0 || len > WORD_NAME_MAX) {
|
||||
vm->data_stack[vm->dsp++] = 0; /* Not frozen */
|
||||
vm_push(vm, 0); /* Not frozen */
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -180,9 +180,9 @@ void forth_FROZEN_QUERY(VM *vm) {
|
||||
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) */
|
||||
vm_push(vm, -1); /* True (frozen) */
|
||||
} else {
|
||||
vm->data_stack[vm->dsp++] = 0; /* False (not frozen) */
|
||||
vm_push(vm, 0); /* False (not frozen) */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,9 +208,9 @@ void forth_HEAT_STORE(VM *vm) {
|
||||
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];
|
||||
cell_t len = vm_pop(vm);
|
||||
cell_t caddr = vm_pop(vm);
|
||||
cell_t heat = vm_pop(vm);
|
||||
|
||||
if (len <= 0 || len > WORD_NAME_MAX) {
|
||||
return;
|
||||
@@ -245,11 +245,11 @@ void forth_HEAT_FETCH(VM *vm) {
|
||||
return;
|
||||
}
|
||||
|
||||
cell_t len = vm->data_stack[--vm->dsp];
|
||||
cell_t caddr = vm->data_stack[--vm->dsp];
|
||||
cell_t len = vm_pop(vm);
|
||||
cell_t caddr = vm_pop(vm);
|
||||
|
||||
if (len <= 0 || len > WORD_NAME_MAX) {
|
||||
vm->data_stack[vm->dsp++] = 0;
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -257,9 +257,9 @@ void forth_HEAT_FETCH(VM *vm) {
|
||||
DictEntry *entry = name ? vm_find_word(vm, name, (size_t)len) : NULL;
|
||||
|
||||
if (entry) {
|
||||
vm->data_stack[vm->dsp++] = entry->execution_heat;
|
||||
vm_push(vm, entry->execution_heat);
|
||||
} else {
|
||||
vm->data_stack[vm->dsp++] = 0;
|
||||
vm_push(vm, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,8 +285,8 @@ void forth_SHOW_HEAT(VM *vm) {
|
||||
return;
|
||||
}
|
||||
|
||||
cell_t len = vm->data_stack[--vm->dsp];
|
||||
cell_t caddr = vm->data_stack[--vm->dsp];
|
||||
cell_t len = vm_pop(vm);
|
||||
cell_t caddr = vm_pop(vm);
|
||||
|
||||
if (len <= 0 || len > WORD_NAME_MAX) {
|
||||
return;
|
||||
@@ -391,7 +391,7 @@ void forth_DECAY_RATE_FETCH(VM *vm) {
|
||||
}
|
||||
|
||||
/* Stack: ( -- rate ) */
|
||||
vm->data_stack[vm->dsp++] = (cell_t)DECAY_RATE_PER_US_Q16;
|
||||
vm_push(vm, (cell_t)DECAY_RATE_PER_US_Q16);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user