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:
co-authored by
Claude Sonnet 5
parent
154eddeab0
commit
00e657019e
@@ -70,7 +70,7 @@
|
||||
#define STARFORTH_CONFIG_HEARTBEAT_THREAD_ENABLED_DEFAULT 1
|
||||
#define STARFORTH_CONFIG_HEARTBEAT_TICK_NS_DEFAULT 10000ULL
|
||||
#define STARFORTH_CONFIG_HEARTBEAT_INFERENCE_FREQUENCY_DEFAULT 1000
|
||||
#define STARFORTH_CONFIG_STADIUM_MAX_VM_COUNT_DEFAULT 4
|
||||
#define STARFORTH_CONFIG_STADIUM_VM_MEMORY_PERCENT_DEFAULT 50
|
||||
#define STARFORTH_CONFIG_STADIUM_CONTAINS_DEPTH_MAX_DEFAULT 5
|
||||
#define STARFORTH_CONFIG_STADIUM_CAPACITY_TICK_DEFAULT 1000
|
||||
#define STARFORTH_CONFIG_STADIUM_MEMORY_PERCENT_DEFAULT 1
|
||||
@@ -161,8 +161,8 @@
|
||||
#define HEARTBEAT_INFERENCE_FREQUENCY STARFORTH_CONFIG_HEARTBEAT_INFERENCE_FREQUENCY_DEFAULT
|
||||
#endif
|
||||
|
||||
#ifndef STADIUM_MAX_VM_COUNT
|
||||
#define STADIUM_MAX_VM_COUNT STARFORTH_CONFIG_STADIUM_MAX_VM_COUNT_DEFAULT
|
||||
#ifndef STADIUM_VM_MEMORY_PERCENT
|
||||
#define STADIUM_VM_MEMORY_PERCENT STARFORTH_CONFIG_STADIUM_VM_MEMORY_PERCENT_DEFAULT
|
||||
#endif
|
||||
|
||||
#ifndef STADIUM_CONTAINS_DEPTH_MAX
|
||||
|
||||
@@ -55,7 +55,7 @@ typedef enum {
|
||||
CAPSULE_RUN_ERR_EXEC_FAIL, /* Execution failed */
|
||||
CAPSULE_RUN_ERR_HASH_MISMATCH, /* Post-run hash mismatch */
|
||||
CAPSULE_RUN_ERR_STILLBORN, /* VM birth failed */
|
||||
CAPSULE_RUN_ERR_FLEET_FULL, /* Outer Stadium at STADIUM_MAX_VM_COUNT (FABRIC.md item 1.5/2.2) */
|
||||
CAPSULE_RUN_ERR_FLEET_FULL, /* Outer Stadium at stadium_max_vm_count() (FABRIC.md item 1.5/2.2) */
|
||||
} CapsuleRunResult;
|
||||
|
||||
/*===========================================================================
|
||||
|
||||
@@ -118,40 +118,53 @@ typedef char stadium_contains_depth_configured_check[(STADIUM_CONTAINS_DEPTH_MAX
|
||||
typedef char stadium_capacity_tick_configured_check[(STADIUM_CAPACITY_TICK > 0) ? 1 : -1];
|
||||
|
||||
/*
|
||||
* Item 3.7: the per-cell owner array stores a quota-slot index in a single
|
||||
* uint8_t, so STADIUM_MAX_VM_COUNT must fit in one byte. Default 4, so this
|
||||
* holds by a wide margin -- checked because it is depended on, not because
|
||||
* it is expected to fail.
|
||||
* Item 3.7, revised 2026-08-15: the per-cell owner array stores a quota-slot
|
||||
* index. The VM population bound is no longer a compile-time constant (see
|
||||
* stadium_max_vm_count() below), so this can no longer be a compile-time
|
||||
* assert -- the owner element type is now uint16_t (65535 slots of
|
||||
* headroom), and stadium_boot_init() itself clamps the computed count to
|
||||
* that range at runtime, logging if it ever has to.
|
||||
*/
|
||||
typedef char stadium_max_vm_count_fits_owner_byte_check[(STADIUM_MAX_VM_COUNT <= 255) ? 1 : -1];
|
||||
|
||||
/*
|
||||
* stadium_boot_init - Boot-time allocation (FABRIC.md item 3.2, §17.6 position
|
||||
* (b)). Sizes the global cell array from the memory budget actually observed
|
||||
* at boot -- STADIUM_MEMORY_PERCENT of pmm_get_stats().free_bytes at the
|
||||
* at boot -- STADIUM_MEMORY_PERCENT of kmalloc_get_stats().free_bytes at the
|
||||
* point of the call, rounded down to whole STADIUM_CELL_BYTES cells -- rather
|
||||
* than a hardcoded count. Also allocates the header/continuation discriminator
|
||||
* bitmap item 3.1 declared but did not allocate: one bit per cell, bit set
|
||||
* means the cell at that index is a patron header, clear means continuation
|
||||
* or not yet in use. Both are kmalloc'd (freestanding kernel, no separate
|
||||
* PMM-backed region needed for this) and explicitly zero-filled, since
|
||||
* kmalloc does not zero.
|
||||
* than a hardcoded count. (Corrected 2026-08-15 from pmm_get_stats(): PMM's
|
||||
* free-byte figure reflects physical pages not yet handed to any subsystem,
|
||||
* but kmalloc_init() (M6) already carved its own fixed-size heap out of PMM
|
||||
* before this ever runs, and every allocation in this function actually
|
||||
* draws from that kmalloc heap, not raw PMM -- pmm_get_stats() was budgeting
|
||||
* against a pool nothing here actually allocates from.) Also computes the
|
||||
* outer Stadium's VM population bound the same way, from the kmalloc heap's
|
||||
* *remaining* free bytes after the cell array's own allocation: see
|
||||
* stadium_max_vm_count() below. Also allocates the header/continuation
|
||||
* discriminator bitmap item 3.1 declared but did not allocate: one bit per
|
||||
* cell, bit set means the cell at that index is a patron header, clear means
|
||||
* continuation or not yet in use. Both are kmalloc'd (freestanding kernel,
|
||||
* no separate PMM-backed region needed for this) and explicitly zero-filled,
|
||||
* since kmalloc does not zero.
|
||||
*
|
||||
* (Item 3.7) Also allocates a per-cell owner byte array (which VM's quota a
|
||||
* cell belongs to) and chains every cell into a single free list, in
|
||||
* ascending index order, granted in full to vm_id 0 (Hera) -- the only VM
|
||||
* that exists (item 0.1). Ascending order guarantees the first-ever
|
||||
* admission pops cell 0, preserving item 3.6's "Hera is patron zero"
|
||||
* invariant once real birth-wiring calls stadium_admit() for the first
|
||||
* time. The free-list next-pointer reuses each cell's own `link` field
|
||||
* while unresident -- a repurposing of documented-but-unspecified storage,
|
||||
* not a header change; see stadium_admit()'s doc for why this doesn't
|
||||
* answer the separate, still-open continuation-chain question.
|
||||
* (Item 3.7) Also allocates a per-cell owner array (which VM's quota a cell
|
||||
* belongs to; uint16_t as of 2026-08-15, see the note above) and chains
|
||||
* every cell into a single free list, in ascending index order, granted in
|
||||
* full to vm_id 0 (Hera) -- the only VM that exists (item 0.1). Ascending
|
||||
* order guarantees the first-ever admission pops cell 0, preserving item
|
||||
* 3.6's "Hera is patron zero" invariant once real birth-wiring calls
|
||||
* stadium_admit() for the first time. The free-list next-pointer reuses
|
||||
* each cell's own `link` field while unresident -- a repurposing of
|
||||
* documented-but-unspecified storage, not a header change; see
|
||||
* stadium_admit()'s doc for why this doesn't answer the separate,
|
||||
* still-open continuation-chain question.
|
||||
*
|
||||
* Also allocates the VM quota array (stadium_quotas), sized to the
|
||||
* computed stadium_max_vm_count() rather than a compile-time bound.
|
||||
*
|
||||
* Must be called after M6 (kmalloc_init) and before any VM is born (§6). Does
|
||||
* not halt boot on failure -- nothing downstream consumes the Stadium yet.
|
||||
*
|
||||
* @return 0 on success, -1 if kmalloc failed for any of the three allocations.
|
||||
* @return 0 on success, -1 if kmalloc failed for any of the four allocations.
|
||||
*/
|
||||
int stadium_boot_init(void);
|
||||
|
||||
@@ -161,6 +174,15 @@ int stadium_is_initialized(void);
|
||||
/* stadium_cell_count - Number of cells in the array, 0 if not initialized. */
|
||||
size_t stadium_cell_count(void);
|
||||
|
||||
/*
|
||||
* stadium_max_vm_count - The outer Stadium's VM population bound, computed
|
||||
* at stadium_boot_init() from the kmalloc heap's remaining free bytes
|
||||
* (replaces the old compile-time STADIUM_MAX_VM_COUNT, 2026-08-15 -- see
|
||||
* stadium_boot_init()'s own doc). 0 if not initialized. capsule_birth.c's
|
||||
* birth gate reads this instead of a macro.
|
||||
*/
|
||||
size_t stadium_max_vm_count(void);
|
||||
|
||||
/* stadium_cells - Pointer to the cell array, NULL if not initialized. */
|
||||
StadiumCell *stadium_cells(void);
|
||||
|
||||
@@ -309,7 +331,7 @@ uint64_t stadium_reservoir_peek(VMUuid vm_id);
|
||||
|
||||
/*
|
||||
* stadium_quota_slot_for_vm - Read-only: vm_id's quota slot index (0 to
|
||||
* STADIUM_MAX_VM_COUNT-1), for callers outside stadium.c that need to key
|
||||
* stadium_max_vm_count()-1), for callers outside stadium.c that need to key
|
||||
* their own per-VM state the same way stadium.c's internal arrays already
|
||||
* do (FABRIC.md §25.5 item 4.2 -- stadium_words.c's word_id -> cell_index
|
||||
* map needs this to stop colliding across VMs; word_id is scoped per-VM,
|
||||
@@ -366,10 +388,14 @@ int stadium_evict(size_t cell_index);
|
||||
/*
|
||||
* StadiumVMQuota - per-VM ownership of a subset of the global cell array
|
||||
* (FABRIC.md §22.3, item 3.7: "each VM holds its own free-list head index
|
||||
* into the global array"). A small table, linearly searched by vm_id -- a
|
||||
* VMUuid (item 3.8) can't be used as a direct array index anyway, and
|
||||
* STADIUM_MAX_VM_COUNT is small enough (default 4) that a linear scan costs
|
||||
* nothing. Not exposed outside stadium.c: nothing outside needs to inspect
|
||||
* into the global array"). Linearly searched by vm_id -- a VMUuid (item 3.8)
|
||||
* can't be used as a direct array index anyway. Was a small, compile-time-
|
||||
* bounded table (linear scan "costs nothing" at the old default of 4);
|
||||
* since 2026-08-15 the table is sized at boot from stadium_max_vm_count()
|
||||
* and could genuinely be large, so this scan is no longer assumed free --
|
||||
* flagged here rather than silently carried forward as still-obviously-fine,
|
||||
* though no algorithmic change was made in this pass. Not exposed outside
|
||||
* stadium.c: nothing outside needs to inspect
|
||||
* quota state directly yet. Slot emptiness is tracked by an internal
|
||||
* `in_use` flag, not a vm_id sentinel value -- there is no unused vm_id bit
|
||||
* pattern to reserve for it.
|
||||
@@ -449,7 +475,7 @@ size_t stadium_admit(VMUuid vm_id, const StadiumPatronHeader *candidate);
|
||||
* REFUSES (returns -1, does not crash) if: the Stadium is not initialized;
|
||||
* new_vm_id already holds a quota; from_vm_id holds no quota; from_vm_id's
|
||||
* free list has fewer than 2 cells (nothing to split); or no empty quota
|
||||
* slot remains (STADIUM_MAX_VM_COUNT exhausted).
|
||||
* slot remains (stadium_max_vm_count() exhausted).
|
||||
*
|
||||
* @param new_vm_id The VM receiving a fresh quota. Must not already have one.
|
||||
* @param from_vm_id The VM whose free list is split. Must already hold a quota.
|
||||
|
||||
@@ -47,11 +47,15 @@
|
||||
#include "starkernel/vm_uuid.h"
|
||||
|
||||
/*
|
||||
* stadium_words_init - Zeroes the word_id -> cell_index map (DICTIONARY_SIZE
|
||||
* entries per VM quota slot, STADIUM_MAX_VM_COUNT slots, static -- no
|
||||
* allocation). Must be called after stadium_boot_init() and
|
||||
* stadium_birth_hera(), before any word ever dispatches. Safe to call again
|
||||
* (re-zeroes for every slot); nothing does today.
|
||||
* stadium_words_init - Allocates and zeroes the word_id -> cell_index map
|
||||
* (DICTIONARY_SIZE entries per VM quota slot, stadium_max_vm_count() slots,
|
||||
* kmalloc'd -- was a flat static array before 2026-08-15, when the VM count
|
||||
* bound became RAM-derived rather than a compile-time constant). Must be
|
||||
* called after stadium_boot_init() and stadium_birth_hera(), before any word
|
||||
* ever dispatches. NOT safe to call twice -- unlike the old zero-only
|
||||
* version, a second call would kmalloc a second set of tables and leak the
|
||||
* first; guarded internally as a no-op if already initialized. Nothing
|
||||
* calls it twice today.
|
||||
*
|
||||
* item 4.2 (FABRIC.md §25.5): the map is keyed by quota slot, not just
|
||||
* word_id -- word_id is assigned per-VM (vm->next_word_id), not globally
|
||||
|
||||
Reference in New Issue
Block a user