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
+72 -3
View File
@@ -40,6 +40,11 @@
#define STADIUM_CELL_BYTES 64
/* Sentinel for `contains` meaning "holds no patron." Not 0 -- cell index 0 is
* a valid index (Hera, item 3.6), so 0 cannot double as "none" without
* conflating "contains Hera" with "contains nothing." */
#define STADIUM_CONTAINS_NONE ((uint32_t)-1)
/*
* StadiumPatronHeader - one member of the closed two-valued cell union
* (FABRIC.md §3). Nine wires: identity, heat, TTL, pin (a bit in `flags`),
@@ -58,9 +63,12 @@ typedef struct {
uint64_t heat; /* offset 8 -- Q48.16, conserved share of 1.0 (§19.1) */
uint32_t ttl; /* offset 16 -- remaining lifetime; messages and ACLs only (§17.1) */
uint32_t link; /* offset 20 -- index into the Stadium, not a pointer */
uint32_t contains; /* offset 24 -- index of the patron held inside this one, or none (item 1.1).
* Chains up to STADIUM_CONTAINS_DEPTH_MAX deep; reap-gating
* enforcement of that bound is item 3.5's scope, not this one's. */
uint32_t contains; /* offset 24 -- index of the patron held inside this one, or
* STADIUM_CONTAINS_NONE (item 1.1). Cell index 0 is a valid
* index (Hera, item 3.6) so 0 cannot mean "none" -- item 3.5
* caught this and picked UINT32_MAX instead. Chains up to
* STADIUM_CONTAINS_DEPTH_MAX deep; reap-gating enforcement of
* that bound is item 3.5's scope. */
uint16_t mass; /* offset 28 -- cells this patron occupies (§19.2) */
uint8_t flags; /* offset 30 -- bit 0 = pin; remaining bits reserved */
uint8_t behaviour; /* offset 31 -- code field. Valid values are StadiumBehaviour (§18.3)
@@ -201,6 +209,67 @@ void stadium_dispatch(size_t cell_index, StadiumBehaviour behaviour);
*/
uint64_t stadium_density(size_t cell_index);
/* Sentinel returned by stadium_admit() on refusal -- no cell index is this large. */
#define STADIUM_CELL_NONE ((size_t)-1)
/*
* stadium_evict - Reap the patron header at cell_index (FABRIC.md §17.2:
* "reap means leaves the floor, not destroyed"). Dispatches its behaviour
* (§18.3), clears its item-3.1 discriminator bit, zeroes its header.
*
* Refuses if the header is pinned (`flags` bit 0, §3's invariance wire) or
* has a non-none `contains` (item 1.1: a patron holding another cannot be
* reaped, full stop). Also refuses for an out-of-range index or a cell whose
* discriminator bit is not set (nothing resident there to reap).
*
* @param cell_index Index of the patron header to reap.
* @return 0 on success, -1 if refused.
*/
int stadium_evict(size_t cell_index);
/*
* stadium_admit - Place a candidate patron header into the Stadium (FABRIC.md
* §19.3).
*
* First scans for an unused cell (discriminator bit clear and mass == 0) and
* places the candidate there directly -- §19.3's density comparison only
* governs the full case, not this one. If none is free, finds the
* least-dense resident (discriminator bit set, mass > 0, not pinned, not
* `contains`-gated -- pinned and gated residents are never eviction
* candidates, per §3 and item 1.1) and evicts it via stadium_evict() only if
* the candidate is strictly denser (§19.3: "denser than," not "at least as
* dense as"). Otherwise refuses.
*
* Does not itself assert anything about which resident this turns out to be
* -- the item-3.6 rule that patron zero (Hera) must never actually be
* selected is a separate, later check at the eviction site.
*
* REFUSES any candidate with mass != 1. A multi-cell patron (mass > 1, e.g.
* §23.3's 1024-byte block at mass 19) needs its continuation chain allocated
* too, which needs the per-VM free lists §22.3 describes -- item 3.2's DONE
* note already deferred those (not this item's scope, they are granted when
* Hera assigns a VM its quota). Admitting only the header and leaking the
* rest would break capacity conservation, so this refuses rather than doing
* that. Revisit when the free lists exist.
*
* REQUIRES candidate->contains to be either STADIUM_CONTAINS_NONE or a valid
* index (< the current cell count) -- refuses otherwise. This does NOT catch
* a zero-initialized candidate that was meant to contain nothing: 0 is a
* valid index (Hera), so a caller that forgets to set `contains` explicitly
* to STADIUM_CONTAINS_NONE will admit a patron that reads as "contains
* Hera" and is therefore permanently un-evictable. There is no way to tell
* "meant to be 0" from "forgot to set it" from inside this function --
* callers must set every field, `contains` included.
*
* @param candidate Header to admit. Copied into the winning cell as-is;
* caller fills in every field including mass and heat.
* @return The cell index admitted into, or STADIUM_CELL_NONE if refused
* (mass != 1, invalid contains, Stadium full and candidate not
* denser than the least-dense evictable resident, or no evictable
* resident exists at all).
*/
size_t stadium_admit(const StadiumPatronHeader *candidate);
#endif /* __STARKERNEL__ */
#endif /* STARKERNEL_VM_STADIUM_H */