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:
co-authored by
Claude Sonnet 5
parent
0b47c256fc
commit
f8a50561b0
@@ -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 28–29 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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user