stadium: make VM population bound RAM-derived, not a static array of 4

Replaces STADIUM_MAX_VM_COUNT (Kconfig, hardcoded default 4) with a
boot-time computation, mirroring the pattern stadium_boot_init() already
used for the cell pool. New Kconfig STADIUM_VM_MEMORY_PERCENT (default
50): max_vm_count = (kmalloc_get_stats().free_bytes after the cell array
* STADIUM_VM_MEMORY_PERCENT / 100) / VM_MEMORY_SIZE, floored to 1, no
ceiling (population is not knowable in advance - could be 4, could be
4000). stadium_quotas and word_slots (plus stat_promotions/stat_evictions)
are now kmalloc'd to the computed count instead of declared with a macro.
New accessor stadium_max_vm_count() replaces every STADIUM_MAX_VM_COUNT
reference, including capsule_birth.c's birth-refusal gate.

Two things found and fixed along the way:

- The existing cell-pool budget was sourced from pmm_get_stats(), which
  reflects physical pages PMM hasn't handed to any subsystem yet - but
  the actual allocation is kmalloc(), which draws from the separate,
  fixed-size heap kmalloc_init() (M6) already carved out of PMM before
  stadium_boot_init() ever runs. Budgeting against PMM's leftover and
  allocating from the kmalloc heap are two different pools. Both the
  cell budget and the new VM-count budget now source from
  kmalloc_get_stats() instead.

- stadium_owner[] (which VM's quota owns each cell) was uint8_t, capped
  at 255 slots by a compile-time assert tied to the old macro. Widened
  to uint16_t (65535 slots of headroom) with a runtime clamp + log if
  the computed count ever exceeds that, since there's no ceiling anymore.

Three-arch QEMU acceptance: all clean to ok>, computed VM count genuinely
differs by actual available RAM (amd64/riscv64: 50 slots at -m 1024,
aarch64: 101 slots), Stadium conservation invariant identical across all
three (resident_sum=43691 reservoir=21845 sum=65536).
logs/20260815-080526/amd64, logs/20260815-080826/aarch64,
logs/20260815-080952/riscv64.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-15 08:11:21 -04:00
co-authored by Claude Sonnet 5
parent 154eddeab0
commit 00e657019e
16 changed files with 26118 additions and 127 deletions
+53 -6
View File
@@ -32,6 +32,7 @@
#include "starkernel/vm/stadium.h"
#include "starkernel/console.h"
#include "starkernel/kmalloc.h"
#include "starkernel/q48_16.h" /* Q48_ONE -- diagnostic print only */
#include "vm.h" /* DICTIONARY_SIZE, WORD_ID_INVALID */
@@ -55,15 +56,61 @@ typedef struct {
uint64_t last_decay_tick;
} StadiumWordSlot;
static StadiumWordSlot word_slots[STADIUM_MAX_VM_COUNT][DICTIONARY_SIZE];
static int words_initialized = 0;
static uint64_t stat_promotions[STADIUM_MAX_VM_COUNT];
static uint64_t stat_evictions[STADIUM_MAX_VM_COUNT];
/* Row-per-quota-slot, kmalloc'd at stadium_words_init() to stadium_max_vm_
* count() rows of DICTIONARY_SIZE entries each -- replaces the old static
* word_slots[STADIUM_MAX_VM_COUNT][DICTIONARY_SIZE] (2026-08-15: the VM
* count bound is computed from RAM, not a compile-time constant, so this
* can no longer be a flat static array). */
static StadiumWordSlot **word_slots = (StadiumWordSlot **)0;
static int words_initialized = 0;
static uint64_t *stat_promotions = (uint64_t *)0;
static uint64_t *stat_evictions = (uint64_t *)0;
void stadium_words_init(void) {
int slot;
size_t slot;
uint32_t i;
for (slot = 0; slot < STADIUM_MAX_VM_COUNT; slot++) {
size_t count = stadium_max_vm_count();
if (words_initialized) return; /* not re-entrant -- see stadium_words.h */
if (count == 0) {
console_println("Stadium words: init skipped (Stadium not initialized)");
return;
}
word_slots = (StadiumWordSlot **)kmalloc(count * sizeof(StadiumWordSlot *));
stat_promotions = (uint64_t *)kmalloc(count * sizeof(uint64_t));
stat_evictions = (uint64_t *)kmalloc(count * sizeof(uint64_t));
if (!word_slots || !stat_promotions || !stat_evictions) {
console_println("Stadium words: kmalloc failed for per-VM tables");
if (word_slots) kfree(word_slots);
if (stat_promotions) kfree(stat_promotions);
if (stat_evictions) kfree(stat_evictions);
word_slots = (StadiumWordSlot **)0;
stat_promotions = (uint64_t *)0;
stat_evictions = (uint64_t *)0;
return;
}
for (slot = 0; slot < count; slot++) {
word_slots[slot] = (StadiumWordSlot *)kmalloc(DICTIONARY_SIZE * sizeof(StadiumWordSlot));
if (!word_slots[slot]) {
console_println("Stadium words: kmalloc failed for a word-slot row");
/* Free everything allocated so far, including this row's
* predecessors, and bail the same all-or-nothing way
* stadium_boot_init() does. */
{
size_t j;
for (j = 0; j < slot; j++) kfree(word_slots[j]);
}
kfree(word_slots);
kfree(stat_promotions);
kfree(stat_evictions);
word_slots = (StadiumWordSlot **)0;
stat_promotions = (uint64_t *)0;
stat_evictions = (uint64_t *)0;
return;
}
for (i = 0; i < DICTIONARY_SIZE; i++) {
word_slots[slot][i].cell_index = STADIUM_CELL_NONE;
word_slots[slot][i].last_decay_tick = 0;