Files
LithosAnanake/src/starkernel/vm/stadium.c
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

279 lines
8.9 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) 2023–2025 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.c - The Stadium cell definitions (item 3.1) and boot-time
* allocation (item 3.2).
*
* Also the translation unit that makes stadium.h's compile-time assertions
* actually get compiled on all three architectures, not merely included by
* something that never builds.
*/
#include "starkernel/vm/stadium.h"
#ifdef __STARKERNEL__
#include "starkernel/kmalloc.h"
#include "starkernel/pmm.h"
#include "starkernel/console.h"
static StadiumCell *stadium_cell_array = (StadiumCell *)0;
static uint8_t *stadium_bitmap = (uint8_t *)0;
static size_t stadium_ncells = 0;
static int stadium_initialized = 0;
/* Freestanding: no libc printf. Prints an unsigned decimal, no leading zeros. */
static void console_put_u64(uint64_t v) {
char buf[21];
int i = 20;
buf[20] = '\0';
if (v == 0) {
console_puts("0");
return;
}
while (v > 0 && i > 0) {
buf[--i] = (char)('0' + (v % 10));
v /= 10;
}
console_puts(&buf[i]);
}
int stadium_boot_init(void) {
pmm_stats_t pmm = pmm_get_stats();
uint64_t budget_bytes = (pmm.free_bytes * (uint64_t)STADIUM_MEMORY_PERCENT) / 100u;
size_t ncells = (size_t)(budget_bytes / STADIUM_CELL_BYTES);
size_t bitmap_bytes = (ncells + 7u) / 8u;
if (ncells == 0) {
console_println("Stadium: boot-time allocation skipped (0 cells from memory budget)");
return -1;
}
StadiumCell *cells = (StadiumCell *)kmalloc(ncells * STADIUM_CELL_BYTES);
uint8_t *bitmap = (uint8_t *)kmalloc(bitmap_bytes);
if (!cells || !bitmap) {
console_println("Stadium: kmalloc failed for boot-time allocation");
if (cells) kfree(cells);
if (bitmap) kfree(bitmap);
return -1;
}
{
uint8_t *raw = (uint8_t *)cells;
size_t n = ncells * STADIUM_CELL_BYTES;
size_t i;
for (i = 0; i < n; i++) raw[i] = 0;
}
{
size_t i;
for (i = 0; i < bitmap_bytes; i++) bitmap[i] = 0;
}
stadium_cell_array = cells;
stadium_bitmap = bitmap;
stadium_ncells = ncells;
stadium_initialized = 1;
console_puts("Stadium: ");
console_put_u64((uint64_t)ncells);
console_puts(" cells (");
console_put_u64((uint64_t)(ncells * STADIUM_CELL_BYTES) / 1024u);
console_println(" KB)");
return 0;
}
int stadium_is_initialized(void) {
return stadium_initialized;
}
size_t stadium_cell_count(void) {
return stadium_ncells;
}
StadiumCell *stadium_cells(void) {
return stadium_cell_array;
}
uint8_t *stadium_header_bitmap(void) {
return stadium_bitmap;
}
/*
* Exhaustive switch, no default: §13/§18.3 require the behaviour set to be
* closed and fixed at build time. With -Wall -Werror, omitting a case here
* for a tag that exists is a build failure, not a silent gap -- the compiler
* enforces closedness, not just this comment.
*
* Handlers are stubs: the real actions belong to subsystems not yet migrated
* onto the Stadium (Phase 4, §25.5). Nothing calls this yet either -- item
* 3.5 is the first consumer.
*/
void stadium_dispatch(size_t cell_index, StadiumBehaviour behaviour) {
console_puts("Stadium: dispatch cell=");
console_put_u64((uint64_t)cell_index);
console_puts(" behaviour=");
switch (behaviour) {
case STADIUM_BEHAVIOUR_MIGRATE:
console_println("MIGRATE (stub)");
break;
case STADIUM_BEHAVIOUR_DELIVER:
console_println("DELIVER (stub)");
break;
case STADIUM_BEHAVIOUR_EXPIRE:
console_println("EXPIRE (stub)");
break;
case STADIUM_BEHAVIOUR_COOL:
console_println("COOL (stub)");
break;
}
}
/*
* FABRIC.md §19.2/§19.3: density is heat / mass, read on demand from fields
* already in the header -- not a value a scheduler maintains. mass == 0
* (an empty or never-admitted slot; everything is zero-initialized until
* something is actually born into the Stadium, which nothing yet does)
* returns 0 rather than dividing by zero. An out-of-range cell_index also
* returns 0 -- there is no patron there to be dense.
*/
uint64_t stadium_density(size_t cell_index) {
StadiumPatronHeader *header;
if (cell_index >= stadium_ncells) return 0;
header = &stadium_cell_array[cell_index].header;
if (header->mass == 0) return 0;
return header->heat / (uint64_t)header->mass;
}
#define STADIUM_FLAG_PIN 0x01u
static int bitmap_get(size_t cell_index) {
return (stadium_bitmap[cell_index / 8u] >> (cell_index % 8u)) & 1u;
}
static void bitmap_set(size_t cell_index) {
stadium_bitmap[cell_index / 8u] |= (uint8_t)(1u << (cell_index % 8u));
}
static void bitmap_clear(size_t cell_index) {
stadium_bitmap[cell_index / 8u] &= (uint8_t)~(1u << (cell_index % 8u));
}
/*
* FABRIC.md §17.2: reap means leaves the floor, not destroyed. Refuses a
* pinned header (§3) or one with a non-none `contains` (item 1.1: a patron
* holding another cannot be reaped). Refuses an out-of-range index or a cell
* whose discriminator bit is not set -- nothing resident there to reap.
*/
int stadium_evict(size_t cell_index) {
StadiumPatronHeader *header;
if (cell_index >= stadium_ncells) return -1;
if (!bitmap_get(cell_index)) return -1;
header = &stadium_cell_array[cell_index].header;
if (header->flags & STADIUM_FLAG_PIN) return -1;
if (header->contains != STADIUM_CONTAINS_NONE) return -1;
stadium_dispatch(cell_index, (StadiumBehaviour)header->behaviour);
bitmap_clear(cell_index);
{
uint8_t *raw = (uint8_t *)header;
size_t i;
for (i = 0; i < sizeof(*header); i++) raw[i] = 0;
}
return 0;
}
/*
* FABRIC.md §19.3: admit if denser than the least-dense resident. Free-cell
* placement first (no comparison needed -- §19.3 only governs the full
* case); otherwise finds the least-dense evictable resident (pinned and
* contains-gated residents are skipped, never eviction candidates) and
* evicts it only if the candidate is strictly denser.
*/
size_t stadium_admit(const StadiumPatronHeader *candidate) {
size_t i;
size_t least_dense_index = STADIUM_CELL_NONE;
uint64_t least_dense_value = 0;
uint64_t candidate_density;
if (!stadium_initialized || !candidate) return STADIUM_CELL_NONE;
/* Multi-cell patrons need their continuation chain allocated too, which
* needs the per-VM free lists item 3.2 deferred (§22.3) -- not this
* item's scope. Refuse rather than admit only the header and leak the
* rest, which would break capacity conservation. */
if (candidate->mass != 1) return STADIUM_CELL_NONE;
/* contains must be a real "none" or a real index -- catches garbage/
* uninitialized values, though not a zero-initialized candidate that
* meant "none": 0 is Hera's valid index, so that case is a caller
* contract issue this function cannot detect (see the header doc). */
if (candidate->contains != STADIUM_CONTAINS_NONE &&
candidate->contains >= stadium_ncells) return STADIUM_CELL_NONE;
for (i = 0; i < stadium_ncells; i++) {
if (!bitmap_get(i) && stadium_cell_array[i].header.mass == 0) {
stadium_cell_array[i].header = *candidate;
bitmap_set(i);
return i;
}
}
for (i = 0; i < stadium_ncells; i++) {
StadiumPatronHeader *h;
if (!bitmap_get(i)) continue;
h = &stadium_cell_array[i].header;
if (h->flags & STADIUM_FLAG_PIN) continue;
if (h->contains != STADIUM_CONTAINS_NONE) continue;
{
uint64_t d = stadium_density(i);
if (least_dense_index == STADIUM_CELL_NONE || d < least_dense_value) {
least_dense_index = i;
least_dense_value = d;
}
}
}
if (least_dense_index == STADIUM_CELL_NONE) return STADIUM_CELL_NONE;
candidate_density = candidate->mass ? (candidate->heat / (uint64_t)candidate->mass) : 0;
if (candidate_density <= least_dense_value) return STADIUM_CELL_NONE;
if (stadium_evict(least_dense_index) != 0) return STADIUM_CELL_NONE;
stadium_cell_array[least_dense_index].header = *candidate;
bitmap_set(least_dense_index);
return least_dense_index;
}
#endif /* __STARKERNEL__ */