FABRIC.md §26: rule the physical heartbeat must be adaptive, not fixed-rate
Loop #7 (vm_runtime.c:703-752) already computes an execution-derived adaptive period and it is orphaned -- nothing in the kernel build ever reads tick_target_ns. Traces the pthread-based consumer it was written for (deliberately disabled for kernel builds, no pthread on bare metal) and confirms via the §18.5 argument that consuming it via the physical re-arm period does not reopen GAP-A1, since the decision to change the period stays execution-derived and the virtual tick still owns all patron state. Rules the scale mismatch (10us hosted base vs 10ms/100Hz kernel base) in favor of reusing Loop #7's decision logic against the kernel-appropriate base, and the mechanism (single-writer/single-reader shared state, no thread/scheduler) against §21.1's existing free-locking finding. Amends item 0.8 to include this -- no new punch-list item, this is inside what 0.8 already builds. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
43aa0a2a36
commit
da4cb14702
@@ -2117,15 +2117,28 @@ on until there is a tick on all three architectures (§16.1, §16.5).*
|
||||
each tick.
|
||||
*Done when:* `heartbeat_ticks()` advances on aarch64 at the configured rate.
|
||||
|
||||
- [ ] **0.8 — Converge the three architectures on one tick path.**
|
||||
- [ ] **0.8 — Converge the three architectures on one tick path, and make the physical
|
||||
heartbeat adaptive.**
|
||||
One `heartbeat_tick()` call site per architecture; the ISR does counter, timestamp and
|
||||
flag only. **Per the GAP-A1 ruling, the hardware tick drives instrumentation only:** the
|
||||
bottom half services TIME-TRUST bookkeeping, and the engine (`vm_tick()`, decay,
|
||||
inference) stays on the virtual tick — execution-paced, exactly as today. Nothing that
|
||||
feeds patron state reads the hardware counter.
|
||||
|
||||
**Per §26 (ruled):** the physical re-arm period is no longer a fixed 100 Hz constant.
|
||||
`heartbeat.c` owns the current adaptive period (`heartbeat_set_adaptive_period_ns()` /
|
||||
`heartbeat_next_period_ns()`); Loop #7's existing site in `vm_runtime.c` calls the setter
|
||||
with its stable/volatile-derived value, rescaled to the 10 ms kernel base per §26.3 (not
|
||||
the 10 µs hosted `HEARTBEAT_TICK_NS`); each architecture's re-arm function reads the
|
||||
getter and converts to its own raw counter units instead of using a hardcoded period. No
|
||||
new concurrency primitive — single writer (mainline), single reader (ISR), same shape
|
||||
§21.1 already found free on one hart.
|
||||
*Done when:* all three architectures drive the same TIME-TRUST bottom half; no loop math
|
||||
runs in interrupt context; and `vm_tick()`'s call sites are unchanged.
|
||||
*Refs:* §16.4 (as ruled), §18.4, §21.2.
|
||||
runs in interrupt context; `vm_tick()`'s call sites are unchanged; **and** the re-arm
|
||||
period measurably varies with Loop #7's stable/volatile signal in a live QEMU run (not
|
||||
merely computed and discarded) — verified the same way item 0.6's IRQ-storm question was
|
||||
verified: direct measurement, not assumption.
|
||||
*Refs:* §16.4 (as ruled), §18.4, §21.2, §26.
|
||||
|
||||
- [ ] **0.9 — Write the concurrency constraint at the mutex stub.**
|
||||
Add a comment at `src/starkernel/vm/host/shim.c:415` stating that the no-op is correct
|
||||
@@ -2451,3 +2464,122 @@ pin-and-panic, and §25.0's rules themselves. None of them moved.
|
||||
means. B1/B2 are ten-minute reads. C and D are mechanical once A1 is ruled. Nothing should
|
||||
go to a coding model before C1, C2 and C3 are fixed at minimum — those are the ones it
|
||||
will hit in its first hour.
|
||||
|
||||
---
|
||||
|
||||
## 26. The hardware heartbeat must itself be adaptive — RULED 2026-08-03
|
||||
|
||||
Raised mid-0.8: the punch list, as written, makes the hardware tick a fixed-rate
|
||||
instrument (100 Hz, unconditionally, on all three architectures — the `apic_timer_init(...,
|
||||
100)` calls built across items 0.1–0.7). That is correct for what §18.4/§18.5 require of
|
||||
the *engine* — the virtual tick stays execution-paced regardless. But it leaves the
|
||||
*physical* heartbeat monotonic, and the physical heartbeat was never supposed to be
|
||||
monotonic. Captain Bob: *"the heartbeat is adaptive... it spreads the heartbeat out when
|
||||
your heart goes faster when you're running... it's gotta be an adaptive heartbeat, that's
|
||||
the whole thing to cage variance."*
|
||||
|
||||
### 26.1 The finding — an adaptive-rate engine already exists, and it is orphaned
|
||||
|
||||
`vm_runtime.c:703-752` ("Loop #7 — Adaptive Heartrate") computes a bounded adaptive period,
|
||||
`vm->heartbeat.tick_target_ns`, from the same ANOVA-driven stability signal Loop #5 already
|
||||
uses: early-exit (stable) slows it down, full-inference (volatile) speeds it up, ±25% per
|
||||
step, clamped to `[¼×, 4×]` of a configured base. This is real, executing code, faithful to
|
||||
the design in the sibling StarForth repo's own
|
||||
`docs/working/architecture/03-architecture/heartbeat-system/architecture.md` (Option A vs.
|
||||
Option B). It is not a proposal — it already runs, on every `vm_tick()`.
|
||||
|
||||
It is orphaned. `tick_target_ns` is written to `vm->heartbeat.worker->tick_ns`
|
||||
(`include/vm.h:286,289`), and `worker` is always `NULL` in kernel builds — nothing in
|
||||
`src/starkernel/` reads `tick_target_ns` at all. The mechanism that was meant to *consume*
|
||||
it, a `pthread_create()`-based background worker (`heartbeat_thread_main()`, vendored into
|
||||
`vm_bootstrap.c:310`), is deliberately and redundantly disabled for kernel builds
|
||||
(`Makefile.starkernel:293,338`, `HEARTBEAT_THREAD_ENABLED` forced to `0` twice) — correctly:
|
||||
this is a bare-metal single-hart kernel, there is no pthread implementation, and the
|
||||
mechanism's own history (`segfault-analysis.md` in the StarForth repo) is a real
|
||||
concurrent-access bug against `RollingWindowOfTruth`, fixed by a mutex a single hart doesn't
|
||||
need and can't cheaply provide (§21.2, item 0.9).
|
||||
|
||||
So: the *decision logic* is real, tested by inheritance, and currently produces a number
|
||||
nothing downstream ever reads. Phase 0 as written would ship a heartbeat that looks adaptive
|
||||
in the source tree and is not adaptive on the wire.
|
||||
|
||||
### 26.2 This does not reopen GAP-A1
|
||||
|
||||
§18.5 already proved the general shape of this argument for Loop #5 and is directly
|
||||
reusable for Loop #7: **adaptation is safe exactly when its inputs are execution-derived**,
|
||||
because then the *decision* to change the period is itself a deterministic function of the
|
||||
execution stream, not of wall-clock jitter. §18.5 point 3 already establishes that every
|
||||
`InferenceInputs` field feeding this ANOVA machinery — rolling window, trajectory length,
|
||||
prefetch hit rate, hot/stale word counts, total heat, word count — is execution-derived with
|
||||
zero timing input. Loop #7's stable/volatile classification is downstream of that same
|
||||
machinery. Nothing new needs proving there.
|
||||
|
||||
What must not change, and does not under this design:
|
||||
|
||||
- The **virtual tick stays the engine's clock** (§18.4, unchanged). Adjusting the physical
|
||||
re-arm period changes *when TIME-TRUST samples land and how often the idle path wakes* —
|
||||
it does not move decay, reap, or inference off the virtual tick onto the hardware one.
|
||||
- The hardware tick's **output** still feeds nothing that reaches the parity hash (§18.5
|
||||
points 1, 5, unchanged) — only its *input* (the period it's told to re-arm at) becomes
|
||||
execution-derived instead of fixed.
|
||||
- What was already true and already inert under §18.5 — that wall-clock interrupt *arrival*
|
||||
timing is not reproducible run to run — stays true and stays inert. Nothing patron-facing
|
||||
ever depended on it; this design doesn't change that.
|
||||
|
||||
### 26.3 The scale mismatch, and the ruling
|
||||
|
||||
`tick_target_ns`'s configured base, `HEARTBEAT_TICK_NS` (`include/starforth_config.h:71`),
|
||||
is `10000ULL` — 10 microseconds. Its live range under Loop #7's ±25%/`[¼×,4×]` bounds is
|
||||
therefore 2.5µs–40µs. The hardware timer configured throughout items 0.1–0.7 runs at 100 Hz
|
||||
— 10 milliseconds. That is a three-orders-of-magnitude mismatch: reprogramming the physical
|
||||
re-arm to the literal `tick_target_ns` value would fire the timer 25,000–400,000 times a
|
||||
second, which on a bare-metal single-hart kernel means the core spends effectively all its
|
||||
time in trap entry/exit and the REPL is never reached. `HEARTBEAT_TICK_NS` was tuned for a
|
||||
hosted OS thread's sleep granularity, not a bare-metal ISR period.
|
||||
|
||||
**RULED (Captain Bob, 2026-08-03):** same relationship, kernel-appropriate scale. The
|
||||
mechanism must be real and load-bearing — a genuine, measurable effect on the physical
|
||||
re-arm period, the same kind of effect the original hosted pthread experiments showed — but
|
||||
computed against the 10 ms / 100 Hz base already established for this kernel, not the 10 µs
|
||||
hosted base. Loop #7's *decision logic* (stable → slower, volatile → faster, ±25% per step,
|
||||
clamped `[¼×, 4×]`) is reused unmodified; only the base it is applied to changes.
|
||||
|
||||
### 26.4 The mechanism — no thread needed
|
||||
|
||||
Captain Bob authorized building a kernel-native thread/task if one were required
|
||||
("if we gotta run a thread or whatever, it doesn't matter"). One is not required, and adding
|
||||
one would need a preemptive scheduler this single-hart kernel does not have (§21.2 already
|
||||
rules out real locking for exactly this reason). The existing shape gets there without new
|
||||
infrastructure:
|
||||
|
||||
- `vm_tick()` already calls Loop #7 on the **mainline path** (execution-paced, never in
|
||||
interrupt context) and already produces a fresh `tick_target_ns` there.
|
||||
- Item 0.8 already introduces a shared `heartbeat.c` owning the top/bottom-half split. That
|
||||
file is the natural owner of one new piece of state: the *current adaptive period*, set by
|
||||
a new `heartbeat_set_adaptive_period_ns(uint64_t ns)` (called from `vm_runtime.c`'s Loop #7
|
||||
site, scaled to the kernel base per §26.3) and read by a new `heartbeat_next_period_ns(void)`.
|
||||
- Each architecture's existing re-arm function (`apic_timer_rearm()`, `riscv64_timer_rearm()`,
|
||||
the aarch64 equivalent) already runs in interrupt context at the top of every tick (§18.4's
|
||||
"one tick" call sites, unchanged). It converts `heartbeat_next_period_ns()` to that
|
||||
architecture's raw counter units — a conversion each already does today for its fixed
|
||||
period — instead of using a hardcoded constant.
|
||||
|
||||
No new concurrency: the write happens on the mainline execution path, the read happens in
|
||||
interrupt context, and the value read is whatever was last written — the same single-writer/
|
||||
single-reader shape every other piece of ISR-read, mainline-written state in this kernel
|
||||
already has (§21.1's finding that locking here is already free, because nothing here is
|
||||
actually concurrent on one hart). No pthread, no kernel task, no scheduler.
|
||||
|
||||
### 26.5 Open, deferred
|
||||
|
||||
- Multi-VM: today Hera is the only VM, so "whose `tick_target_ns` drives the one physical
|
||||
timer" has one answer. Not resolved for when Hermes/Artemis return — deferred, not
|
||||
applicable yet (consistent with §20.5's other Tripod-return deferrals).
|
||||
- `HEARTBEAT_TICK_NS`'s name and its hosted-scale value are unchanged by this ruling — the
|
||||
kernel-side base (10 ms) is a **separate** constant, not a redefinition of the hosted one.
|
||||
Naming the kernel constant is an implementation detail of the item that builds this, not a
|
||||
document-level open question.
|
||||
|
||||
**RULED.** Item 0.8 is amended below to include this; no new punch-list item is needed —
|
||||
this is squarely inside what 0.8 already builds (`heartbeat.c`, the three re-arm call
|
||||
sites).
|
||||
|
||||
Reference in New Issue
Block a user