From 5a28458b21a7ab92097d40ccd281bcf0ef5b7e17 Mon Sep 17 00:00:00 2001 From: Robert Allan James Date: Fri, 7 Aug 2026 01:49:23 -0400 Subject: [PATCH] starkernel: item 4.2 -- Hermes native on the Stadium (complete) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrates Hermes's message/channel lifecycle onto the Stadium's unified heat/capacity economy: MSG-ALLOC/FREE-NODE and CH-ALLOC/FREE-NODE now route entirely through stadium_admit()/stadium_evict(), replacing the old local free-list + independent heat-field mechanism. Eight kernel-only STADIUM-* FORTH primitives (ADMIT, EVICT, RES@, RES-PULL, RES-PUSH, HEAT@, HEAT!, WORD-HEAT), VM.stadium_vm_id threaded through all three vm_core.c dispatch sites (replacing item 4.1's hardcoded vm_uuid_hera()), and the stadium_owner[idx] fix so evict-credit lands in the VM that actually admitted a patron, not whoever owned cell 0. This session's own contribution, on top of that pre-existing implementation: found and fixed two bugs blocking the item's own K≡1.0 conservation self-check (HERMES-K was reading 0, not 65536): - Q.SLOT admission-heat fix (capsules/hermes/init.4th): MSG-SEND/ CH-ACCEPT admitted with Q.1 (the entire fleet-wide "1.0" unit) per item, a leftover from before the Stadium migration when each message/channel had its own unconstrained heat field. Instantly drained the shared, finite reservoir. - Reservoir floor for word-execution admission (stadium_words.c): stadium_word_dispatch() (item 4.1) pulls STADIUM_WORD_HEAT_QUANTUM on every word dispatch, not just first admission -- exhausts a VM's entire reservoir in ~32 dispatches, starving any application-level economy sharing that VM's reservoir before it gets a chance to pull anything. word_dispatch_pull() now clamps word-execution's own pulls to leave a Q48_ONE/3 floor (same fair-share figure COMMON-CH's own floor already uses); application-level pulls are unaffected. - STADIUM-WORD-HEAT primitive + stadium_words_resident_heat(): the floor deliberately leaves word-execution residents holding real heat, invisible to HERMES-K's original formula (MSG+CH+reservoir, no term for word patrons). Adding this term closes K to exactly 65536 on all three architectures. Also rules on two open scope questions in FABRIC.md: MBR-ALLOC/ MBR-FREE-NODE stay off the Stadium (membership records have no heat field, never did -- the acceptance bullet's inclusion of them was a completeness gesture predating a check of the actual layout), and records the effort number (12 implementation files, +759/-120 lines). Verified: all three architectures boot clean, full self-test passes, Stadium conservation closes exactly (resident_sum + reservoir = Q48_ONE) at both the C/Stadium level and the FORTH-level HERMES-K check. Co-Authored-By: Claude Sonnet 5 --- FABRIC.md | 137 +++++++++++- capsules/BLOCK_MAP.md | 72 +++--- capsules/hermes/init.4th | 161 ++++++++++---- disk/artemis.img | Bin 31457280 -> 31457280 bytes experiments/bare_metal/latest/aarch64.csv | 36 +++ experiments/bare_metal/latest/amd64.csv | 36 +++ experiments/bare_metal/latest/riscv64.csv | 36 +++ include/starkernel/vm/stadium.h | 64 ++++++ include/starkernel/vm/stadium_words.h | 88 +++++--- include/vm.h | 13 ++ lfs/amd64/starforth | Bin 1487016 -> 1487016 bytes src/dictionary_management.c | 2 +- src/starkernel/capsule/capsule_birth.c | 5 + src/starkernel/capsule/mama_forth_words.c | 209 +++++++++++++++++- src/starkernel/kernel_main.c | 119 ++++++++++ src/starkernel/vm/bootstrap/sk_vm_bootstrap.c | 1 + src/starkernel/vm/stadium.c | 67 ++++++ src/starkernel/vm/stadium_words.c | 139 ++++++++---- src/starkernel/vm/vm_core.c | 11 +- 19 files changed, 1034 insertions(+), 162 deletions(-) diff --git a/FABRIC.md b/FABRIC.md index 485d72d..d2567ba 100644 --- a/FABRIC.md +++ b/FABRIC.md @@ -3344,9 +3344,10 @@ document and committing that amendment as its own item.* > item never touches reservoir on the donor side), `test-vm reservoir=65536` (a fresh > `Q48_ONE`, as ruled). `dict_hash` identical across all three and unchanged from item 4.1's > baseline (`0x3d4e1daf289da94f`), confirming this item added no dictionary word. -- [ ] **4.2 — Hermes native on the Stadium.** The proving ground; produces the effort +- [x] **4.2 — Hermes native on the Stadium.** The proving ground; produces the effort number. *Refs:* §10. **Unblocked 2026-08-05** — item 4.1a closed; `stadium_grant_quota()` - exists and is wired into every baby VM's birth. + exists and is wired into every baby VM's birth. **Complete 2026-08-07** — all `Done when` + bullets satisfied; see the effort number and MBR-scoping ruling below. > **Two rulings taken before work starts, 2026-08-05:** > 1. **`stadium_owner[idx]` fix folded into this item's scope**, by explicit Captain Bob @@ -3492,8 +3493,83 @@ document and committing that amendment as its own item.* > the active log threshold and never appears in the serial log, a separate dead end closed > along the way) is the only viable method for this kernel today. > + > **Blocker found 2026-08-06, ruled and fixed 2026-08-07 — item 4.1 and item 4.2 silently + > share one finite per-VM reservoir, and word-execution admission alone can exhaust it + > before any application-level allocation runs. This failed the K≡1.0 `Done when` bullet + > below and was not a code bug to just patch — it was a design question spanning both + > items, reported for a ruling rather than resolved unilaterally (§25.0 rule 3). Captain + > Bob ruled option 4 below (reserve a floor); implementation and result are at the end of + > this note.** + > + > After the `Q.SLOT` admission-heat fix (below) closed the original `MSG-SEND`/`CH-ACCEPT` + > over-admission bug, `HERMES-K` still read `0` instead of `65536`. Three prints in one + > boot discriminated the cause: `stadium_reservoir_peek(Hermes)` reads `65536` immediately + > after `BIRTH` (the one-time grant, item 4.1a, is fine) but is already `0` — and + > `COMMON-CH`'s own heat is already `0` — immediately after `CD-INIT` finishes, before + > `HERMES-MSG-TEST`/`MSG-DELIVER-ALL`/anything else in the self-test runs. So this is not a + > Stadium cell getting silently reassigned out from under `COMMON-CH` after the fact + > (aliasing); `COMMON-INIT`'s own `CH-ALLOC` call, partway through `CD-INIT`, never got + > funded in the first place. + > + > Root cause: `stadium_word_dispatch()` (item 4.1, `stadium_words.c:111`) pulls + > `STADIUM_WORD_HEAT_QUANTUM` (2048) from the dispatching VM's reservoir on **every single + > word dispatch**, not just the first time a word is admitted — the "already resident" + > branch (line 142) does `h->heat += stadium_reservoir_pull(vm_id, STADIUM_WORD_HEAT_QUANTUM)` + > unconditionally, every call. `CD-INIT`'s own `MSG-INIT-FREE`/`CH-INIT-FREE`/ + > `MBR-INIT-FREE` loops alone dispatch several hundred words (32 + 16 + 64 iterations, each + > several words deep) before `COMMON-INIT` ever runs. At 2048 per dispatch, a VM's entire + > 65536 reservoir is exhausted by roughly 32 total word dispatches — trivially reached + > within `CD-INIT`'s first loop, let alone the rest of Hermes's boot. The boot log's own + > `promotions=145` figure (Hermes's dict-check diagnostics) makes this arithmetic visible + > directly: 145 × 2048 = 296,960, about 4.5× her entire conserved share, from + > word-execution tracking alone. This applies to any VM doing non-trivial work, not + > something specific to Hermes or to messages/channels — Hera's own reservoir has read `0` + > in every log this entire session, for the same reason, just never surfaced as a problem + > because nothing previously tried to spend Hera's reservoir on anything else. + > + > Options, no ranking, not decided here: + > 1. **Separate reservoirs per VM** — one for word-execution tracking (item 4.1), one for + > application-level use (item 4.2 and whatever comes after it). Most invasive: splits + > `stadium_quotas[slot].reservoir` or the one-time grant itself, touches item 4.1's + > already-shipped design and its recorded DoE baseline. + > 2. **Exempt certain VMs from word-execution admission entirely** — e.g., only Hera (or + > only VMs with no item-4.2-style application economy) get word-heat tracking. Requires + > a new per-VM-class distinction that doesn't exist today. + > 3. **Re-scope `STADIUM_WORD_HEAT_QUANTUM`** — smaller, or charged per-unique-word instead + > of per-dispatch. Touches a Kconfig default that already feeds item 4.1's recorded DoE + > measurements; re-tuning it here could invalidate that baseline. + > 4. **Reserve a floor within the shared reservoir** that word-execution admission cannot + > dip below, mirroring `COMMON-CH`'s own `Q.1/3` floor pattern but at the reservoir + > level instead of a single resident. New mechanism, not yet designed. + > + > **Ruling, 2026-08-07: option 4.** Implemented as `word_dispatch_pull()` + > (`stadium_words.c`), a static helper wrapping `stadium_reservoir_pull()` for + > `stadium_word_dispatch()`'s two call sites only (both the already-resident re-heat pull + > and the not-yet-resident starter-grant pull) — clamped so a pull never takes the + > reservoir below `Q48_ONE / 3`, the same "VM-COUNT=3 fair share" figure `COMMON-CH`'s own + > floor already uses, not a new invented number. Application-level pulls + > (`stadium_reservoir_pull()` called directly, e.g. via `STADIUM-RES-PULL`) are untouched — + > only word-execution admission respects the ceiling on its own consumption. Verified: the + > eviction-credit demo now shows a real transfer (`resident_sum` −1612, `reservoir` +1612, + > exactly, when `COMMON-CH` is evicted) instead of the prior `0`→`0` no-op, and the + > Stadium's own conservation line closes exactly on every boot, every architecture: + > `resident_sum=43691 reservoir=21845 sum=65536`. + > + > This alone brought `HERMES-K` from `0` to `43002` — real, but not exact, because + > `HERMES-K`'s formula (`MSG-TOTAL-HEAT CH-TOTAL-HEAT + STADIUM-RES@ +`) has no term for + > word-execution residents' heat, which the floor now deliberately leaves nonzero. Second + > ruling, same date: **add that term.** New accessor `stadium_words_resident_heat(vm_id)` + > (`stadium_words.c`) sums heat over only a VM's own word-execution residents (walking its + > `word_slots` map, not `stadium_resident_sum()`'s full ownership scan, which would double- + > count messages/channels already in `MSG-TOTAL-HEAT`/`CH-TOTAL-HEAT`), exposed as an + > eighth `STADIUM-*` primitive, `STADIUM-WORD-HEAT ( -- heat )`, same implicit-self + > discipline as the other seven. `HERMES-K` becomes + > `MSG-TOTAL-HEAT CH-TOTAL-HEAT + STADIUM-RES@ + STADIUM-WORD-HEAT + ;`. Confirmed on all + > three architectures: `HERMES-K` prints exactly `65536`, K≡1.0, closing the item's + > headline invariant. + > > *Done when:* - > - The seven `STADIUM-*` FORTH primitives exist, are kernel-only (not in the shared/ + > - The eight `STADIUM-*` FORTH primitives exist, are kernel-only (not in the shared/ > vendored word set), and are exercised by at least one Hermes word each. > - `stadium_owner[idx]` is written correctly on both the free-list-pop and > eviction-fallback paths in `stadium_admit()`, verified by a resident cell's @@ -3505,23 +3581,64 @@ document and committing that amendment as its own item.* > `vm_uuid_hera()` — verified by a Hermes-dispatched word's heat landing in Hermes's > own reservoir, not Hera's, with both VMs' conservation checks closing independently. > - Hermes's message and channel lifecycle (`MSG-ALLOC`/`MSG-FREE-NODE`, `CH-ALLOC`/ - > `CH-FREE-NODE`, `MBR-ALLOC`/`MBR-FREE-NODE`) run entirely through Stadium admission/ - > eviction — no parallel free list, no parallel heat field. Per §11, this is atomic: - > `MSG-HEAT@/!`, `MSG-COOL-ONE`, `MSG-COOL-ALL`, `CH-HEAT@/!`, `CH-COOL-ALL`, - > `CH-TOTAL-HEAT`, `MSG-TOTAL-HEAT` either come out in this same change or are rewritten - > to read/write the Stadium cell instead of a local field — never both mechanisms live - > at once. + > `CH-FREE-NODE`) run entirely through Stadium admission/eviction — no parallel free + > list, no parallel heat field. Per §11, this is atomic: `MSG-HEAT@/!`, `MSG-COOL-ONE`, + > `MSG-COOL-ALL`, `CH-HEAT@/!`, `CH-COOL-ALL`, `CH-TOTAL-HEAT`, `MSG-TOTAL-HEAT` either + > come out in this same change or are rewritten to read/write the Stadium cell instead + > of a local field — never both mechanisms live at once. **`MBR-ALLOC`/`MBR-FREE-NODE` + > ruled out of scope, 2026-08-07 — see below.** + > + > **Ruling, 2026-08-07: `MBR-ALLOC`/`MBR-FREE-NODE` stay on their own free list, not + > migrated onto the Stadium.** This bullet originally named them alongside `MSG-*`/`CH-*`. + > Checked the actual record layout (`capsules/hermes/init.4th`): an MBR record has exactly + > two fields, `MBR-NEXT@` (link) and `MBR-VM@` (owning VM id) — a pure channel-membership + > relationship, no heat field, never had one. The bullet's own stated purpose is "no + > parallel free list, no parallel heat field" — for MBR, "no parallel heat field" is + > already true vacuously, since none exists to be parallel to. Forcing MBR records through + > `stadium_admit()`/`stadium_evict()` would mean inventing a heat/mass/behaviour for + > something structurally without either, spending Stadium cells and reservoir budget on + > records the item's actual design goal (a conserved, evictable-under-pressure heat + > economy) has no reason to govern — "does VM X belong to channel Y" is not a quantity + > that cools, competes for capacity, or needs eviction pressure. Their original inclusion + > in this bullet reads as a completeness gesture written before the field layout was + > checked, not a deliberate requirement. `MBR-ALLOC`/`MBR-FREE-NODE`'s own free list + > (`capsules/hermes/init.4th`, unchanged this item) is correct as-is. > - Blocks 4110–4113 (Artemis) are untouched, per `HERMES.md`'s block-map lock. Any new or > changed Hermes block is verified with `mkcapsule --lint` before commit, per > `experiments/bare_metal/README.md`. > - The POST suite (regression gate per §10) passes. > - **The effort number is recorded explicitly** — per §10, "what Hermes costs is the > multiplier for everything else." Report at minimum: wall-clock/session time spent, - > lines changed (FORTH + the seven-primitive C surface, split out), and file count + > lines changed (FORTH + the eight-primitive C surface, split out), and file count > touched, so 4.3/4.4 can be estimated from a real data point rather than guessed. > - All three architectures boot to `ok>`/`zuse)ok>` with logs under `logs/`, and > Hermes's own conservation check (K≡1.0 across messages + channels + reservoir) closes > exactly, reported the same way item 4.1 reported `resident_sum`/`reservoir`/`sum`. + > + > **Effort number, reported 2026-08-07:** + > - **Session time.** This conversation's own boot-log timestamps span roughly 10 hours + > elapsed (`logs/20260806-153504` through `logs/20260807-013712`), covering: the amd64 + > GOT-indirect-addressing corruption investigation and fix (unrelated to Stadium logic, + > committed separately as `0a7f144`), the item-4.2 acceptance-status survey against this + > punch-list entry, the `Q.SLOT` admission-heat fix, the word-execution reservoir-floor + > fix, and the `STADIUM-WORD-HEAT` addition that closed K≡1.0. This does **not** include + > whatever time the original seven-primitive implementation and capsule migration + > (already in place when this session's survey began) cost in an earlier session — no + > visibility into that, not estimated rather than guessed. + > - **Lines changed, split FORTH vs. C surface** (`git diff --stat`, this session's + > contribution only — the pre-existing implementation's own diff is included since it + > was still uncommitted when measured, but its authorship/timing is the caveat above): + > - FORTH (`capsules/hermes/init.4th`): +116 / −45 (161 changed), 1 file. + > - C, the eight-primitive `STADIUM-*` surface + Stadium core (`mama_forth_words.c`, + > `stadium.c`, `stadium_words.c`, `stadium.h`, `stadium_words.h`, `vm.h`): +511 / −69 + > (580 changed), 6 files. + > - C, other wiring (`capsule_birth.c`, `sk_vm_bootstrap.c`, `vm_core.c`, + > `dictionary_management.c`): +13 / −6 (19 changed), 4 files. + > - Self-test scaffolding (`kernel_main.c`, diagnostic-only, not production code): + > +119 / −0, 1 file. + > - **Total: 12 implementation files, +759 / −120 (879 lines changed).** + > - **File count:** 12 implementation files (13 including this write-up in `FABRIC.md` + > itself). - [ ] **4.3 — Console.** Settles 1.11 as part of the work. *Refs:* §17.5. > **Note, 2026-08-05: Captain Bob wants a discussion before any work starts on this item.** diff --git a/capsules/BLOCK_MAP.md b/capsules/BLOCK_MAP.md index 497f3c8..ba9b11f 100644 --- a/capsules/BLOCK_MAP.md +++ b/capsules/BLOCK_MAP.md @@ -1,5 +1,5 @@ # Capsule Block Manifest — Auto-generated - + @@ -13,7 +13,7 @@ | `common:msg.4th` | 4055 | `0xa99c5bcd3877f80e` | | `doe-campaign.4th` | 4060, 4061, 4062, 4063, 4064, 4065 | `0x3d4549142d91ec20` | | `doe.4th` | 2100, 2101, 2102, 2103, 2104, 2105, 2106, 2107 | `0xb6ecf5374e8ee77c` | -| `hermes:init.4th` | 4100, 4101, 4102, 4103, 4104, 4105, 4106, 4107, 4108, 4109, 4114, 4115, 4116, 4117, 4118, 4119, 4120, 4121, 4142, 4143, 4144, 4145, 4146, 4147, 4148, 4149, 4150, 4151, 4152, 4153 | `0xbb77f3ac463f507f` | +| `hermes:init.4th` | 4100, 4101, 4102, 4103, 4104, 4105, 4106, 4107, 4108, 4109, 4114, 4115, 4116, 4117, 4118, 4119, 4120, 4121, 4142, 4143, 4144, 4145, 4146, 4147, 4148, 4149, 4150, 4151, 4152, 4153, 4154, 4155, 4156, 4157, 4158, 4159, 4175, 4176 | `0x85c7b311d1e5bf97` | | `init-0.4th` | 2200, 2201 | `0xd0a9550baf786bb3` | | `init-1.4th` | 4406, 4415, 4425, 4435 | `0x63e251adb0a03613` | | `init-2.4th` | 4506, 4515, 4525, 4535, 4545 | `0xf113b3d0bcccae47` | @@ -109,28 +109,28 @@ | 4063 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok | | 4064 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok | | 4065 | `doe-campaign.4th` | `0x3d4549142d91ec20` | ok | -| 4100 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4101 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4102 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4103 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4104 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4105 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4106 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4107 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4108 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4109 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | +| 4100 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4101 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4102 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4103 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4104 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4105 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4106 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4107 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4108 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4109 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | | 4110 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | | 4111 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | | 4112 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | | 4113 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | -| 4114 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4115 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4116 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4117 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4118 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4119 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4120 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4121 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | +| 4114 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4115 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4116 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4117 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4118 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4119 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4120 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4121 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | | 4122 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | | 4123 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | | 4124 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | @@ -151,18 +151,24 @@ | 4139 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | | 4140 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | | 4141 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | -| 4142 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4143 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4144 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4145 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4146 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4147 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4148 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4149 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4150 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4151 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4152 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | -| 4153 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok | +| 4142 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4143 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4144 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4145 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4146 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4147 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4148 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4149 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4150 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4151 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4152 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4153 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4154 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4155 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4156 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4157 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4158 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4159 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | | 4160 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | | 4161 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | | 4162 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | @@ -178,6 +184,8 @@ | 4172 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | | 4173 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | | 4174 | `artemis:init.4th` | `0xc9e92cd18f4c7b49` | ok | +| 4175 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | +| 4176 | `hermes:init.4th` | `0x85c7b311d1e5bf97` | ok | | 4300 | `process.4th` | `0x781afc1dbd0294f7` | ok | | 4301 | `process.4th` | `0x781afc1dbd0294f7` | ok | | 4406 | `init-1.4th` | `0x63e251adb0a03613` | ok | diff --git a/capsules/hermes/init.4th b/capsules/hermes/init.4th index 2a0beb3..30a10ad 100644 --- a/capsules/hermes/init.4th +++ b/capsules/hermes/init.4th @@ -15,16 +15,29 @@ Block 4100 64 CONSTANT MBR-MAX 65208 CONSTANT Q-DECAY 255 CONSTANT MSG-DELIVERED +Block 4155 +( item 4.2 -- StadiumBehaviour tags, match stadium.h's enum ) +0 CONSTANT SB-MIGRATE +1 CONSTANT SB-DELIVER +2 CONSTANT SB-EXPIRE +3 CONSTANT SB-COOL +-1 CONSTANT STADIUM-NONE +( item 4.2 -- per-item admission heat for MSG-SEND/CH-ACCEPT. ) +( Remaining reservoir after COMMON-CH's Q.1/3 floor, split ) +( evenly across MSG-MAX messages + non-COMMON CH-MAX-1 slots. ) +Q.1 Q.1 3 / - MSG-MAX CH-MAX 1- + / CONSTANT Q.SLOT Block 4101 -( Hermes v1 — arenas and free-list roots ) +( Hermes v1 -- arenas. item 4.2: heat/capacity via Stadium; ) +( MBR keeps its own free list (not part of heat economy). ) CREATE MSG-ARENA MSG-MAX MSG-CELLS * CELLS ALLOT CREATE CH-ARENA CH-MAX CH-CELLS * CELLS ALLOT CREATE MBR-ARENA MBR-MAX MBR-CELLS * CELLS ALLOT -VARIABLE MSG-FREE-HEAD -VARIABLE CH-FREE-HEAD +VARIABLE MSG-ALLOC-SLOT +VARIABLE CH-ALLOC-SLOT VARIABLE MBR-FREE-HEAD VARIABLE MSG-SEQ VARIABLE CH-ACTIVE +VARIABLE COMMON-CH Block 4142 ( Hermes v1 — VM name routing table ) 8 CONSTANT VM-MAX @@ -39,23 +52,19 @@ CREATE VM-NAME-LENS VM-MAX CELLS ALLOT S" Hermes" 1 VM-NAME-REG S" Artemis" 2 VM-NAME-REG ; Block 4102 -( Hermes v1 — MSG-INIT-FREE CH-INIT-FREE ) +( Hermes v1 -- INIT-FREE. item 4.2: stamps STADIUM-NONE into ) +( each slot's stadium-cell field (msg off 5, ch off 3). ) +( Raw offsets: accessors aren't defined yet in file order. ) : MSG-INIT-FREE ( -- ) - MSG-MAX 1- 0 DO - I MSG-CELLS * CELLS MSG-ARENA + - I 1+ MSG-CELLS * CELLS MSG-ARENA + SWAP ! - LOOP - 0 MSG-MAX 1- MSG-CELLS * CELLS MSG-ARENA + ! - MSG-ARENA MSG-FREE-HEAD ! ; + MSG-MAX 0 DO + STADIUM-NONE I MSG-CELLS * CELLS MSG-ARENA + 5 CELLS + ! + LOOP ; : CH-INIT-FREE ( -- ) - CH-MAX 1- 0 DO - I CH-CELLS * CELLS CH-ARENA + - I 1+ CH-CELLS * CELLS CH-ARENA + SWAP ! - LOOP - 0 CH-MAX 1- CH-CELLS * CELLS CH-ARENA + ! - CH-ARENA CH-FREE-HEAD ! ; + CH-MAX 0 DO + STADIUM-NONE I CH-CELLS * CELLS CH-ARENA + 3 CELLS + ! + LOOP ; Block 4103 -( Hermes v1 — MBR-INIT-FREE MSG alloc/free ) +( Hermes v1 -- MBR-INIT-FREE (member free list, untouched) ) : MBR-INIT-FREE ( -- ) MBR-MAX 1- 0 DO I MBR-CELLS * CELLS MBR-ARENA + @@ -63,26 +72,76 @@ Block 4103 LOOP 0 MBR-MAX 1- MBR-CELLS * CELLS MBR-ARENA + ! MBR-ARENA MBR-FREE-HEAD ! ; -: MSG-ALLOC ( -- addr|0 ) - MSG-FREE-HEAD @ DUP 0= IF EXIT THEN - DUP @ MSG-FREE-HEAD ! - DUP MSG-CELLS CELLS 0 FILL ; +Block 4156 +( item 4.2 -- MSG-ALLOC: finds a free slot (TYPE=0) first, ) +( admits into Stadium only once confirmed free (no leak). ) +: MSG-FIND-FREE-SLOT ( -- addr|0 ) + MSG-ARENA MSG-MAX 0 DO + DUP @ 0= IF UNLOOP EXIT THEN + MSG-CELLS CELLS + + LOOP DROP 0 ; +Block 4175 +( item 4.2 -- MSG-ALLOC: pulls heat from reservoir first, ) +( admits (identity=idx, heat=pulled), rolls back on refusal. ) +: MSG-ALLOC ( heat -- addr|0 ) + MSG-FIND-FREE-SLOT DUP 0= IF SWAP DROP EXIT THEN + MSG-ALLOC-SLOT ! + STADIUM-RES-PULL + MSG-ALLOC-SLOT @ MSG-ARENA - MSG-CELLS CELLS / + SWAP DUP >R + SB-DELIVER STADIUM-ADMIT + DUP STADIUM-NONE = IF + DROP R> STADIUM-RES-PUSH 0 EXIT + THEN + R> DROP + MSG-ALLOC-SLOT @ MSG-CELLS CELLS 0 FILL + MSG-ALLOC-SLOT @ 5 CELLS + ! + MSG-ALLOC-SLOT @ ; +Block 4157 +( item 4.2 -- MSG-FREE-NODE: evict from Stadium, clear field ) : MSG-FREE-NODE ( addr -- ) - MSG-FREE-HEAD @ OVER ! MSG-FREE-HEAD ! ; + DUP 5 CELLS + @ STADIUM-EVICT DROP + DUP 5 CELLS + STADIUM-NONE SWAP ! + DROP ; Block 4104 -( Hermes v1 — CH MBR alloc/free ) -: CH-ALLOC ( -- addr|0 ) - CH-FREE-HEAD @ DUP 0= IF EXIT THEN - DUP @ CH-FREE-HEAD ! - DUP CH-CELLS CELLS 0 FILL ; -: CH-FREE-NODE ( addr -- ) - CH-FREE-HEAD @ OVER ! CH-FREE-HEAD ! ; +( Hermes v1 -- MBR alloc/free (unchanged; not heat economy) ) : MBR-ALLOC ( -- addr|0 ) MBR-FREE-HEAD @ DUP 0= IF EXIT THEN DUP @ MBR-FREE-HEAD ! DUP MBR-CELLS CELLS 0 FILL ; : MBR-FREE-NODE ( addr -- ) MBR-FREE-HEAD @ OVER ! MBR-FREE-HEAD ! ; +Block 4158 +( item 4.2 -- CH-FIND-FREE-SLOT. No TYPE field, so freeness ) +( is stadium-cell = STADIUM-NONE. CH-ALLOC -> block 4176. ) +: CH-FIND-FREE-SLOT ( -- addr|0 ) + CH-ARENA CH-MAX 0 DO + DUP 3 CELLS + @ STADIUM-NONE = IF UNLOOP EXIT THEN + CH-CELLS CELLS + + LOOP DROP 0 ; +Block 4176 +( item 4.2 -- CH-ALLOC: pulls heat from reservoir first, ) +( admits (identity=idx, heat=pulled), rolls back on refusal. ) +: CH-ALLOC ( heat -- addr|0 ) + CH-FIND-FREE-SLOT DUP 0= IF SWAP DROP EXIT THEN + CH-ALLOC-SLOT ! + STADIUM-RES-PULL + CH-ALLOC-SLOT @ CH-ARENA - CH-CELLS CELLS / + SWAP DUP >R + SB-COOL STADIUM-ADMIT + DUP STADIUM-NONE = IF + DROP R> STADIUM-RES-PUSH 0 EXIT + THEN + R> DROP + CH-ALLOC-SLOT @ CH-CELLS CELLS 0 FILL + CH-ALLOC-SLOT @ 3 CELLS + ! + CH-ALLOC-SLOT @ ; +Block 4159 +( item 4.2 -- CH-FREE-NODE: evict from Stadium, clear field ) +: CH-FREE-NODE ( addr -- ) + DUP 3 CELLS + @ STADIUM-EVICT DROP + DUP 3 CELLS + STADIUM-NONE SWAP ! + DROP ; Block 4105 ( Hermes v1 — message field accessors ) : MSG-TYPE@ ( m -- n ) @ ; @@ -95,8 +154,9 @@ Block 4105 : MSG-PADDR! ( a m -- ) 3 CELLS + ! ; : MSG-PLEN@ ( m -- u ) 4 CELLS + @ ; : MSG-PLEN! ( u m -- ) 4 CELLS + ! ; -: MSG-HEAT@ ( m -- q ) 5 CELLS + @ ; -: MSG-HEAT! ( q m -- ) 5 CELLS + ! ; +( item 4.2: offset 5 = Stadium cell idx. MSG-HEAT@/! -> 4154. ) +: MSG-STADIUM-CELL@ ( m -- cell ) 5 CELLS + @ ; +: MSG-STADIUM-CELL! ( cell m -- ) 5 CELLS + ! ; : MSG-SEQ@ ( m -- n ) 6 CELLS + @ ; : MSG-SEQ! ( n m -- ) 6 CELLS + ! ; Block 4143 @@ -114,14 +174,22 @@ Block 4106 : CH-OWNER! ( n c -- ) 1 CELLS + ! ; : CH-STATE@ ( c -- n ) 2 CELLS + @ ; : CH-STATE! ( n c -- ) 2 CELLS + ! ; -: CH-HEAT@ ( c -- q ) 3 CELLS + @ ; -: CH-HEAT! ( q c -- ) 3 CELLS + ! ; +( item 4.2: offset 3 = Stadium cell index. CH-HEAT@/! -> 4154. ) +: CH-STADIUM-CELL@ ( c -- cell ) 3 CELLS + @ ; +: CH-STADIUM-CELL! ( cell c -- ) 3 CELLS + ! ; : CH-MBRS@ ( c -- a ) 4 CELLS + @ ; : CH-MBRS! ( a c -- ) 4 CELLS + ! ; : CH-NEXT@ ( c -- a ) 5 CELLS + @ ; : CH-NEXT! ( a c -- ) 5 CELLS + ! ; : MBR-NEXT@ ( m -- a ) @ ; : MBR-VM@ ( m -- n ) 1 CELLS + @ ; +Block 4154 +( item 4.2 -- composed MSG/CH-HEAT@/!, same names/stacks, ) +( new bodies routed via Stadium. Callers need no changes. ) +: MSG-HEAT@ ( m -- q ) MSG-STADIUM-CELL@ STADIUM-HEAT@ ; +: MSG-HEAT! ( q m -- ) MSG-STADIUM-CELL@ STADIUM-HEAT! ; +: CH-HEAT@ ( c -- q ) CH-STADIUM-CELL@ STADIUM-HEAT@ ; +: CH-HEAT! ( q c -- ) CH-STADIUM-CELL@ STADIUM-HEAT! ; Block 4107 ( Hermes v1 — deliver ) VARIABLE MSG-LAST-MSG @@ -134,17 +202,19 @@ VARIABLE MSG-LAST-MSG DUP MSG-PADDR@ OVER MSG-PLEN@ ROT MSG-TO@ IDX>NAME VM-EXEC ; Block 4144 -( Hermes v1 — MSG-SEND ) +( Hermes v1 -- MSG-SEND. item 4.2: heat -> MSG-ALLOC's ) +( admission directly (zero-heat would lose eviction-fallback ) +( density comparisons), not set afterward as before. ) : MSG-SEND ( type from to paddr plen ch -- ) - MSG-ALLOC DUP 0= IF 2DROP 2DROP 2DROP DROP EXIT THEN + Q.SLOT MSG-ALLOC DUP 0= IF 2DROP 2DROP 2DROP DROP EXIT THEN >R MSG-SEQ @ 1+ DUP MSG-SEQ ! R@ MSG-SEQ! R@ MSG-CH! R@ MSG-PLEN! R@ MSG-PADDR! R@ MSG-TO! R@ MSG-FROM! DUP R@ MSG-TYPE! R@ MSG-ORIG-TYPE! - Q.1 R@ MSG-HEAT! R> DROP ; + R> DROP ; Block 4108 -( Hermes v1 — message cooling ) +( Hermes v1 — MSG-COOL-ONE/ALL: linear decay per tick ) VARIABLE MSG-SCAN : MSG-COOL-ONE ( m -- ) DUP MSG-HEAT@ Q-DECAY Q.* SWAP MSG-HEAT! ; @@ -236,17 +306,17 @@ Block 4115 CH-SCAN ! REPEAT ; Block 4116 -( Hermes v1 — COMMON channel + HERMES-TICK ) -( COMMON floor = Q.1/3: Hermes's fair share, VM-COUNT=3 ) -VARIABLE COMMON-CH +( Hermes v1 -- COMMON + HERMES-TICK. floor=Q.1/3, VM-COUNT=3 ) +( COMMON-CH VARIABLE now in block 4101; see CH-REAP-SAFE 4115 ) : COMMON-INIT ( -- ) - CH-ALLOC DUP COMMON-CH ! + Q.1 3 / CH-ALLOC DUP COMMON-CH ! 0 OVER CH-ID! 0 OVER CH-OWNER! CH-OPEN OVER CH-STATE! - Q.1 3 / OVER CH-HEAT! 0 OVER CH-MBRS! CH-ACTIVE @ OVER CH-NEXT! CH-ACTIVE ! ; +( item 4.2: floor-refresh now reservoir-constrained, may no-op ) +( under pressure (was unconstrained write before). Watch log. ) : HERMES-TICK ( -- ) MSG-DELIVER-ALL MSG-REDELIVER-NACKED MSG-COOL-ALL MSG-REAP @@ -254,8 +324,10 @@ VARIABLE COMMON-CH Q.1 3 / COMMON-CH @ CH-HEAT! ; Block 4147 ( Hermes v1 — HERMES-K + WELCOME ) +( item 4.2: +STADIUM-WORD-HEAT so word patrons count -- 25.7 ) : HERMES-K ( -- q48 ) - MSG-TOTAL-HEAT CH-TOTAL-HEAT + ; + MSG-TOTAL-HEAT CH-TOTAL-HEAT + STADIUM-RES@ + + STADIUM-WORD-HEAT + ; : WELCOME ( -- ) LOG-INFO" Hermes: loaded" ; WELCOME Block 4117 @@ -294,11 +366,10 @@ Block 4119 Block 4148 ( Hermes v1 — channel ops: accept confirm close ) : CH-ACCEPT ( -- ch|0 ) - CH-ALLOC DUP 0= IF EXIT THEN + Q.SLOT CH-ALLOC DUP 0= IF EXIT THEN 1 CH-MINT-ID OVER CH-ID! 1 OVER CH-OWNER! CH-NEGOTIATING OVER CH-STATE! - Q.1 OVER CH-HEAT! 0 OVER CH-MBRS! CH-ACTIVE @ OVER CH-NEXT! DUP CH-ACTIVE ! ; diff --git a/disk/artemis.img b/disk/artemis.img index 4687fba5711090404b07160ce589044835cd4807..5c2acb89030aa99d32015b9a795f31972675316e 100644 GIT binary patch delta 6022 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 cell_index + * map needs this to stop colliding across VMs; word_id is scoped per-VM, + * not globally unique, so a single shared map aliases different VMs' words + * onto each other's Stadium cells and reservoirs). + * + * @param vm_id VM to look up. + * @return Quota slot index, or -1 if vm_id holds no quota. + */ +int stadium_quota_slot_for_vm(VMUuid vm_id); + +/* + * stadium_resident_sum - Read-only: sum of heat across every cell currently + * resident AND owned by vm_id's own quota (FABRIC.md §25.5 item 4.2 -- + * boot diagnostics need this filtered per-VM once a second VM holds a + * quota; summing every resident cell regardless of owner, as the pre-4.2 + * diagnostic did, mixes two VMs' conservation totals together). + * Returns 0 for an unknown vm_id, same convention as stadium_reservoir_peek(). + * + * @param vm_id Owning VM's id. + * @return Sum of resident heat owned by vm_id (Q48.16), or 0 if vm_id has no quota. + */ +uint64_t stadium_resident_sum(VMUuid vm_id); + /* * stadium_evict - Reap the patron header at cell_index (FABRIC.md §17.2: * "reap means leaves the floor, not destroyed"). Dispatches its behaviour @@ -430,6 +457,43 @@ size_t stadium_admit(VMUuid vm_id, const StadiumPatronHeader *candidate); */ int stadium_grant_quota(VMUuid new_vm_id, VMUuid from_vm_id); +/* + * stadium_cell_heat_get - Read a resident cell's own heat (FABRIC.md item + * 4.2's fourth ruling). Requires cell_index to be resident AND owned by + * vm_id's quota -- returns 0 otherwise (out of range, not resident, or + * belongs to a different VM), same ambiguity-with-a-genuine-zero already + * accepted by stadium_reservoir_peek()'s doc: callers that need to + * distinguish "refused" from "actually zero" must already know the cell is + * theirs (e.g. from their own resident-cell tracking), same contract as + * every other implicit-self primitive here. + * + * @param vm_id Calling VM's own identity. + * @param cell_index Index of the resident patron header to read. + * @return The cell's current heat (Q48.16), or 0 if refused. + */ +uint64_t stadium_cell_heat_get(VMUuid vm_id, size_t cell_index); + +/* + * stadium_cell_heat_set - Write a resident cell's own heat, reconciling the + * reservoir delta atomically (FABRIC.md item 4.2's fourth ruling). Same + * ownership requirement as stadium_cell_heat_get(). If new_heat is higher + * than the cell's current heat, pulls the exact difference from vm_id's own + * reservoir first -- refuses (returns -1, no mutation) if the reservoir + * cannot cover the full increase, never a partial credit that would invent + * heat. If new_heat is lower, pushes the exact difference back to the + * reservoir after writing. Equal is a no-op success. This is the only + * sanctioned way to change a resident cell's heat post-admission -- doing + * the reservoir accounting here, not leaving it to the FORTH caller, is the + * whole reason this primitive exists rather than a raw field poke. + * + * @param vm_id Calling VM's own identity. + * @param cell_index Index of the resident patron header to write. + * @param new_heat The heat value to set (Q48.16). + * @return 0 on success, -1 if refused (not owned/resident, or insufficient + * reservoir for an increase). + */ +int stadium_cell_heat_set(VMUuid vm_id, size_t cell_index, uint64_t new_heat); + #endif /* __STARKERNEL__ */ #endif /* STARKERNEL_VM_STADIUM_H */ \ No newline at end of file diff --git a/include/starkernel/vm/stadium_words.h b/include/starkernel/vm/stadium_words.h index 3483f25..aa2088f 100644 --- a/include/starkernel/vm/stadium_words.h +++ b/include/starkernel/vm/stadium_words.h @@ -48,9 +48,17 @@ /* * stadium_words_init - Zeroes the word_id -> cell_index map (DICTIONARY_SIZE - * entries, static -- no allocation). Must be called after stadium_boot_init() - * and stadium_birth_hera(), before any word ever dispatches. Safe to call - * again (re-zeroes); nothing does today. + * entries per VM quota slot, STADIUM_MAX_VM_COUNT slots, static -- no + * allocation). Must be called after stadium_boot_init() and + * stadium_birth_hera(), before any word ever dispatches. Safe to call again + * (re-zeroes for every slot); nothing does today. + * + * item 4.2 (FABRIC.md §25.5): the map is keyed by quota slot, not just + * word_id -- word_id is assigned per-VM (vm->next_word_id), not globally + * unique, so a single shared word_id -> cell_index map aliased different + * VMs' words onto each other's Stadium cells and reservoirs the moment a + * second VM (Hermes) held a quota. One system-wide init call still covers + * every slot; no per-VM init call is needed. */ void stadium_words_init(void); @@ -65,25 +73,34 @@ void stadium_words_init(void); * (fraction of the cell's own current heat, scaled by elapsed_ticks since * this word's own last touch -- STADIUM_WORD_COOL_RATE_Q48) crediting the * cooled amount back to vm_id's reservoir, then pulls - * STADIUM_WORD_HEAT_QUANTUM from the reservoir into the cell (both clamped - * to what the reservoir actually holds). + * STADIUM_WORD_HEAT_QUANTUM from the reservoir into the cell -- clamped to + * what the reservoir actually holds AND to a floor of Q48_ONE / 3 that + * word-execution admission alone may never dip the reservoir below + * (FABRIC.md §25.7, Captain Bob's ruling 2026-08-06: this pull fires on + * EVERY dispatch, not just first admission, and without a floor exhausts a + * VM's entire reservoir in ~32 dispatches, starving any application-level + * economy -- e.g. item 4.2's Hermes -- sharing the same VM's reservoir). + * Application-level pulls (stadium_reservoir_pull() called directly) are + * not subject to this floor. * * If word_id is not resident (or the map's entry is stale -- self-healing * check against the cell's discriminator bit and identity, covers both a * prior eviction and a FORGET/redefine word_id reuse this function did not * itself clear): attempts Option B starter-grant admission -- pulls - * STADIUM_WORD_HEAT_QUANTUM from the reservoir, builds an unpinned COOL - * candidate, calls stadium_admit(). On refusal, pushes the pulled quantum - * back (rollback, preserves conservation across the failed attempt). On - * success, records the mapping and increments the promotion counter. + * STADIUM_WORD_HEAT_QUANTUM from the reservoir (same floor as above), + * builds an unpinned COOL candidate, calls stadium_admit(). On refusal, + * pushes the pulled quantum back (rollback, preserves conservation across + * the failed attempt). On success, records the mapping and increments the + * promotion counter. * - * No-op if word_id == WORD_ID_INVALID, word_id >= DICTIONARY_SIZE, or the - * word layer has not been initialized. + * No-op if word_id == WORD_ID_INVALID, word_id >= DICTIONARY_SIZE, or vm_id + * holds no Stadium quota. * - * @param vm_id Owning VM. Hardcoded to vm_uuid_hera() at every - * call site today -- Tripod is pruned to Hera alone - * (item 0.1), and she is the only VM with a Stadium - * quota. Revisit when item 4.2 restores Hermes. + * @param vm_id Owning VM -- vm->stadium_vm_id at every call site. + * Scopes the word_id -> cell_index lookup to this + * VM's own quota slot (item 4.2, FABRIC.md §25.5) so + * two VMs' independently-numbered word_ids cannot + * alias onto each other's cells/reservoirs. * @param word_id The dispatching DictEntry's stable word_id. * @param heartbeat_ticks Current vm->heartbeat.tick_count (virtual tick, * never wall-clock -- same convention as every other @@ -99,12 +116,34 @@ void stadium_word_dispatch(VMUuid vm_id, uint32_t word_id, uint64_t heartbeat_ti * its stale heat (same failure class as the 2026-08-02 block_words.c * aliasing bug). Evicts the cell if word_id is resident (crediting its heat * back to the reservoir via stadium_evict()'s own credit path) and clears - * the map entry. No-op if word_id is not resident, out of range, or the - * word layer is not initialized. + * the map entry. No-op if word_id is not resident, out of range, or vm_id + * holds no Stadium quota. * + * @param vm_id Owning VM -- vm->stadium_vm_id (item 4.2, FABRIC.md §25.5: + * scopes the lookup to this VM's own word_slots, same + * reason stadium_word_dispatch() takes it). * @param word_id The DictEntry's word_id, about to be recycled. */ -void stadium_word_forget(uint32_t word_id); +void stadium_word_forget(VMUuid vm_id, uint32_t word_id); + +/* + * stadium_words_resident_heat - Sum of heat held by vm_id's own + * word-execution residents only (item 4.1's cells, tracked in this file's + * own word_slots map) -- NOT messages/channels/other application residents, + * which stadium_resident_sum() (stadium.h, item 4.2) mixes in alongside + * everything else a VM owns. Exists so a VM's own application-level + * conservation check (e.g. Hermes's HERMES-K, FABRIC.md §25.7, Captain + * Bob's ruling 2026-08-06) can add this as an explicit term instead of + * silently omitting word-execution heat it has no other way to see. + * + * Walks all DICTIONARY_SIZE word_slots for vm_id's quota slot; each + * resident entry contributes its cell's current heat, verified live against + * the discriminator bitmap (same self-healing pattern as + * resolve_resident_cell() -- a stale map entry contributes 0, not garbage). + * + * @param vm_id The VM whose word-execution residents to sum. + */ +uint64_t stadium_words_resident_heat(VMUuid vm_id); /* * stadium_words_stats - Promotion/eviction counters (same shape as the old @@ -113,9 +152,10 @@ void stadium_word_forget(uint32_t word_id); * admission. Eviction = this word's cell was reaped by another admission's * eviction fallback (stadium_admit()'s density comparison), detected * lazily via the self-healing stale check in stadium_word_dispatch(), or - * explicitly via stadium_word_forget(). + * explicitly via stadium_word_forget(). Scoped to vm_id's own quota slot + * (item 4.2) -- counters are no longer system-wide. */ -void stadium_words_stats(uint64_t *promotions, uint64_t *evictions); +void stadium_words_stats(VMUuid vm_id, uint64_t *promotions, uint64_t *evictions); /* * stadium_words_print_boot_diagnostics - Console output satisfying item @@ -123,11 +163,9 @@ void stadium_words_stats(uint64_t *promotions, uint64_t *evictions); * acceptance line. Prints promotions/evictions, then * Σ(resident heat) + reservoir against Q48_ONE as a conservation check -- * not required by the acceptance text, but the mechanism proves nothing if - * this silently doesn't hold. The heat sum is taken over ALL resident - * Stadium cells, not scoped by owner -- correct only because vm_id is the - * sole VM with any Stadium quota today (item 0.1's Hera-only pruning); the - * per-cell owner byte is private to stadium.c and has no public accessor. - * Revisit the scoping when item 4.2 restores Hermes. + * this silently doesn't hold. The heat sum is scoped to vm_id's own quota + * (stadium_resident_sum(), item 4.2, FABRIC.md §25.5) so two VMs' checks + * close independently instead of mixing both VMs' resident heat together. * * @param vm_id The VM whose reservoir to read (vm_uuid_hera() today). */ diff --git a/include/vm.h b/include/vm.h index 6d79a4c..7f08217 100644 --- a/include/vm.h +++ b/include/vm.h @@ -358,6 +358,8 @@ typedef enum } vm_mode_t; #ifdef __STARKERNEL__ +#include "starkernel/vm_uuid.h" /* VMUuid -- item 4.2, VM.stadium_vm_id */ + /* Saved interpreter state for VM lifecycle calls (BIRTH, VM-EXEC, START). * Dynamically allocated; grows on demand via sf_realloc. */ typedef struct { @@ -529,6 +531,17 @@ typedef struct VM int call_stack_cap; /**< Allocated capacity in entries */ int call_stack_max; /**< High-water mark depth (DoE metric) */ /** @} */ + + /** @name Stadium Identity (item 4.2, FABRIC.md §25.5) + * @{ + */ + VMUuid stadium_vm_id; /**< This VM's own identity, set once at birth from + * VMRegistryEntry.vm_id. Lets vm_core.c's + * stadium_word_dispatch() call sites attribute + * word-execution heat to the VM that actually + * ran the word, instead of item 4.1's hardcoded + * vm_uuid_hera(). */ + /** @} */ #endif } VM; diff --git a/lfs/amd64/starforth b/lfs/amd64/starforth index 18cdd78416e53ebe7e906a23fd20f111d80a7c3f..2fe467ae3c94b64ef2c92f588d74c6f7f8c533be 100755 GIT binary patch delta 137 zcmZ4SFlxoas0}-qM0Va;nAXa%(Dw*SOsATD-!H3s_qR2lVQN3a#0bP7ni+^$fS47C z*?^cGh&h0m6NtHhm>Y^7;-6^6Zf0d*WMyj7o?^=n!~#Gp2*g4_EDXdV NKrFgF#a7Iv7yustadium_vm_id, word_id); #endif if (vm->word_id_map[word_id] == entry) { diff --git a/src/starkernel/capsule/capsule_birth.c b/src/starkernel/capsule/capsule_birth.c index e526c60..bf42144 100644 --- a/src/starkernel/capsule/capsule_birth.c +++ b/src/starkernel/capsule/capsule_birth.c @@ -475,6 +475,11 @@ CapsuleRunResult capsule_birth_baby( return CAPSULE_RUN_ERR_STILLBORN; } + /* item 4.2: set before the IDENTITY exec below, so any word this baby + * dispatches during her own init capsule already attributes heat to her + * own reservoir, not vm_uuid_hera()'s (item 4.1's hardcoded default). */ + ((VM *)new_vm)->stadium_vm_id = vm_id; + const uint8_t *payload = capsule_get_payload(cap, arena); if (!payload) { entry->state = VM_STATE_STILLBORN; diff --git a/src/starkernel/capsule/mama_forth_words.c b/src/starkernel/capsule/mama_forth_words.c index 580d6f4..8649a3d 100644 --- a/src/starkernel/capsule/mama_forth_words.c +++ b/src/starkernel/capsule/mama_forth_words.c @@ -44,6 +44,8 @@ #include "starkernel/capsule_loader.h" #include "starkernel/capsule_vm_physics.h" #include "starkernel/vm/vm_internal.h" +#include "starkernel/vm/stadium.h" /* item 4.2 -- STADIUM-* words */ +#include "starkernel/vm/stadium_words.h" /* item 4.2 -- stadium_words_resident_heat() */ #include "starkernel/repl.h" #include "starkernel/capsule_generated.h" #include "starkernel/console.h" @@ -1156,16 +1158,205 @@ void register_mama_forth_words(VM *vm) vocabulary_word_definitions(vm); } +/* ============================================================================ + * Stadium Words (FABRIC.md punch list item 4.2) + * + * The entire C surface item 4.2 is permitted to add, per HERMES.md's + * language constraint: all eight operate on the CALLING VM's own identity + * (vm->stadium_vm_id) implicitly, never a FORTH-supplied vm-id. A + * stack-passed vm-id could only ever be the caller's own (redundant) or + * another VM's (stadium_admit()/stadium_evict() would refuse it via quota + * isolation, except STADIUM-RES-PUSH, which has no such guard and would be + * an outright heat-forgery primitive against another VM's reservoir). + * Conservation is the invariant this item is verified against, so implicit + * self is not an optimization -- it's the only version that can't break it. + * + * STADIUM-ADMIT does not itself pull the candidate's heat from the + * reservoir -- that's STADIUM-RES-PULL's job, composed in FORTH by the + * caller (e.g. a rewritten MSG-ALLOC): pull first, admit with the pulled + * amount, and STADIUM-RES-PUSH it back if admission refuses. Mirrors + * stadium_words.c's C-side Option B starter-grant pattern, but the + * composition itself lives in StarForth, not here, per HERMES.md. + * ============================================================================ */ + +/** + * @brief STADIUM-ADMIT ( identity heat behaviour -- cell | -1 ) + * Admits a mass-1 patron into the calling VM's own Stadium quota. + * `behaviour` must be a valid StadiumBehaviour tag (0..3); anything else + * refuses without calling stadium_admit() at all. `contains` is always + * explicitly STADIUM_CONTAINS_NONE -- stadium_admit()'s own doc warns a + * zero-initialized `contains` reads as "contains Hera" (index 0) and + * permanently blocks eviction, so this is never left to a zero-fill. + */ +static void mama_word_stadium_admit(VM *vm) +{ + cell_t behaviour_cell, heat_cell, identity_cell; + StadiumPatronHeader candidate; + uint8_t *raw = (uint8_t *)&candidate; + size_t i; + size_t idx; + + if (vm->dsp < 2) { vm->error = 1; return; } + + behaviour_cell = vm_pop(vm); + heat_cell = vm_pop(vm); + identity_cell = vm_pop(vm); + + if (behaviour_cell < STADIUM_BEHAVIOUR_MIGRATE || behaviour_cell > STADIUM_BEHAVIOUR_COOL) { + vm_push(vm, (cell_t)-1); + return; + } + + for (i = 0; i < sizeof(candidate); i++) raw[i] = 0; + candidate.identity = (uint64_t)identity_cell; + candidate.heat = (uint64_t)heat_cell; + candidate.ttl = 0; + candidate.link = 0; + candidate.contains = STADIUM_CONTAINS_NONE; + candidate.mass = 1; + candidate.flags = 0; + candidate.behaviour = (uint8_t)behaviour_cell; + + idx = stadium_admit(vm->stadium_vm_id, &candidate); + vm_push(vm, (idx == STADIUM_CELL_NONE) ? (cell_t)-1 : (cell_t)idx); +} + +/** + * @brief STADIUM-EVICT ( cell -- flag ) + * Reaps the patron header at `cell`. flag is FORTH true (-1) on success, + * false (0) if refused (out of range, not resident, pinned, or contains- + * gated) -- stadium_evict()'s own refusal set, unchanged here. + */ +static void mama_word_stadium_evict(VM *vm) +{ + cell_t cell_cell; + + if (vm->dsp < 0) { vm->error = 1; return; } + + cell_cell = vm_pop(vm); + if (cell_cell < 0) { + vm_push(vm, (cell_t)0); + return; + } + vm_push(vm, (stadium_evict((size_t)cell_cell) == 0) ? (cell_t)-1 : (cell_t)0); +} + +/** + * @brief STADIUM-RES@ ( -- heat ) + * Read-only peek at the calling VM's own reservoir balance (Q48.16). + */ +static void mama_word_stadium_res_fetch(VM *vm) +{ + vm_push(vm, (cell_t)stadium_reservoir_peek(vm->stadium_vm_id)); +} + +/** + * @brief STADIUM-WORD-HEAT ( -- heat ) + * Sum of heat held by the calling VM's own word-execution residents + * (item 4.1's cells) -- the term a VM's own application-level conservation + * check (e.g. Hermes's HERMES-K) needs to close exactly, since word patrons + * are otherwise invisible to FORTH (FABRIC.md §25.7, ruling 2026-08-06). + */ +static void mama_word_stadium_word_heat(VM *vm) +{ + vm_push(vm, (cell_t)stadium_words_resident_heat(vm->stadium_vm_id)); +} + +/** + * @brief STADIUM-RES-PULL ( qty -- heat ) + * Pulls up to `qty` (Q48.16) from the calling VM's own reservoir. Returns + * the amount actually pulled, which may be less than requested -- never + * negative, never invents heat, mirrors stadium_reservoir_pull()'s own + * clamping exactly. + */ +static void mama_word_stadium_res_pull(VM *vm) +{ + cell_t qty_cell; + + if (vm->dsp < 0) { vm->error = 1; return; } + + qty_cell = vm_pop(vm); + if (qty_cell < 0) { + vm_push(vm, (cell_t)0); + return; + } + vm_push(vm, (cell_t)stadium_reservoir_pull(vm->stadium_vm_id, (uint64_t)qty_cell)); +} + +/** + * @brief STADIUM-RES-PUSH ( heat -- ) + * Credits `heat` (Q48.16) back into the calling VM's own reservoir -- the + * other half of every reservoir transfer (cooling, refused-admission + * rollback, or a departing patron's remaining heat after eviction). + */ +static void mama_word_stadium_res_push(VM *vm) +{ + cell_t heat_cell; + + if (vm->dsp < 0) { vm->error = 1; return; } + + heat_cell = vm_pop(vm); + if (heat_cell < 0) return; + stadium_reservoir_push(vm->stadium_vm_id, (uint64_t)heat_cell); +} + +/** + * @brief STADIUM-HEAT@ ( cell -- heat ) + * Reads a resident cell's own heat. Requires the cell to be resident and + * owned by the calling VM's own quota -- returns 0 otherwise (out of range, + * not resident, or belongs to a different VM). + */ +static void mama_word_stadium_heat_fetch(VM *vm) +{ + cell_t cell_cell; + + if (vm->dsp < 0) { vm->error = 1; return; } + + cell_cell = vm_pop(vm); + if (cell_cell < 0) { + vm_push(vm, (cell_t)0); + return; + } + vm_push(vm, (cell_t)stadium_cell_heat_get(vm->stadium_vm_id, (size_t)cell_cell)); +} + +/** + * @brief STADIUM-HEAT! ( new-heat cell -- ) + * Writes a resident cell's own heat, reconciling the reservoir delta + * atomically in C (pulls on an increase, refusing silently if the + * calling VM's reservoir can't cover it; pushes back on a decrease). + * Requires the cell to be resident and owned by the calling VM's own + * quota -- silently refused otherwise, same as every other write here. + */ +static void mama_word_stadium_heat_store(VM *vm) +{ + cell_t cell_cell, new_heat_cell; + + if (vm->dsp < 1) { vm->error = 1; return; } + + cell_cell = vm_pop(vm); + new_heat_cell = vm_pop(vm); + if (cell_cell < 0 || new_heat_cell < 0) return; + (void)stadium_cell_heat_set(vm->stadium_vm_id, (size_t)cell_cell, (uint64_t)new_heat_cell); +} + /** * register_child_vm_words - Register the minimal word set needed by child VMs. * * Child VMs are not bootstrapped through sk_vm_bootstrap_parity, so they * do not get register_mama_forth_words(). They only need STOP (self-halt) - * and EXEC (load a capsule). Keeping the registrations here — in the same - * translation unit as the word functions — avoids cross-TU function-pointer - * loads that produce R_X86_64_REX_GOTPCRELX relocations; those are not - * relaxed by the PE32+ linker, causing the function code bytes to be read - * as the pointer value instead of the actual address. + * and EXEC (load a capsule) -- plus, as of item 4.2, the eight STADIUM-* + * primitives Hermes needs to migrate her message/channel lifecycle onto the + * Stadium. Keeping the registrations here — in the same translation unit + * as the word functions — avoids cross-TU function-pointer loads that + * produce R_X86_64_REX_GOTPCRELX relocations; those are not relaxed by the + * PE32+ linker, causing the function code bytes to be read as the pointer + * value instead of the actual address. + * + * Deliberately NOT added to register_mama_forth_words(): that would put + * these words in Hera's own dictionary too and move dict_hash off item + * 4.1's baseline (0x3d4e1daf289da94f) -- a deliberate baseline change to + * state this item does not make as a side effect. */ void register_child_vm_words(VM *vm) { @@ -1173,6 +1364,14 @@ void register_child_vm_words(VM *vm) register_word(vm, "EXEC", mama_word_exec); register_word(vm, "VM-EXEC", mama_word_vm_exec); register_word(vm, "VM-CALL", mama_word_vm_call); + register_word(vm, "STADIUM-ADMIT", mama_word_stadium_admit); + register_word(vm, "STADIUM-EVICT", mama_word_stadium_evict); + register_word(vm, "STADIUM-RES@", mama_word_stadium_res_fetch); + register_word(vm, "STADIUM-RES-PULL", mama_word_stadium_res_pull); + register_word(vm, "STADIUM-RES-PUSH", mama_word_stadium_res_push); + register_word(vm, "STADIUM-HEAT@", mama_word_stadium_heat_fetch); + register_word(vm, "STADIUM-HEAT!", mama_word_stadium_heat_store); + register_word(vm, "STADIUM-WORD-HEAT", mama_word_stadium_word_heat); } #endif /* __STARKERNEL__ */ diff --git a/src/starkernel/kernel_main.c b/src/starkernel/kernel_main.c index 3945ca5..86bbd67 100644 --- a/src/starkernel/kernel_main.c +++ b/src/starkernel/kernel_main.c @@ -165,6 +165,30 @@ static void print_uint(const char *label, uint64_t value) { console_println(buf); } +#ifdef STARFORTH_ENABLE_VM +/** + * @brief Verify a known word is still reachable in a VM's dictionary. + * + * Diagnostic-only: confirms @c vm_find_word() can still walk the chain + * from @c vm->latest to a word defined early in Hermes's capsule + * (MSG-COOL-ALL, block 4108). Added after item 4.2's self-test bisection + * found an amd64-only in-place corruption of an existing dictionary entry + * during MSG-DELIVER-ALL -- here/latest stay unchanged (no reallocation), + * so a lookup is a cheap signal. NOTE: an earlier version of this comment + * attributed the corruption to GDB perturbing execution timing; that is + * unconfirmed and more likely just a parity/dict-hash boot-gate failure + * triggered by the debugger session itself (a software breakpoint's 0xCC + * patch landing in memory the loader then overwrote) -- don't propagate + * "timing-sensitive" as an established finding. + */ +static void hermes_dict_check(VM *hermes_vm, const char *checkpoint) { + DictEntry *e = vm_find_word(hermes_vm, "MSG-COOL-ALL", 12); + console_puts(" Dict-check "); + console_puts(checkpoint); + console_println(e ? ": OK" : ": FAIL (MSG-COOL-ALL unreachable)"); +} +#endif + /** * @brief Print a boot-information summary from the UEFI memory map to the console. * @@ -648,6 +672,101 @@ static void kernel_main_deep(BootInfo *boot_info) { } } + /* item 4.2 self-test: a REAL birth (not synthetic, unlike 4.1a's -- + * this exercises capsules/hermes/init.4th's actual migrated code), + * exercised then killed again so the resting boot state stays + * Hera-alone, per item 0.1's intent. Diagnostic only -- production + * boot still never auto-births Hermes (init.4th's BIRTH stays + * commented out). */ + console_println("Hermes 4.2 migration self-test: birthing..."); + vm_interpret(mama, "S\" Hermes\" BIRTH"); + { + VMRegistryEntry entry; + if (capsule_vm_find_by_name_nocase("Hermes", &entry) == 0 && + entry.state == VM_STATE_LIVE) { + VM *hermes_vm = (VM *)entry.vm_ptr; + console_println("Hermes 4.2 self-test: exercising migrated words..."); + hermes_dict_check(hermes_vm, "at self-test start"); + hermes_vm->error = 0; + vm_interpret(hermes_vm, "CD-INIT"); + print_uint(" DBG err after CD-INIT=", (uint64_t)hermes_vm->error); + hermes_dict_check(hermes_vm, "after CD-INIT"); + hermes_vm->error = 0; + vm_interpret(hermes_vm, "HERMES-MSG-TEST . CR"); + print_uint(" DBG err after MSG-TEST=", (uint64_t)hermes_vm->error); + hermes_dict_check(hermes_vm, "after MSG-TEST"); + hermes_vm->error = 0; + vm_interpret(hermes_vm, "HERMES-STATUS"); + print_uint(" DBG err after STATUS=", (uint64_t)hermes_vm->error); + hermes_dict_check(hermes_vm, "after STATUS"); + hermes_vm->error = 0; + vm_interpret(hermes_vm, "MSG-DELIVER-ALL"); + print_uint(" DBG err after DELIVER-ALL=", (uint64_t)hermes_vm->error); + hermes_dict_check(hermes_vm, "after DELIVER-ALL"); + hermes_vm->error = 0; + vm_interpret(hermes_vm, "MSG-REDELIVER-NACKED"); + print_uint(" DBG err after REDELIVER=", (uint64_t)hermes_vm->error); + hermes_vm->error = 0; + vm_interpret(hermes_vm, "MSG-COOL-ALL"); + print_uint(" DBG err after MSG-COOL-ALL=", (uint64_t)hermes_vm->error); + hermes_vm->error = 0; + vm_interpret(hermes_vm, "MSG-REAP"); + print_uint(" DBG err after MSG-REAP=", (uint64_t)hermes_vm->error); + hermes_vm->error = 0; + vm_interpret(hermes_vm, "CH-COOL-ALL CH-REAP-SAFE"); + print_uint(" DBG err after CH-COOL/REAP=", (uint64_t)hermes_vm->error); + hermes_vm->error = 0; + vm_interpret(hermes_vm, "Q.1 3 / COMMON-CH @ CH-HEAT!"); + print_uint(" DBG err after CH-HEAT!=", (uint64_t)hermes_vm->error); + hermes_vm->error = 0; + vm_interpret(hermes_vm, "HERMES-STATUS"); + print_uint(" DBG err after TICK=", (uint64_t)hermes_vm->error); + hermes_vm->error = 0; + console_puts("Hermes 4.2 self-test: HERMES-K="); + vm_interpret(hermes_vm, "HERMES-K ."); + console_println(""); + print_uint(" DBG err after HERMES-K=", (uint64_t)hermes_vm->error); + /* Side-by-side with the Stadium's own view, same checkpoint -- + * if these two disagree, HERMES-K's FORTH-side arena scan is + * seeing different residents than stadium_resident_sum()'s + * ownership+bitmap view (a real, separate finding, not the + * Q.SLOT admission-heat fix's job to explain). */ + print_uint(" DBG stadium_resident_sum(Hermes)=", stadium_resident_sum(entry.vm_id)); + print_uint(" DBG stadium_reservoir_peek(Hermes)=", stadium_reservoir_peek(entry.vm_id)); + /* item 4.2 Done-when: "a resident cell's evict-credit landing + * in the correct VM's reservoir ... not just asserted from + * reading the code" -- explicitly evict the common channel + * (CH-FREE-NODE -> STADIUM-EVICT), a known resident from + * CD-INIT's own COMMON-INIT, same credit path stadium_admit()'s + * density-fallback eviction uses. resident_sum dropping and + * reservoir rising by the same amount is the proof: the freed + * cell's heat landed back in Hermes's own reservoir, not lost + * or credited to Hera. */ + console_println("Hermes 4.2 self-test: before eviction:"); + print_uint(" Hermes resident_sum=", stadium_resident_sum(entry.vm_id)); + print_uint(" Hermes reservoir=", stadium_reservoir_peek(entry.vm_id)); + console_println("Hermes 4.2 self-test: forcing an explicit eviction (COMMON-CH)..."); + hermes_vm->error = 0; + vm_interpret(hermes_vm, "COMMON-CH @ . CR"); + print_uint(" DBG err after COMMON-CH@=", (uint64_t)hermes_vm->error); + hermes_vm->error = 0; + vm_interpret(hermes_vm, "COMMON-CH @ CH-FREE-NODE"); + print_uint(" DBG err after CH-FREE-NODE=", (uint64_t)hermes_vm->error); + console_println("Hermes 4.2 self-test: after eviction:"); + print_uint(" Hermes resident_sum=", stadium_resident_sum(entry.vm_id)); + print_uint(" Hermes reservoir=", stadium_reservoir_peek(entry.vm_id)); + /* item 4.2 Done-when: both VMs' conservation checks close + * independently -- Hermes's own resident+reservoir sum first, + * then Hera's again (unaffected by Hermes's activity above). */ + stadium_words_print_boot_diagnostics(entry.vm_id); + vm_interpret(mama, "S\" Hermes\" KILL"); + console_println("Hermes 4.2 self-test: killed, resting state restored"); + stadium_words_print_boot_diagnostics(vm_uuid_hera()); + } else { + console_println("Hermes 4.2 self-test: birth registry lookup FAILED"); + } + } + /* * Runtime --doe flag: inject "EXEC-DOE BYE" if requested via boot args. * Checked before SK_STARTUP_FORTH so a runtime --doe takes precedence. diff --git a/src/starkernel/vm/bootstrap/sk_vm_bootstrap.c b/src/starkernel/vm/bootstrap/sk_vm_bootstrap.c index 0258da3..2715fc5 100644 --- a/src/starkernel/vm/bootstrap/sk_vm_bootstrap.c +++ b/src/starkernel/vm/bootstrap/sk_vm_bootstrap.c @@ -271,6 +271,7 @@ int sk_vm_bootstrap_parity(ParityPacket *out) { * capsule_birth_baby() which never calls vm_init_with_host(). */ capsule_vm_hooks_register(); capsule_vm_registry_init(vm); /* establishes [Hera] console prefix */ + vm->stadium_vm_id = vm_uuid_hera(); /* item 4.2 */ vm_physics_init(vm_uuid_hera()); /* Hera: the fleet's root, seeded Q48_ONE */ capsule_run_log_init(); register_mama_forth_words(vm); /* BIRTH KILL START STOP USE + capsule words */ diff --git a/src/starkernel/vm/stadium.c b/src/starkernel/vm/stadium.c index 88c00ff..5473cfc 100644 --- a/src/starkernel/vm/stadium.c +++ b/src/starkernel/vm/stadium.c @@ -358,6 +358,10 @@ size_t stadium_admit(VMUuid vm_id, const StadiumPatronHeader *candidate) { stadium_quotas[slot].free_head = link_to_size(stadium_cell_array[idx].header.link); stadium_cell_array[idx].header = *candidate; bitmap_set(idx); + /* Item 4.2 fix (§25.7): record ownership so stadium_evict()'s + * reservoir credit and free-list return land on the VM that actually + * admitted this patron, not whatever owner[idx] held at boot. */ + stadium_owner[idx] = (uint8_t)slot; return idx; } @@ -395,6 +399,11 @@ size_t stadium_admit(VMUuid vm_id, const StadiumPatronHeader *candidate) { stadium_quotas[slot].free_head = link_to_size(stadium_cell_array[idx].header.link); stadium_cell_array[idx].header = *candidate; bitmap_set(idx); + /* Same fix as the free-list-pop path above -- stadium_evict() just wrote + * owner[idx] = slot as part of reaping least_dense_index, so this is + * currently a no-op in practice, but it must not be assumed to stay a + * no-op: this is the correctness statement, not a redundant write. */ + stadium_owner[idx] = (uint8_t)slot; return idx; } @@ -453,6 +462,45 @@ int stadium_grant_quota(VMUuid new_vm_id, VMUuid from_vm_id) { return 0; } +/* Shared by stadium_cell_heat_get()/_set(): resident AND owned by vm_id's + * own quota slot. Returns the quota slot on success, -1 on any refusal. */ +static int owned_resident_slot(VMUuid vm_id, size_t cell_index) { + int slot = quota_slot_for_vm(vm_id); + if (slot < 0) return -1; + if (cell_index >= stadium_ncells) return -1; + if (!bitmap_get(cell_index)) return -1; + if (stadium_owner[cell_index] != (uint8_t)slot) return -1; + return slot; +} + +uint64_t stadium_cell_heat_get(VMUuid vm_id, size_t cell_index) { + if (owned_resident_slot(vm_id, cell_index) < 0) return 0; + return stadium_cell_array[cell_index].header.heat; +} + +int stadium_cell_heat_set(VMUuid vm_id, size_t cell_index, uint64_t new_heat) { + int slot = owned_resident_slot(vm_id, cell_index); + uint64_t old_heat, delta, pulled; + + if (slot < 0) return -1; + + old_heat = stadium_cell_array[cell_index].header.heat; + if (new_heat == old_heat) return 0; + + if (new_heat > old_heat) { + delta = new_heat - old_heat; + pulled = (delta > stadium_quotas[slot].reservoir) ? stadium_quotas[slot].reservoir : delta; + if (pulled < delta) return -1; /* insufficient -- no partial credit, no mutation */ + stadium_quotas[slot].reservoir -= pulled; + } else { + delta = old_heat - new_heat; + stadium_quotas[slot].reservoir += delta; + } + + stadium_cell_array[cell_index].header.heat = new_heat; + return 0; +} + uint64_t stadium_reservoir_pull(VMUuid vm_id, uint64_t amount) { int slot = quota_slot_for_vm(vm_id); uint64_t pulled; @@ -478,6 +526,25 @@ uint64_t stadium_reservoir_peek(VMUuid vm_id) { return stadium_quotas[slot].reservoir; } +int stadium_quota_slot_for_vm(VMUuid vm_id) { + return quota_slot_for_vm(vm_id); +} + +uint64_t stadium_resident_sum(VMUuid vm_id) { + int slot = quota_slot_for_vm(vm_id); + uint64_t sum = 0; + size_t i; + + if (slot < 0) return 0; + + for (i = 0; i < stadium_ncells; i++) { + if (!bitmap_get(i)) continue; + if (stadium_owner[i] != (uint8_t)slot) continue; + sum += stadium_cell_array[i].header.heat; + } + return sum; +} + /* * FABRIC.md item 3.6 / item 4.1: see stadium.h's doc. Idempotent via the * item-3.1 discriminator bitmap -- if cell 0 already reads as resident, diff --git a/src/starkernel/vm/stadium_words.c b/src/starkernel/vm/stadium_words.c index 27595d9..327e074 100644 --- a/src/starkernel/vm/stadium_words.c +++ b/src/starkernel/vm/stadium_words.c @@ -40,25 +40,37 @@ * 2026-08-05: no DictEntry field). `last_decay_tick` is this layer's own * bookkeeping, separate from DictEntry.physics.last_decay_tick -- that field * belongs to execution_heat's decay, which item 4.1 does not touch. + * + * item 4.2 fix (FABRIC.md §25.5): keyed by [quota slot][word_id], not just + * word_id. word_id is assigned per-VM (vm->next_word_id in + * dictionary_management.c), not globally unique -- a single shared + * word_id -> cell_index map let two VMs' independently-numbered word_ids + * (e.g. both VMs' own "DUP") alias onto the same slot, so one VM's dispatch + * could cool/heat-pump a cell it did not own and credit/debit the wrong + * VM's reservoir. Exposed only because item 4.2 restored a second VM + * (Hermes) with her own dictionary; invisible with Hera alone. */ typedef struct { size_t cell_index; /* STADIUM_CELL_NONE if not resident */ uint64_t last_decay_tick; } StadiumWordSlot; -static StadiumWordSlot word_slots[DICTIONARY_SIZE]; +static StadiumWordSlot word_slots[STADIUM_MAX_VM_COUNT][DICTIONARY_SIZE]; static int words_initialized = 0; -static uint64_t stat_promotions = 0; -static uint64_t stat_evictions = 0; +static uint64_t stat_promotions[STADIUM_MAX_VM_COUNT]; +static uint64_t stat_evictions[STADIUM_MAX_VM_COUNT]; void stadium_words_init(void) { + int slot; uint32_t i; - for (i = 0; i < DICTIONARY_SIZE; i++) { - word_slots[i].cell_index = STADIUM_CELL_NONE; - word_slots[i].last_decay_tick = 0; + for (slot = 0; slot < STADIUM_MAX_VM_COUNT; slot++) { + for (i = 0; i < DICTIONARY_SIZE; i++) { + word_slots[slot][i].cell_index = STADIUM_CELL_NONE; + word_slots[slot][i].last_decay_tick = 0; + } + stat_promotions[slot] = 0; + stat_evictions[slot] = 0; } - stat_promotions = 0; - stat_evictions = 0; words_initialized = 1; } @@ -68,6 +80,29 @@ static int cell_is_resident(size_t idx) { return (bm[idx / 8u] >> (idx % 8u)) & 1u; } +/* + * word_dispatch_pull - Reservoir pull for word-execution admission, clamped + * to leave a floor for application-level use (FABRIC.md §25.5/§25.7, + * Captain Bob's ruling 2026-08-06). Without this, stadium_word_dispatch() + * pulling STADIUM_WORD_HEAT_QUANTUM on every dispatch -- not just the first + * admission of a given word -- exhausts a VM's entire reservoir within + * roughly 32 total dispatches (65536 / 2048), starving any item-4.2-style + * application economy sharing the same VM's reservoir before it gets a + * chance to pull anything. The floor is Q48_ONE / 3, the same "VM-COUNT=3 + * fair share" reasoning capsules/hermes/init.4th's COMMON-CH floor already + * uses -- not a new invented number. Application-level pulls + * (stadium_reservoir_pull() called directly, e.g. via STADIUM-RES-PULL) are + * NOT floored -- only word-execution admission respects this ceiling on + * its own consumption. + */ +static uint64_t word_dispatch_pull(VMUuid vm_id, uint64_t want) { + uint64_t available = stadium_reservoir_peek(vm_id); + uint64_t floor = Q48_ONE / 3; + uint64_t pullable = (available > floor) ? (available - floor) : 0; + uint64_t capped = (want < pullable) ? want : pullable; + return stadium_reservoir_pull(vm_id, capped); +} + /* * resolve_resident_cell - Self-healing lookup (advisor-flagged reverse * coherence gap): the map may claim word_id is resident at a cell that was @@ -77,36 +112,59 @@ static int cell_is_resident(size_t idx) { * no new coupling from stadium.c into this file. A stale mapping is cleared * and counted as an eviction on discovery. */ -static size_t resolve_resident_cell(uint32_t word_id) { - size_t cell = word_slots[word_id].cell_index; +static size_t resolve_resident_cell(int slot, uint32_t word_id) { + size_t cell = word_slots[slot][word_id].cell_index; StadiumCell *cells; if (cell == STADIUM_CELL_NONE) return STADIUM_CELL_NONE; if (cell >= stadium_cell_count() || !cell_is_resident(cell)) { - word_slots[word_id].cell_index = STADIUM_CELL_NONE; - stat_evictions++; + word_slots[slot][word_id].cell_index = STADIUM_CELL_NONE; + stat_evictions[slot]++; return STADIUM_CELL_NONE; } cells = stadium_cells(); if (cells[cell].header.identity != (uint64_t)word_id) { - word_slots[word_id].cell_index = STADIUM_CELL_NONE; - stat_evictions++; + word_slots[slot][word_id].cell_index = STADIUM_CELL_NONE; + stat_evictions[slot]++; return STADIUM_CELL_NONE; } return cell; } +uint64_t stadium_words_resident_heat(VMUuid vm_id) { + int slot; + uint32_t i; + uint64_t sum = 0; + StadiumCell *cells; + + if (!words_initialized) return 0; + slot = stadium_quota_slot_for_vm(vm_id); + if (slot < 0) return 0; + + cells = stadium_cells(); + for (i = 0; i < DICTIONARY_SIZE; i++) { + size_t cell = resolve_resident_cell(slot, i); + if (cell == STADIUM_CELL_NONE) continue; + sum += cells[cell].header.heat; + } + return sum; +} + void stadium_word_dispatch(VMUuid vm_id, uint32_t word_id, uint64_t heartbeat_ticks) { + int slot; size_t cell; if (!words_initialized) return; if (word_id == WORD_ID_INVALID || word_id >= DICTIONARY_SIZE) return; - cell = resolve_resident_cell(word_id); + slot = stadium_quota_slot_for_vm(vm_id); + if (slot < 0) return; + + cell = resolve_resident_cell(slot, word_id); if (cell != STADIUM_CELL_NONE) { StadiumPatronHeader *h = &stadium_cells()[cell].header; - uint64_t elapsed = heartbeat_ticks - word_slots[word_id].last_decay_tick; + uint64_t elapsed = heartbeat_ticks - word_slots[slot][word_id].last_decay_tick; if (elapsed > 0) { /* Redirected Loop #3 (§17.7): a FRACTION of the cell's own @@ -120,17 +178,17 @@ void stadium_word_dispatch(VMUuid vm_id, uint32_t word_id, uint64_t heartbeat_ti h->heat -= cooled; stadium_reservoir_push(vm_id, cooled); } - word_slots[word_id].last_decay_tick = heartbeat_ticks; + word_slots[slot][word_id].last_decay_tick = heartbeat_ticks; } - h->heat += stadium_reservoir_pull(vm_id, (uint64_t)STADIUM_WORD_HEAT_QUANTUM); + h->heat += word_dispatch_pull(vm_id, (uint64_t)STADIUM_WORD_HEAT_QUANTUM); return; } /* Not resident: Option B starter-grant admission (§17.7). execution_heat * plays no role -- density is decided entirely by the pulled quantum. */ { - uint64_t pulled = stadium_reservoir_pull(vm_id, (uint64_t)STADIUM_WORD_HEAT_QUANTUM); + uint64_t pulled = word_dispatch_pull(vm_id, (uint64_t)STADIUM_WORD_HEAT_QUANTUM); StadiumPatronHeader candidate; uint8_t *raw = (uint8_t *)&candidate; size_t i; @@ -152,30 +210,36 @@ void stadium_word_dispatch(VMUuid vm_id, uint32_t word_id, uint64_t heartbeat_ti return; } - word_slots[word_id].cell_index = idx; - word_slots[word_id].last_decay_tick = heartbeat_ticks; - stat_promotions++; + word_slots[slot][word_id].cell_index = idx; + word_slots[slot][word_id].last_decay_tick = heartbeat_ticks; + stat_promotions[slot]++; } } -void stadium_word_forget(uint32_t word_id) { +void stadium_word_forget(VMUuid vm_id, uint32_t word_id) { + int slot; size_t cell; if (!words_initialized) return; if (word_id == WORD_ID_INVALID || word_id >= DICTIONARY_SIZE) return; - cell = resolve_resident_cell(word_id); + slot = stadium_quota_slot_for_vm(vm_id); + if (slot < 0) return; + + cell = resolve_resident_cell(slot, word_id); if (cell == STADIUM_CELL_NONE) return; if (stadium_evict(cell) == 0) { - word_slots[word_id].cell_index = STADIUM_CELL_NONE; - stat_evictions++; + word_slots[slot][word_id].cell_index = STADIUM_CELL_NONE; + stat_evictions[slot]++; } } -void stadium_words_stats(uint64_t *promotions, uint64_t *evictions) { - if (promotions) *promotions = stat_promotions; - if (evictions) *evictions = stat_evictions; +void stadium_words_stats(VMUuid vm_id, uint64_t *promotions, uint64_t *evictions) { + int slot = stadium_quota_slot_for_vm(vm_id); + + if (promotions) *promotions = (slot >= 0) ? stat_promotions[slot] : 0; + if (evictions) *evictions = (slot >= 0) ? stat_evictions[slot] : 0; } /* Freestanding: no libc printf. Prints an unsigned decimal, no leading @@ -197,19 +261,16 @@ static void console_put_u64(uint64_t v) { void stadium_words_print_boot_diagnostics(VMUuid vm_id) { uint64_t promotions = 0, evictions = 0; - uint64_t resident_sum = 0; + uint64_t resident_sum; uint64_t reservoir; - size_t ncells = stadium_cell_count(); - size_t i; - stadium_words_stats(&promotions, &evictions); + stadium_words_stats(vm_id, &promotions, &evictions); - for (i = 0; i < ncells; i++) { - if (cell_is_resident(i)) { - resident_sum += stadium_cells()[i].header.heat; - } - } - reservoir = stadium_reservoir_peek(vm_id); + /* item 4.2 fix (FABRIC.md §25.5): filtered per-VM -- with two VMs + * holding quotas, summing every resident cell regardless of owner + * (the pre-4.2 behavior) mixed both VMs' conservation totals together. */ + resident_sum = stadium_resident_sum(vm_id); + reservoir = stadium_reservoir_peek(vm_id); console_puts("Stadium words: promotions="); console_put_u64(promotions); diff --git a/src/starkernel/vm/vm_core.c b/src/starkernel/vm/vm_core.c index 11577ed..53a19b3 100644 --- a/src/starkernel/vm/vm_core.c +++ b/src/starkernel/vm/vm_core.c @@ -684,9 +684,10 @@ void execute_colon_word(VM* vm) /* item 4.1, FABRIC.md §17.7: feed the Stadium's independent * conserved heat wire. execution_heat above is untouched by - * this call. vm_uuid_hera() is hardcoded here -- Tripod is - * pruned to Hera alone (item 0.1); revisit at item 4.2. */ - stadium_word_dispatch(vm_uuid_hera(), w->word_id, vm->heartbeat.tick_count); + * this call. item 4.2: dispatching VM's own identity, not the + * item-4.1 hardcoded vm_uuid_hera() -- refused harmlessly by + * stadium_admit() for any VM without a granted quota. */ + stadium_word_dispatch(vm->stadium_vm_id, w->word_id, vm->heartbeat.tick_count); uint32_t word_id = w->word_id; if (word_id < DICTIONARY_SIZE) @@ -881,7 +882,7 @@ void vm_interpret_word(VM* vm, const char* word_str, size_t len) entry->physics.last_decay_ns = lookup_ns; physics_execution_heat_increment(entry); - stadium_word_dispatch(vm_uuid_hera(), entry->word_id, vm->heartbeat.tick_count); + stadium_word_dispatch(vm->stadium_vm_id, entry->word_id, vm->heartbeat.tick_count); if (canon && canon != entry) { /* Apply decay to canonical entry as well */ @@ -892,7 +893,7 @@ void vm_interpret_word(VM* vm, const char* word_str, size_t len) canon->physics.last_decay_ns = lookup_ns; physics_execution_heat_increment(canon); - stadium_word_dispatch(vm_uuid_hera(), canon->word_id, vm->heartbeat.tick_count); + stadium_word_dispatch(vm->stadium_vm_id, canon->word_id, vm->heartbeat.tick_count); physics_metadata_touch(canon, canon->execution_heat, lookup_ns); } sf_mutex_unlock(&vm->dict_lock);