/* 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__ */