Aggregates the sweep's cross-cutting architectural findings (file-scope statics standing in for per-VM state, missing overflow guards, duplicate word registration/shadowing) and gives an executive-summary coverage index across all 34 src/word_source/*.c files, per Bob's original framing for this initiative. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
12 KiB
Isabelle/HOL Word-Source Sweep — Aggregated Architectural Findings
Status: src/word_source/*.c coverage sweep complete as of commit 346c793
(53 theories, all green, ~35–40s full build). This document synthesizes the
cross-cutting findings the sweep surfaced along the way — each was noted in
the relevant .thy file's header comment as it was found; this pulls them
together into one place for review, since no single file's header shows the
pattern's full size.
These are not proof gaps (things the sweep declined to model). They are real properties of the C implementation that the act of formalizing surfaced. Nothing here has been fixed — per project convention, findings are reported, not acted on, until you decide what (if anything) to do about them.
1. File-scope C statics standing in for per-VM state
The single biggest finding of the sweep. A recurring pattern: state that
conceptually belongs to one VM instance (struct VM) is instead a C
file-scope static, shared by every VM in the process. In the Tripod
multi-VM fleet (Hera/Hermes/Artemis + any future VMs), this means one VM's
actions silently affect every other VM's behavior through hidden shared
state, with no locking or per-VM isolation.
Confirmed instances, in the order the sweep found them:
| # | File | Static(s) | What it backs | Severity |
|---|---|---|---|---|
| 1 | control_words.c |
cf_stack/cf_sp, cf_last_mode, leave_addrs/leave_sp, endof_addrs/endof_sp |
Compile-time control-flow (IF/THEN/BEGIN/DO/CASE/...) nesting state | High — two VMs compiling control structures at overlapping times corrupt each other's nesting; a VM whose compile aborts mid-structure leaves stale state for whoever compiles next. The one reset guard (cf_epoch_sync) is itself a single global. |
| 2 | dictionary_manipulation_words.c |
static cell_t state_variable |
Backed [, ], STATE; also written (inertly) by INTERPRET |
Mixed — [/]/STATE were dead/shadowed and have been removed (§3 instance #1). INTERPRET is live (not shadowed) and still writes this static on every call, but the write is functionally inert since nothing on any live path reads it anymore; left alone as a live registered word rather than edited under this repair's scope. |
| 3 | string_words.c |
static vaddr_t word_scratch_addr |
WORD's scratch buffer |
Medium — lazily allocated on first call, reused by every VM thereafter. |
| 4 | system_words.c |
static int system_running, static int forth_79_standard |
COLD/WARM/BYE run-state; 79-STANDARD mode flag |
Medium — process-wide instead of per-VM. |
| 5 | vocabulary_words.c |
forth_vocab/context_vocab/current_vocab, context_var_addr/current_var_addr, first-char search index, plus static int initialized guard |
The entire vocabulary subsystem | Highest severity in the sweep — one VM's VOCABULARY/DEFINITIONS/FORTH silently changes where every VM looks up and defines words. The initialized guard compounds it: only the first VM to touch any vocabulary word ever seeds the vocabulary roots, seeded from its own dictionary. |
| 6 | starforth_words.c |
g_prng_state |
SEED/RANDOM |
Medium — every VM in the fleet draws from the same RNG stream (also a reproducibility/determinism concern for the DoE campaigns, not just isolation). |
| 7 | ttf_words.c |
static int g_ttf_font_ready |
TTF font-load-once gate | Low–Medium — one VM's font initialization silently satisfies the "ready" check for every other VM. |
Seven confirmed live instances, plus one dead-code instance (#2, see §3).
All were found incidentally — the sweep wasn't looking for this pattern,
it kept encountering it because vm_state (the abstract model) only has a
field when the C genuinely threads it through struct VM, so file-scope
statics kept showing up as "this word can't be modelled against per-VM state
the way its siblings can."
Recommendation: #5 (vocabulary) and #1 (control-flow compile state) are
the two that would actually corrupt VM behavior in the live Tripod fleet
today, if two VMs exercise them concurrently. Worth scoping as a real fix
independent of this proof work — moving these into struct VM fields.
2. Missing overflow/capacity guards before stack pushes
Corrected after re-checking against the real C, not just the proof
model (2026-08-14, during the repair pass below). The sweep's .thy files
flagged ~15 words across 6 files as pushing with no capacity check. On
inspection, that overstated the real defect count by a lot — the abstract
proof model's push helper didn't credit two things the real C already
does:
vm_push()(src/stack_management.c:75) bounds-checks internally (if (vm->dsp >= STACK_SIZE - 1) { vm->error = 1; return; }) before every write. Any word that callsvm_push()—FB-WIDTH/FB-HEIGHT, all four flagged keyboard words, and allLOG-*/LOG-LEVEL@words — was already safe. Not a bug; a proof-model gap (fixed in the theories, no C change needed).VM_PUSH/VM_POP(include/vm.h:706-719) is a macro that resolves to the checkedvm_push/vm_popin every build except one compiled withSTARFORTH_PERFORMANCEdefined, which switches it to uncheckedvm_push_fast/vm_pop_fast. Repo-wide grep confirmsSTARFORTH_PERFORMANCEis never defined by any Makefile or Kconfig target in this repo — only referenced insidevm.hitself andstack_words.c. SoQ.1/Q.0/Q.SCALE(q48_words.c) and theINFER-*@/WINDOW-DIVERSITY/L8-MODE/BAYES-*words (inference_words.c), which all go throughVM_PUSH, are safe under every configuration this repo currently builds. The exposure is real but dormant — it would only activate if some future build target defined that macro, which is a build-configuration decision, not a per-word bug to patch 14 times over.
The one real, live, unconditional instance: DECAY-RATE@
(physics_freeze_words.c) writes straight to vm->data_stack[vm->dsp++]
with no guard at all and no prior pop to make room — unlike its
neighbors in the same file (FROZEN?, HEAT@) which pop 2 before pushing
1, net-shrinking the stack and therefore can't overflow. Fixed: added
the same if (vm->dsp >= STACK_SIZE) { vm->error = 1; return; } guard
LOOKUP-STRATEGY@ (dictionary_heat_diagnostic_words.c:98) already uses
for the identical shape.
3. Duplicate word registration / dead-code shadowing
Three confirmed instances where two different C files register a same-named word, and FORTH's newest-registration-wins dictionary lookup means the earlier registration is permanently dead code:
[,],STATE—dictionary_manipulation_words.c(module 13) registered first,defining_words.c(module 17) registered the same names later and shadowed them. Repaired 2026-08-14: removed the three dead functions (dictionary_m_word_left_bracket/right_bracket/state) and theirregister_word()calls fromdictionary_manipulation_words.c— confirmed via repo-wide grep they had no other callers or header declarations. The livedefining_words.cversions (vm->state_addr) are untouched. Correction:INTERPRETis not part of this shadow —defining_words.cnever registers a word by that name, sodictionary_manipulation_words.c'sINTERPRETis the only registration and is live, reachable code (see §1 instance #1's updated text). It still writes the deadstate_variablestatic on every call, but that write is functionally inert (nothing on any live path reads it) and, being a live registered word, was left alone rather than edited under this repair's "confirmed-dead-registrations-only" scope — reported, not touched.DEFER,IS,DEFER@—defer_words.c(module 27) shadowsdefining_words.c(module 17) in both hosted and kernel builds (defer_words.chas no__STARKERNEL__guard despite CLAUDE.md documenting it as a "kernel-only addition"; the hosted Makefile wildcards it in regardless). Repaired 2026-08-14: removed the three dead functions (defining_word_defer/is/defer_fetch) and the now-orphaneddefining_runtime_deferhelper (would otherwise trigger an unused-static-function warning under-Wall -Werror) plus theirregister_word()calls fromdefining_words.c. The livedefer_words.cimplementation is entirely separate code, untouched.starforth_words.c's own double-registration —register_starforth_wordsregisters 10 words into the STARFORTH vocabulary, then re-registers 12 words (the same 10 plusENTROPY@/ENTROPY!) into that same vocabulary context. Not yet judged intentional or not — resolving that needs the vocabulary-chain mechanics, which are themselves unmodelled (see §1 instance #5). Not touched.
Verification for #1/#2's repair: hosted make builds clean with zero
warnings under -Wall -Werror; the hosted build's own comprehensive
self-test suite (runs automatically at every startup) passed 965/965
implemented tests, 0 failures, 0 errors, including the Defining Words Tests (Module 13) block that exercises DEFER/IS/DEFER@ end to end
through the live defer_words.c path. Three-arch QEMU boot acceptance
(per .claude/CLAUDE.md, mandatory for any change touching vendored
kernel word-source) — see this document's closing status line for result.
4. Notable one-off findings (not patterns, but worth knowing)
EXECUTE(system_words.c) casts a popped cell straight to aDictEntryhost pointer and calls through it, gated only by a null check. Same hazard class as?/DUMPbelow, but far more consequential sinceEXECUTEis a core, ubiquitous primitive rather than a diagnostic word. Flagged as the highest-severity single-word finding in the sweep.?andDUMP(format_words.c) cast the popped cell straight to a host pointer and dereference it, bypassingvm_addr_ok— an out-of-VM-bounds read.TYPE(io_words.c) has a signed-overflow bypass in its bounds check (machine-checked witness in the proof).DECIMAL/HEX/OCTAL(format_words.c) write only the memory cell atbase_addr, nevervm->base(the separate host-mirror field number- output words actually read viacurrent_base()) — proved asdecimal_does_not_change_vm_baseet al. Net effect: these words silently affect number parsing but never number printing.LATEST(dictionary_words.c) has a body identical toHERE(both pushvm->here) — does not consultvm->latestdespite its doc comment claiming otherwise.ALIGNbounds-checkshereagainstDICTIONARY_MEMORY_SIZE(2MB) whileALLOT/,/C,/2,check againstVM_MEMORY_SIZE(5MB) instead — two different ceilings for the same pointer.INFER-*(array_ptr,inference_words.c) setsvm->errorand still pushes a placeholder value anyway — violates the "error or push, never both" shape essentially every other word in the sweep follows.- Three different L8 mode-selector representations exist in the live
system: the 4-mode
ssm_l8field this suite has modelled since early in the sweep, a legacy 16-modessm_l8_state_tthatL8-MODE/L8-UPDATE/L8-APPLYactually manipulate, and a separate 128-config adaptive table thatL8-TABLE-FORCE's own comment says the heartbeat's bandit actually drives. Open question for you — not guessed at in the proof.
Where these came from
Each finding above is documented in full (with the specific line numbers
and the lemma that proves it, where machine-checked) in the header comment
of its .thy file under proof/. This document is an index and synthesis,
not a replacement — consult the individual file for the exact argument.
Generated 2026-08-14 from the completed word-source sweep, commit 346c793.
Repair-pass acceptance, 2026-08-14: three-architecture QEMU boot, one at
a time per .claude/CLAUDE.md. All three reached ok> with an identical
dictionary parity hash (0x24b4279f0670aa3a) and identical self-test results
(Total tests run: 1003, Passed: 965, Failed: 0, Errors: 0) —
logs/20260814-195128/amd64, logs/20260814-201210/aarch64,
logs/20260814-202224/riscv64. Confirms the §2/§3 repairs (the
DECAY-RATE@ guard and the two dead-registration removals) introduced no
behavioral drift on any architecture.