starkernel: item 3.5 -- admission and eviction

Punch list §25 item 3.5 complete.

stadium_admit(candidate) places into an unused cell if one exists (no
comparison needed), otherwise finds the least-dense resident -- skipping
pinned and contains-gated patrons, which are never eviction candidates
-- and evicts it only if the candidate is strictly denser, per §19.3.
stadium_evict(cell_index) dispatches the departing patron's behaviour
before clearing its slot, per §17.2.

Caught a real bug before it ran: the first draft used contains == 0 to
mean "holds nothing," but cell index 0 is a valid index (Hera, item
3.6). Fixed with a proper sentinel, STADIUM_CONTAINS_NONE (UINT32_MAX).

A second-pass review found mass was not accounted for: both functions
handled exactly one cell regardless of the candidate's stated mass,
which leaks cells on eviction of any mass > 1 patron and breaks
capacity conservation. Fixed by refusing any candidate with mass != 1
-- multi-cell patrons need the per-VM free lists item 3.2 already
deferred (§22.3), not built here.

Documented, not fixed: the discriminator bitmap can't distinguish free
from continuation cells, so the free-cell scan reads continuation-cell
payload bytes under the header layout -- latent since nothing creates
continuation cells yet, and the mass != 1 refusal keeps it provably
latent. Superseded by the free list when it exists.

Unexercised at runtime: nothing calls either function yet (no real
patron kind is wired to the Stadium). No self-test added -- filling
~74,000+ cells to reach the eviction-on-full branch was judged
impractical, following item 2.2's own precedent for its unexercised
fleet-full path.

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-04 17:28:38 -04:00
co-authored by Claude Sonnet 5
parent 0b47c256fc
commit f8a50561b0
10 changed files with 31541 additions and 5 deletions
+58 -1
View File
@@ -2796,8 +2796,65 @@ document and committing that amendment as its own item.*
> `dict_hash=0x3d4e1daf289da94f`, matching the item-3.3 baseline — amd64
> (`logs/20260804-171332`), aarch64 (`logs/20260804-171414`), riscv64
> (`logs/20260804-171513`).
- [ ] **3.5 — Admission and eviction.** Admit if denser than the least dense resident.
- [x] **3.5 — Admission and eviction.** Admit if denser than the least dense resident.
*Refs:* §19.3.
> **DONE 2026-08-04.** `stadium_admit(candidate)` and `stadium_evict(cell_index)` in
> `stadium.c`. Admission scans for an unused cell first (bitmap bit clear, `mass == 0`) and
> places there directly, no comparison needed — §19.3's density rule only governs the full
> case. Otherwise finds the least-dense resident, skipping pinned patrons (`flags` bit 0,
> §3) and `contains`-gated ones (item 1.1: a patron holding another cannot be reaped), and
> evicts it only if the candidate is strictly denser ("denser than," not "at least as dense
> as," per §19.3's own wording). Eviction dispatches the departing patron's behaviour
> (§18.3) before clearing its slot, per §17.2 ("reap means leaves the floor, not
> destroyed").
>
> **A real bug caught before this ever ran:** the first draft used `contains == 0` to mean
> "holds nothing." Cell index 0 is a valid index — Hera, item 3.6's patron zero — so that
> conflated "contains Hera" with "contains nothing." Fixed with a proper sentinel,
> `STADIUM_CONTAINS_NONE` (`UINT32_MAX`), distinct from every valid index. Caught by
> re-reading before compiling, not by any test.
>
> **A second-pass review (before the boot run) found one blocking gap, fixed, and two
> non-blocking ones, recorded rather than fixed:**
>
> - **Blocking, fixed:** neither function accounted for `mass`. Admission placed exactly
> one cell and set exactly one bit regardless of the candidate's stated mass; eviction
> symmetrically freed one cell and orphaned the rest. For `mass > 1` (§23.3: a 1024-byte
> block is mass 19) this breaks capacity conservation — cells leak on every eviction of a
> multi-cell patron, and the "Stadium is full" test becomes wrong since occupancy was
> never correctly accounted. The fix is refusal, not implementation:
> **`stadium_admit()` now refuses any candidate with `mass != 1`.** A multi-cell patron
> needs its continuation chain allocated through the per-VM free lists (§22.3) —
> item 3.2's own DONE note already deferred those as out of scope, granted only when Hera
> assigns a VM its quota. This item does not build them; it refuses what it can't yet do
> correctly rather than doing it wrong.
> - **Not fixed, documented as a live latent gap:** the discriminator bitmap can only say
> header-vs-not-header, not free-vs-continuation. The free-cell scan
> (`!bitmap_get(i) && mass == 0`) reads offsets 2829 of whatever cell is actually there
> under the *header* struct layout; for a real continuation cell those offsets are
> payload bytes, and if they happen to read as zero the scan would treat a live
> continuation cell as free and overwrite it. Latent, not live: nothing creates
> continuation cells yet, and the `mass != 1` refusal above keeps this provably latent
> for as long as that refusal stands. The real fix is the free list itself — a cell is
> free iff it is on one, no union-punning needed — which supersedes this scan when built.
> - **Not fixed, minor:** `stadium_admit()`'s two full-array scans are O(N) each, and
> `candidate_density` duplicates `stadium_density()`'s arithmetic inline because the
> candidate is not yet in the array to call it on. Both go away with the free list; not
> worth a workaround for code with no caller yet.
>
> **Unexercised at runtime, stated plainly rather than implied by a passing boot:** nothing
> calls `stadium_admit()` or `stadium_evict()` yet (no real patron kind is wired to the
> Stadium — Phase 4 migration work). No self-test was added: filling ~74,000+ cells to
> actually reach the eviction-on-full branch in a boot run was judged impractical for the
> value it would add, following the same honesty precedent item 2.2 recorded for its own
> unexercised fleet-full path. The free-cell placement branch, the pin/contains skip
> logic, and the density-comparison branch have never executed against real data.
>
> **Regression: clean.** All three architectures boot to `ok>` with identical
> `dict_hash=0x3d4e1daf289da94f`, matching the item-3.4 baseline — amd64
> (`logs/20260804-172516`), aarch64 (`logs/20260804-172556`), riscv64
> (`logs/20260804-172651`).
- [ ] **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.