Files
LithosAnanake/include/starkernel/vm/stadium.h
T
Robert Allan JamesandClaude Sonnet 5 f8a50561b0 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>
2026-08-04 17:28:38 -04:00

275 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* stadium.h - The Stadium cell and header (FABRIC.md §3, punch list item 3.1)
*
* A cell is one of exactly two things: a patron header, or a continuation
* cell owned by exactly one patron. The union is closed, two-valued, and
* fixed at build time -- not a type field. See FABRIC.md §3.
*/
#ifndef STARKERNEL_VM_STADIUM_H
#define STARKERNEL_VM_STADIUM_H
#ifdef __STARKERNEL__
#include <stddef.h>
#include <stdint.h>
#include "starforth_config.h" /* STADIUM_CONTAINS_DEPTH_MAX, STADIUM_CAPACITY_TICK, STADIUM_MEMORY_PERCENT */
#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`),
* link, code field (`behaviour`), mass, payload, contains. `flags` bit 0 is
* `pin`; the remaining bits are reserved. `behaviour` is the closed code-field
* enumeration (§18.3) -- not yet defined, item 3.3's scope.
*
* Field order is largest-to-smallest so natural C99 alignment adds zero
* padding: every offset below is already a multiple of that field's own
* alignment, and the struct's total size (64) is a multiple of its max
* alignment (8), so no compiler inserts trailing padding either. Do not
* reorder without re-checking this holds on all three ISAs.
*/
typedef struct {
uint64_t identity; /* offset 0 -- handle or name, never a content hash while resident (§24.4) */
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
* 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)
* tags cast to uint8_t -- kept as uint8_t rather than the enum type
* itself since C does not guarantee an enum's underlying type, and
* this field's offset is load-bearing for the 64-byte layout item
* 3.1 validated. */
uint8_t payload[32]; /* offset 32 -- inline payload, used when mass == 1 */
} StadiumPatronHeader;
/*
* StadiumContinuationCell - the other member of the union. Owned by exactly
* one patron header, chained by `next`. Never ranked, never reaped, never
* dispatched (§3) -- pure floor space, accounted for in its owner's mass.
*/
typedef struct {
uint32_t next; /* offset 0 -- index of the next continuation cell, or none */
uint8_t payload[60]; /* offset 4 */
} StadiumContinuationCell;
/*
* StadiumCell - the closed two-valued union itself (§3). Which member is
* valid for a given array slot is NOT stored in the cell -- FABRIC.md's item
* 3.1 amendment to §3 rules this an external side bitmap, one bit per cell,
* kept outside the cell array. Declared here as the indexing contract this
* type expects; item 3.2 (boot-time allocation) allocates the bitmap itself.
*/
typedef union {
StadiumPatronHeader header;
StadiumContinuationCell continuation;
} StadiumCell;
/* C99-portable compile-time size assertions (no _Static_assert -- that's C11). */
typedef char stadium_header_size_check[(sizeof(StadiumPatronHeader) == STADIUM_CELL_BYTES) ? 1 : -1];
typedef char stadium_continuation_size_check[(sizeof(StadiumContinuationCell) == STADIUM_CELL_BYTES) ? 1 : -1];
typedef char stadium_cell_size_check[(sizeof(StadiumCell) == STADIUM_CELL_BYTES) ? 1 : -1];
/*
* Items 1.1 and 1.4 named this item as where their Kconfig symbols would be
* implemented. Neither has a consumer yet (item 3.5 for the depth cap,
* capacity arbitration -- not yet on the punch list -- for the tick); these
* checks only prove the symbols are defined and sane, the same discipline
* already applied to the byte-count checks above.
*/
typedef char stadium_contains_depth_configured_check[(STADIUM_CONTAINS_DEPTH_MAX > 0) ? 1 : -1];
typedef char stadium_capacity_tick_configured_check[(STADIUM_CAPACITY_TICK > 0) ? 1 : -1];
/*
* stadium_boot_init - Boot-time allocation (FABRIC.md item 3.2, §17.6 position
* (b)). Sizes the global cell array from the memory budget actually observed
* at boot -- STADIUM_MEMORY_PERCENT of pmm_get_stats().free_bytes at the
* point of the call, rounded down to whole STADIUM_CELL_BYTES cells -- rather
* than a hardcoded count. Also allocates the header/continuation discriminator
* bitmap item 3.1 declared but did not allocate: one bit per cell, bit set
* means the cell at that index is a patron header, clear means continuation
* or not yet in use. Both are kmalloc'd (freestanding kernel, no separate
* PMM-backed region needed for this) and explicitly zero-filled, since
* kmalloc does not zero.
*
* Must be called after M6 (kmalloc_init) and before any VM is born (§6). Does
* not halt boot on failure -- nothing downstream consumes the Stadium yet.
*
* @return 0 on success, -1 if kmalloc failed for either allocation.
*/
int stadium_boot_init(void);
/* stadium_is_initialized - Whether stadium_boot_init() has succeeded. */
int stadium_is_initialized(void);
/* stadium_cell_count - Number of cells in the array, 0 if not initialized. */
size_t stadium_cell_count(void);
/* stadium_cells - Pointer to the cell array, NULL if not initialized. */
StadiumCell *stadium_cells(void);
/*
* stadium_header_bitmap - Pointer to the discriminator bitmap declared in
* item 3.1, NULL if not initialized. ceil(stadium_cell_count() / 8) bytes.
*/
uint8_t *stadium_header_bitmap(void);
/*
* StadiumBehaviour - the closed code-field enumeration (FABRIC.md §13, §18.3).
* The engine dispatches on this tag and never asks a patron what kind it is
* -- §3's entire point. Two patrons may share a tag: a VM's tag is COOL, the
* same tag a word carries (§18.3). Mapped from §17.1's patron table:
*
* MIGRATE -- blocks: reap event is migration back to Artemis (§17.2)
* DELIVER -- messages: reap event is delivery
* EXPIRE -- ACLs: reap event is TTL expiry
* COOL -- words and VMs: reap event is cooling off the floor
*
* Closed and fixed at build time -- see stadium_dispatch()'s exhaustive
* switch for how the compiler enforces that.
*/
typedef enum {
STADIUM_BEHAVIOUR_MIGRATE = 0,
STADIUM_BEHAVIOUR_DELIVER,
STADIUM_BEHAVIOUR_EXPIRE,
STADIUM_BEHAVIOUR_COOL
} StadiumBehaviour;
/*
* stadium_dispatch - Calls the behaviour handler for a patron's code field.
* The engine calls this at reap and never asks what kind of patron departed
* (§3, §18.3) -- only cell_index and behaviour cross this boundary.
*
* Handlers are stubs today: the real migrate-to-Artemis / deliver / expire /
* cool actions belong to their own subsystems, which have not been migrated
* onto the Stadium yet (Phase 4, §25.5). Nothing calls stadium_dispatch()
* yet either -- that begins with item 3.5 (admission and eviction).
*
* @param cell_index Index into the Stadium of the patron header dispatching.
* @param behaviour Which of the closed tag set to invoke.
*/
void stadium_dispatch(size_t cell_index, StadiumBehaviour behaviour);
/*
* stadium_density - Heat / mass for the patron header at cell_index (FABRIC.md
* §19.2, §19.3). Read, not computed by a scheduler: both operands already
* live in the header, so this is a division on demand, not maintained
* bookkeeping. Result stays valid Q48.16, since heat is already Q48.16 and
* mass is a plain integer divisor.
*
* Returns 0 if mass is 0 -- an empty or never-admitted slot (everything is
* zero-initialized by stadium_boot_init() until something is actually born
* into the Stadium, which nothing yet does) has no footprint to be dense
* within, rather than a division by zero.
*
* Does not validate that cell_index actually holds a header rather than a
* continuation cell or an out-of-range index -- callers are expected to
* consult the item-3.1 discriminator bitmap first. Ranking (finding the
* densest or least-dense resident) is item 3.5's scope, not this one's;
* this function only supplies the per-cell value that comparison reads.
*
* @param cell_index Index into the Stadium of the patron header to measure.
* @return Density in Q48.16, or 0 if the header's mass is 0.
*/
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 */