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>
289 lines
9.4 KiB
C
289 lines
9.4 KiB
C
/*
|
||
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__ */ |