Stadium: real block-patron admission + MIGRATE dispatch (FABRIC-3.md §B)

stadium_admit()'s mass==1 refusal looked like a hard blocker for 1024-byte
blocks, but stadium_word_dispatch()'s real candidate construction proves
Stadium cells carry pure identity/heat/bookkeeping, never the resident's
actual content -- a block patron follows the same shape (identity=LBN,
payload unused), so this was real, scoped work, not a case for stubbing.

New stadium_blocks.h/.c mirror stadium_words.c's admission/cooling shape,
keyed by (quota_slot, lbn) in a fixed-capacity open-addressing hash table
(tombstone deletion) instead of a dense array, since LBN space isn't
densely bounded like word_id. Wired into block_word_block()/buffer()/
update() (block_words.c), __STARKERNEL__-guarded. stadium_dispatch()'s
MIGRATE case now calls blk_flush(lbn) for real instead of printing
"(stub)". Three new Kconfig constants (STADIUM_BLOCK_HEAT_QUANTUM/
STADIUM_BLOCK_COOL_RATE_Q48/STADIUM_BLOCK_TRACK_CAP_MULT) mirror the
word-patron ones, same three-layer wiring.

VM-COOL/DELIVER/EXPIRE stay explicit punch-list items -- VM-COOL
deferred pending the still-iterating Tripod/Zuse/messaging vision,
DELIVER/EXPIRE are their own future subsystem integrations per
FABRIC.md's own "open, not resolved" notes.

Verified clean compile (zero warnings) and clean boot to REPL with
conservation intact (resident_sum + reservoir == Q48_ONE) on all three
architectures (amd64/aarch64/riscv64); BLOCK/BUFFER touches exercised
live from the REPL with no crash; a 22,000-distinct-block flood loop
against an artificially shrunk Stadium ran clean under heavy admission
load. A live MIGRATE console fire was not directly observed this
session (root-caused to a pre-existing reservoir-floor/density-eviction
interaction unrelated to this change, documented in FABRIC-3.md) --
flagged as an honest follow-up, not silently claimed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
This commit is contained in:
Robert Allan James
2026-08-25 23:28:59 -04:00
co-authored by Claude Sonnet 5
parent ecdb547786
commit c7c9332321
17 changed files with 27731 additions and 13 deletions
+15
View File
@@ -76,6 +76,9 @@
#define STARFORTH_CONFIG_STADIUM_MEMORY_PERCENT_DEFAULT 1
#define STARFORTH_CONFIG_STADIUM_WORD_HEAT_QUANTUM_DEFAULT 2048
#define STARFORTH_CONFIG_STADIUM_WORD_COOL_RATE_Q48_DEFAULT 21845
#define STARFORTH_CONFIG_STADIUM_BLOCK_HEAT_QUANTUM_DEFAULT 2048
#define STARFORTH_CONFIG_STADIUM_BLOCK_COOL_RATE_Q48_DEFAULT 21845
#define STARFORTH_CONFIG_STADIUM_BLOCK_TRACK_CAP_MULT_DEFAULT 2
#define STARFORTH_CONFIG_HEARTBEAT_CHECK_FREQUENCY_DEFAULT 256
#define STARFORTH_CONFIG_HEARTBEAT_WINDOW_TUNING_FREQUENCY_DEFAULT 1000
#define STARFORTH_CONFIG_HEARTBEAT_SLOPE_VALIDATION_FREQUENCY_DEFAULT 5000
@@ -185,6 +188,18 @@
#define STADIUM_WORD_COOL_RATE_Q48 STARFORTH_CONFIG_STADIUM_WORD_COOL_RATE_Q48_DEFAULT
#endif
#ifndef STADIUM_BLOCK_HEAT_QUANTUM
#define STADIUM_BLOCK_HEAT_QUANTUM STARFORTH_CONFIG_STADIUM_BLOCK_HEAT_QUANTUM_DEFAULT
#endif
#ifndef STADIUM_BLOCK_COOL_RATE_Q48
#define STADIUM_BLOCK_COOL_RATE_Q48 STARFORTH_CONFIG_STADIUM_BLOCK_COOL_RATE_Q48_DEFAULT
#endif
#ifndef STADIUM_BLOCK_TRACK_CAP_MULT
#define STADIUM_BLOCK_TRACK_CAP_MULT STARFORTH_CONFIG_STADIUM_BLOCK_TRACK_CAP_MULT_DEFAULT
#endif
#ifndef HEARTBEAT_CHECK_FREQUENCY
#define HEARTBEAT_CHECK_FREQUENCY STARFORTH_CONFIG_HEARTBEAT_CHECK_FREQUENCY_DEFAULT
#endif
+124
View File
@@ -0,0 +1,124 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 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_blocks.h - Block patrons on the Stadium (FABRIC-3.md §B/§D,
* MIGRATE punch-list item)
*
* The block-specific layer on top of the generic L0 engine (stadium.h), same
* relationship stadium_words.h/.c already has: nothing in stadium.c/.h knows
* a block patron exists -- it only ever sees cell_index, VMUuid, and
* StadiumPatronHeader. This file is where "block" becomes a concrete
* meaning: an (owning quota slot, LBN) -> cell_index map, the starter-grant
* admission rule (mirrors stadium_word_dispatch()'s Option B exactly), and
* the reservoir-quantum touch/cool that feeds and drains a resident block's
* Stadium heat.
*
* Unlike words, LBN is not densely bounded (block_subsystem.c's unified LBN
* space spans RAM/RAMDRIVE/DISK/USB and can be large), so the map here is a
* fixed-capacity open-addressing hash table sized off stadium_cell_count()
* at init, not a dense per-VM array -- see stadium_blocks.c for the layout.
* A block's actual 1024 content bytes are never copied into a Stadium cell;
* they stay exactly where block_subsystem.c already keeps them. The cell
* only ever carries identity (the LBN) and heat/bookkeeping, same as a word
* patron's cell never carries the word's own dictionary entry.
*/
#ifndef STARKERNEL_VM_STADIUM_BLOCKS_H
#define STARKERNEL_VM_STADIUM_BLOCKS_H
#ifdef __STARKERNEL__
#include <stdint.h>
#include "starkernel/vm_uuid.h"
/*
* stadium_blocks_init - Allocates and zeroes the (quota slot, LBN) ->
* cell_index hash table (capacity computed from stadium_cell_count() *
* STADIUM_BLOCK_TRACK_CAP_MULT at call time, kmalloc'd). Must be called
* after stadium_boot_init() -- so stadium_cell_count() is non-zero -- and
* before any block ever dispatches; the real boot site is immediately after
* the existing stadium_words_init() call (kernel_main.c), same M7/M7.1
* ordering. Not safe to call twice -- guarded internally as a no-op if
* already initialized, same convention as stadium_words_init().
*/
void stadium_blocks_init(void);
/*
* stadium_block_dispatch - The per-touch entry point, called from
* block_word_block()/block_word_buffer()/block_word_update()
* (src/word_source/block_words.c) -- mirroring stadium_word_dispatch()'s
* call pattern and cooling/admission logic 1:1, keyed by LBN instead of
* word_id.
*
* If (vm_id, lbn) is already resident: applies the same redirected Loop #3
* cooling stadium_word_dispatch() applies (fraction of the cell's own
* current heat, scaled by elapsed_ticks since this block's own last touch --
* STADIUM_BLOCK_COOL_RATE_Q48), crediting the cooled amount back to vm_id's
* reservoir, then pulls STADIUM_BLOCK_HEAT_QUANTUM from the reservoir into
* the cell -- clamped to the reservoir's actual balance AND to the same
* Q48_ONE / 3 floor stadium_word_dispatch() enforces, so block-touch
* admission alone can never starve other reservoir consumers sharing the
* same VM.
*
* If not resident (or the table's entry is stale -- self-healing check
* against the cell's discriminator bit and identity, same pattern
* resolve_resident_cell() uses in stadium_words.c): attempts starter-grant
* admission -- pulls STADIUM_BLOCK_HEAT_QUANTUM (same floor), builds an
* unpinned MIGRATE candidate (identity = lbn, mass = 1, payload unused --
* the block's real content is never copied here), calls stadium_admit(). On
* refusal, pushes the pulled quantum back (rollback). On success, records
* the mapping. If the hash table itself is full and has no slot for this
* (vm_id, lbn) pair, this touch is silently skipped -- Stadium's own
* capacity already bounds real residency, so a table miss under load is
* graceful degradation, not an error.
*
* No-op if vm_id holds no Stadium quota, or stadium_blocks_init() has not
* run.
*
* @param vm_id Owning VM -- vm->stadium_vm_id at every call site,
* same quota-isolation reasoning stadium_word_
* dispatch() already documents (LBN numbering is
* global, not per-VM, but quota scoping still keeps
* two VMs' admissions from evicting each other).
* @param lbn Logical block number being touched.
* @param heartbeat_ticks Current vm->heartbeat.tick_count.
*/
void stadium_block_dispatch(VMUuid vm_id, uint32_t lbn, uint64_t heartbeat_ticks);
/*
* stadium_blocks_print_boot_diagnostics - Console output mirroring
* stadium_words_print_boot_diagnostics(): promotions/evictions for vm_id's
* own block-touch table, plus a conservation check
* (Σ(resident block heat) + reservoir against Q48_ONE is NOT a standalone
* invariant here -- word-execution heat and any other resident shares the
* same reservoir, so this prints the block-only resident sum as a
* diagnostic term, not a claim that it alone should equal Q48_ONE).
*
* @param vm_id The VM whose block-touch table/reservoir to read.
*/
void stadium_blocks_print_boot_diagnostics(VMUuid vm_id);
#endif /* __STARKERNEL__ */
#endif /* STARKERNEL_VM_STADIUM_BLOCKS_H */