Files
StarForth/src/starkernel/vm/stadium.c
T
Robert Allan JamesandClaude Sonnet 5 72487e7fff starkernel: item 3.6 -- Hera as patron zero, pinned (Phase 3 core complete)
Punch list §25 item 3.6 complete. Phase 3 (§25.4) core is now done:
items 3.1-3.6 all closed.

stadium_evict() now panics via sk_hal_panic() if a resident cell 0
(Hera, patron zero by construction of §6's boot order) is ever
selected for eviction. Placement is deliberate: the check runs before
the pin/contains refusal checks, not after -- if it ran after, a
wrongly-cleared pin would let the ordinary refusal path quietly return
-1 instead of ever reaching the panic, defeating the point of a check
that's supposed to be independent of pin holding.

Per §20.5 #3's explicit wording, not implemented as a filter:
stadium_admit()'s least-dense search is unchanged, still relying on
the general pin skip from item 3.5. Adding a second filter there would
have done exactly what that section warns against ("filtering hides
the bug, asserting reports it").

The panic path is, and will remain, unexercised by the acceptance
mechanism: sk_hal_panic() halts the machine, and triggering it
deliberately is incompatible with the three-arch boot being this
project's sole acceptance test. Correctness rests on the placement
argument, not a test -- same honesty precedent as items 3.4 and 3.5's
other unexercised paths.

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 17:36:23 -04:00

289 lines
9.4 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"
#include "starkernel/hal/hal.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.
*
* §20.5 #3 / item 3.6: panics, does not return, if a resident cell 0 (Hera,
* patron zero) is ever selected -- checked before pin/contains below, on
* purpose, so a wrongly-cleared pin cannot silently swallow the violation
* via the ordinary refusal path instead of surfacing it.
*/
int stadium_evict(size_t cell_index) {
StadiumPatronHeader *header;
if (cell_index >= stadium_ncells) return -1;
if (!bitmap_get(cell_index)) return -1;
if (cell_index == STADIUM_HERA_CELL_INDEX) {
sk_hal_panic("Stadium: eviction selected patron zero (Hera) -- governor invariant broken");
}
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__ */