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
+68
@@ -823,3 +823,71 @@ unreachable NULL-write, `m5_time_trust`/`m5_variance` dead fields.
|
||||
closed items, four sequencing-blocked items (no ruling needed, just not startable yet), and
|
||||
one code question. Nothing in either track's open half is closeable without either Artemis or
|
||||
an explicit decision from Captain Bob — which is the state this pass was asked to produce.
|
||||
|
||||
---
|
||||
|
||||
## G. Stadium VM population bound made RAM-derived — 2026-08-15
|
||||
|
||||
Captain Bob flagged, mid-conversation, that `STADIUM_MAX_VM_COUNT` being a hardcoded
|
||||
compile-time `4` was not the design he had in mind: "The stadium has capacity that is
|
||||
determined at boot time with the remaining ram... a static array of four, then we're
|
||||
fucked." Confirmed by grep: `STADIUM_MAX_VM_COUNT` (Kconfig default 4) sized two genuinely
|
||||
static compile-time arrays — `stadium_quotas[STADIUM_MAX_VM_COUNT]` and
|
||||
`word_slots[STADIUM_MAX_VM_COUNT][DICTIONARY_SIZE]` — and gated `capsule_birth.c`'s birth
|
||||
refusal, with zero RAM-based computation anywhere near it. Not an oversight: `FABRIC.md`
|
||||
§20.5 item 1.5 (RESOLVED 2026-08-04) explicitly decided this the other way — "fixed for the
|
||||
machine's lifetime once set at build... the outer total does not itself flex at runtime."
|
||||
Captain Bob overruled that: population is not knowable in advance (could be 4, could be
|
||||
4000), so the bound must be computed at boot, no ceiling.
|
||||
|
||||
**What was already correct, and what wasn't.** The Stadium's *cell pool* (actual patron
|
||||
storage) was already boot-time RAM-derived — `stadium_boot_init()`
|
||||
(`src/starkernel/vm/stadium.c:118`) already computed `ncells` from a percentage of free
|
||||
memory and `kmalloc()`'d it. Only the *VM population ceiling* was static.
|
||||
|
||||
**A second, real bug found while fixing this.** The cell-pool budget was computed from
|
||||
`pmm_get_stats().free_bytes` — physical pages PMM hasn't handed to any subsystem yet — but
|
||||
the actual allocation was `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; the percentage was being applied to memory nothing here actually draws from. Fixed
|
||||
as part of this change — both the existing cell budget and the new VM-count budget now
|
||||
source from `kmalloc_get_stats()`.
|
||||
|
||||
**Owner-byte width.** `stadium_owner[]` (which VM's quota owns each cell) was `uint8_t`,
|
||||
capped at 255 slots by the old compile-time assert. With no ceiling, this needed widening —
|
||||
done, `uint16_t` (65535 slots of headroom), with a runtime clamp + log if the computed count
|
||||
ever exceeds that.
|
||||
|
||||
**Mechanism (mirrors the existing cell-pool pattern exactly):** new Kconfig
|
||||
`STADIUM_VM_MEMORY_PERCENT` (default 50, untuned placeholder like its `STADIUM_MEMORY_PERCENT`
|
||||
sibling). At boot, after the cell array is allocated: `max_vm_count =
|
||||
(kmalloc_get_stats().free_bytes * STADIUM_VM_MEMORY_PERCENT / 100) / VM_MEMORY_SIZE`
|
||||
(`VM_MEMORY_SIZE` = 5 MiB, the real per-VM footprint — not the small bookkeeping tables),
|
||||
floored to 1 so Hera can always boot, clamped to 65535. `stadium_quotas` and `word_slots`
|
||||
(plus their `stat_promotions`/`stat_evictions` companions) are now `kmalloc()`'d to that
|
||||
count instead of declared with the macro. New accessor `stadium_max_vm_count()` replaces
|
||||
every `STADIUM_MAX_VM_COUNT` reference, including `capsule_birth.c`'s birth-refusal gate.
|
||||
|
||||
**Files touched:** `Kconfig.kernel`, `include/starforth_config.h`,
|
||||
`include/starkernel/vm/stadium.h`, `include/starkernel/vm/stadium_words.h`,
|
||||
`include/starkernel/capsule_run.h` (comment only), `src/starkernel/vm/stadium.c`,
|
||||
`src/starkernel/vm/stadium_words.c`, `src/starkernel/capsule/capsule_birth.c`.
|
||||
|
||||
**Verification, all three architectures clean, computed VM count genuinely differs across
|
||||
runs (proof it's really reading RAM, not a disguised constant):**
|
||||
|
||||
- **amd64:** `Stadium: 83886 cells (5242 KB), 50 VM slots` — `logs/20260815-080526/amd64/`
|
||||
- **aarch64:** `Stadium: 167772 cells (10485 KB), 101 VM slots` — `logs/20260815-080826/aarch64/`
|
||||
- **riscv64:** `Stadium: 83886 cells (5242 KB), 50 VM slots` — `logs/20260815-080952/riscv64/`
|
||||
|
||||
All three reached `ok>` clean with Hermes's self-test, an identical Stadium conservation
|
||||
check (`resident_sum=43691 reservoir=21845 sum=65536`, `Q48_ONE=65536`), and KILL/rest all
|
||||
passing — the RAM-derived count changed, the physics invariant it feeds into didn't, which
|
||||
is exactly what should happen. Up from the old fixed `4` in every case.
|
||||
|
||||
**Related, same conversation:** this surfaced alongside a correction to §12 Q5's
|
||||
`STADIUM_CAPACITY_TICK` framing (F.2 above) — Captain Bob's "the clock is only the
|
||||
heartbeat, period" pushback, confirmed against `FABRIC.md` §16.4/§17.1's decided one-clock
|
||||
rule. Both are Stadium-capacity-adjacent but independent: this item is the VM *population*
|
||||
bound (a count), that one is the fleet-capacity *cadence* (a tick threshold).
|
||||
|
||||
Reference in New Issue
Block a user