starkernel: item 3.7 -- per-VM free lists (Phase 3 core complete, for real)

Punch list §25 item 3.7 complete. Added to §25.4 after starting item
4.1 surfaced it as an unbuilt prerequisite -- 3.6's earlier "Phase 3
core complete" claim is corrected in this same commit.

StadiumVMQuota table (size STADIUM_MAX_VM_COUNT, linearly searched by
vm_id -- capsule_birth.c's vm_id is monotonic and never reused, so it
cannot index a table directly, and a 4-entry scan costs nothing). New
per-cell stadium_owner byte array records which quota a cell belongs
to, needed so eviction returns a freed cell to the correct VM's list
and so eviction search stays scoped to the evicting VM's own residents
(quota isolation).

Free-list linkage reuses each cell's `link` field as a next-free
pointer while unresident -- link is documented only as generic "index
into the Stadium, not a pointer," so this is a repurposing, not a
header change. Does not answer the separate, still-open question of
which field carries a multi-cell patron's first continuation-cell
index; item 3.5's mass != 1 refusal stands exactly as it was.

Boot-time: every cell chained into one list in ascending index order,
granted whole to vm_id 0 (Hera), the only VM that exists. Ascending
order preserves item 3.6's "Hera is patron zero" invariant once real
birth-wiring lands.

stadium_admit()'s signature changed to take vm_id -- a change to code
shipped in item 3.5, amended there. Pops the calling VM's free-list
head first (O(1)); only falls back to a same-VM-scoped eviction search
if empty.

Caught a real bug before the boot run: the header zero-fill on
eviction (and the initial free-list build) both left contains == 0,
but 0 is Hera's valid index -- the same collision item 3.1's
STADIUM_CONTAINS_NONE fix addressed, recurring at a new site. Fixed by
explicitly setting contains = STADIUM_CONTAINS_NONE at both free-list
sites.

Explicitly out of scope, reported not invented: granting quota to any
VM other than Hera is capacity arbitration (item 1.3 left "how much
moves per transfer" open). stadium_owner is set once at boot and never
rewritten, so quota_slot_for_vm() refuses every vm_id != 0 permanently
until item 4.2 adds the grant path and owner-array writes.

Verified: three-architecture boot (amd64, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the
item-3.6 baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-04 18:09:34 -04:00
co-authored by Claude Sonnet 5
parent 72487e7fff
commit e55111c2c5
10 changed files with 31563 additions and 48 deletions
+75 -5
View File
@@ -2855,6 +2855,13 @@ document and committing that amendment as its own item.*
> `dict_hash=0x3d4e1daf289da94f`, matching the item-3.4 baseline — amd64
> (`logs/20260804-172516`), aarch64 (`logs/20260804-172556`), riscv64
> (`logs/20260804-172651`).
>
> **Amended by item 3.7, 2026-08-04, same day.** `stadium_admit()`'s signature changed —
> it now takes a `vm_id` parameter and scopes both free-cell placement and eviction-search
> to that VM's own quota, per §22.3's per-VM free lists (built in 3.7, not this item). The
> two full-array O(N) scans this item shipped are gone in the O(1)-free-list-pop common
> case; the "not fixed, minor" note above about them is superseded. The `mass != 1`
> refusal and the pin/contains logic described above are otherwise unchanged.
- [x] **3.6 — Hera as patron zero, pinned.** Assert at the eviction site; selecting Hera is
a panic, not a filtered candidate. *Refs:* §20.5 #3.
@@ -2889,11 +2896,74 @@ document and committing that amendment as its own item.*
> (`logs/20260804-173311`), aarch64 (`logs/20260804-173350`), riscv64
> (`logs/20260804-173446`).
>
> **Phase 3 core complete.** Items 3.13.6 close out §25.4. The Stadium has a validated
> 64-byte cell, boot-time allocation sized from a real memory query, a closed
> compiler-enforced behaviour set, density as a read, admission/eviction with the mass and
> Hera invariants both enforced, and nothing yet calling any of it — Phase 4 (§25.5) is
> where real patron kinds (words first, per item 4.1) start migrating onto it.
> **Correction, 2026-08-04, same day:** the line originally here claimed "Phase 3 core
> complete" with items 3.13.6. That was premature — starting work on item 4.1 surfaced
> that its own prerequisite (the per-VM free lists §22.3 describes) doesn't exist yet.
> §25.4 gained a seventh item, 3.7, below. Phase 3 core is not complete until it is.
- [x] **3.7 — Per-VM free lists.** Each VM holds its own free-list head index into the
global array (§22.3); cells are drawn by popping that head, granted by Hera. Added
2026-08-04 after starting item 4.1 surfaced this as an unbuilt prerequisite — see item
3.6's correction note above. *Refs:* §22.3.
> **DONE 2026-08-04.** `StadiumVMQuota` table (`stadium.c`, size `STADIUM_MAX_VM_COUNT`,
> linearly searched by `vm_id`): `capsule_birth.c`'s `vm_id` is monotonic and never reused
> (`next_vm_id` only increments, even across VM death — verified by reading, not assumed),
> so it cannot index a table directly; a linear scan over 4 entries costs nothing.
>
> A new per-cell `stadium_owner` byte array (one byte per cell, same pattern as item 3.1's
> discriminator bitmap) records which quota slot a cell belongs to — needed because
> eviction must return a freed cell to the *correct* VM's list, and because eviction's
> least-dense search must stay scoped to the evicting VM's own residents (quota
> isolation: one VM's admission can never evict another VM's patron). A compile-time check
> (`STADIUM_MAX_VM_COUNT <= 255`) confirms the quota-slot index fits the byte.
>
> Free-list linkage reuses each cell's own `link` field as a "next free cell" pointer while
> unresident — `link` is documented only as generic "index into the Stadium, not a
> pointer," so this is a repurposing of already-permitted, previously-unspecified storage,
> not a header change. It does **not** answer the separate, still-open question of which
> field would carry a multi-cell patron's first continuation-cell index — item 3.5's
> `mass != 1` refusal stands exactly as it was.
>
> At `stadium_boot_init()`, every cell is chained into one list in ascending index order
> and granted in full to `vm_id` 0 (Hera) — the only VM that exists (item 0.1). Ascending
> order guarantees the first-ever pop returns cell 0, preserving item 3.6's "Hera is patron
> zero" invariant once real birth-wiring calls `stadium_admit()`.
>
> `stadium_admit()`'s signature changed to `stadium_admit(vm_id, candidate)` — a change to
> code shipped in item 3.5, amended there (see above). Pops the calling VM's free-list
> head first (O(1)); only falls back to a same-VM-scoped eviction search if that list is
> empty.
>
> **A real bug caught before the boot run, by a second review pass:** the zero-fill that
> clears a header on eviction (and the initial free-list build) both leave `contains == 0`
> — but 0 is Hera's valid index (item 3.1's earlier `STADIUM_CONTAINS_NONE` fix was about
> exactly this collision), so every cell on a free list was silently readable as "contains
> Hera." Fixed by explicitly setting `contains = STADIUM_CONTAINS_NONE` at both sites
> (the boot-time chain-build loop, and `stadium_evict()`'s free-list-return step) rather
> than leaving it to the zero-fill's incidental value.
>
> **Explicitly out of scope, reported not invented:**
> - Granting quota to any VM other than Hera, and transferring capacity between VMs, is
> capacity *arbitration* — item 1.3 left "how much capacity moves per eligible transfer"
> explicitly open, so this item does not invent it. Only the boot-time all-to-Hera grant
> exists; `stadium_owner` is set once at boot and never written again, so
> `quota_slot_for_vm()` returns refusal for every `vm_id != 0`, permanently, until
> something else writes to it. Item 4.2 ("Hermes native on the Stadium") will need both
> the grant path and the owner-array writes — flagging now so it isn't a surprise there.
> - Multi-cell continuation-chain attachment remains unresolved (see above); item 3.5's
> refusal is untouched.
>
> **Unexercised at runtime,** same as items 3.43.6: nothing calls `stadium_admit()` or
> `stadium_evict()` yet. The free-list pop path, the quota-scoped eviction fallback, and
> the boot-time chain-build are all unexercised against real data.
>
> **Regression: clean.** All three architectures boot to `ok>` with identical
> `dict_hash=0x3d4e1daf289da94f`, matching the item-3.6 baseline, and the `Stadium: N
> cells (M KB)` boot line is unaffected in format — amd64 (`logs/20260804-180453`, `74234
> cells (4639 KB)`), aarch64 (`logs/20260804-180541`), riscv64 (`logs/20260804-180637`).
>
> **Phase 3 core complete, for real this time.** Items 3.13.7 close out §25.4.
---