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
125 lines
5.7 KiB
C
125 lines
5.7 KiB
C
/*
|
||
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_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 */
|