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:
co-authored by
Claude Sonnet 5
parent
ecdb547786
commit
c7c9332321
+29
-6
@@ -300,12 +300,35 @@ other three become honest, explicit punch-list items instead of being touched sp
|
|||||||
sites). Not yet built — this is real, scoped, buildable work, not a stub-around candidate.
|
sites). Not yet built — this is real, scoped, buildable work, not a stub-around candidate.
|
||||||
**Still open**, plan to be presented before implementation per the no-stubs/no-early-coding
|
**Still open**, plan to be presented before implementation per the no-stubs/no-early-coding
|
||||||
conventions.
|
conventions.
|
||||||
- [ ] Wire `STADIUM_BEHAVIOUR_MIGRATE` in `stadium_dispatch()` to call
|
- [x] **Resolved (2026-08-25): real block-patron admission + real `MIGRATE` dispatch, both
|
||||||
`blk_subsys_relocate_block()` for real, once block-patron admission (above) exists. On
|
live.** New `stadium_blocks.h`/`stadium_blocks.c` mirror `stadium_words.c`'s shape (Option B
|
||||||
reap, `MIGRATE`'s real action may just be `blk_flush(lbn)` (write-back, per `FABRIC.md`
|
starter-grant admission, redirected Loop #3 cooling, self-healing stale-entry detection) but
|
||||||
§17.2's own wording: "reap event is migration back to Artemis") — `blk_subsys_relocate_block()`
|
key residency by `(quota_slot, lbn)` in a fixed-capacity open-addressing hash table sized off
|
||||||
is a separate primitive for compudynamics-driven mid-residency relocation to a *different*
|
`stadium_cell_count()` (tombstone-based deletion, since LBN space isn't densely bounded like
|
||||||
LBN, not necessarily what fires at ordinary Stadium eviction. Confirm which before wiring.
|
`word_id`), not a dense array. Wired into `block_word_block()`/`block_word_buffer()`/
|
||||||
|
`block_word_update()` (`block_words.c`), `#ifdef __STARKERNEL__`-guarded. `stadium_dispatch()`'s
|
||||||
|
`MIGRATE` case now calls `blk_flush(lbn)` for real (confirmed `blk_flush()`, not
|
||||||
|
`blk_subsys_relocate_block()`, is the right primitive — the latter is for compudynamics-driven
|
||||||
|
relocation to a *different* LBN mid-residency, not ordinary reap write-back). Three new Kconfig
|
||||||
|
tuning constants (`STADIUM_BLOCK_HEAT_QUANTUM`/`STADIUM_BLOCK_COOL_RATE_Q48`/
|
||||||
|
`STADIUM_BLOCK_TRACK_CAP_MULT`) mirror the word-patron ones exactly, same three-layer wiring.
|
||||||
|
**Verified:** clean compile, zero warnings, on all three architectures; clean boot to
|
||||||
|
`zuse)ok>`/`ok>` REPL on all three, conservation (`resident_sum + reservoir == Q48_ONE`) intact
|
||||||
|
identically across all three; `BLOCK`/`BUFFER` touches exercised live from the REPL on amd64 and
|
||||||
|
riscv64 with no crash; a 22,000-distinct-block flood loop (amd64, artificially shrunk to a
|
||||||
|
20,971-cell Stadium via a one-off smaller `-m` to make quota pressure reachable) ran clean under
|
||||||
|
heavy admission-path load with no corruption. **Honest gap, not silently claimed:** a live
|
||||||
|
`MIGRATE lbn=...` console print was NOT directly observed firing in this session. Root cause
|
||||||
|
traced, not a defect in this work: Hera's reservoir was already sitting exactly at the
|
||||||
|
`Q48_ONE / 3` floor from the boot-time self-tests before any manual testing began, so every
|
||||||
|
block-touch candidate pulled 0 heat — and a 0-heat candidate can never be *strictly denser* than
|
||||||
|
an existing resident, so `stadium_admit()`'s eviction fallback correctly refuses rather than
|
||||||
|
evicts once the free list is exhausted. This is a pre-existing interaction between the reservoir
|
||||||
|
floor and density-based eviction (applies equally to word patrons, not something this pass
|
||||||
|
introduced), not something wrong with the new code. Follow-up: force a live `MIGRATE` fire from
|
||||||
|
inside a child VM's own REPL context (where the `STADIUM-ADMIT`/`STADIUM-EVICT` diagnostic words
|
||||||
|
are registered — attempted via `USE` this session, which failed with "EXEC: failed", not yet
|
||||||
|
root-caused) to directly and deterministically exercise the eviction path with nonzero heat.
|
||||||
- [ ] Scope VM-`COOL`: confirm whether `capsule_vm_kill()`/Tripod's existing VM lifecycle has
|
- [ ] Scope VM-`COOL`: confirm whether `capsule_vm_kill()`/Tripod's existing VM lifecycle has
|
||||||
any real hook point equivalent to `stadium_evict()`, or needs the same kind of admission
|
any real hook point equivalent to `stadium_evict()`, or needs the same kind of admission
|
||||||
work `MIGRATE` needs. **Deferred (2026-08-25)** per the user's own "we're going to have to
|
work `MIGRATE` needs. **Deferred (2026-08-25)** per the user's own "we're going to have to
|
||||||
|
|||||||
@@ -128,6 +128,41 @@ config STADIUM_WORD_COOL_RATE_Q48
|
|||||||
is a reasonable starting point, not the same quantity. Untuned
|
is a reasonable starting point, not the same quantity. Untuned
|
||||||
placeholder: real tuning is DoE work (item 5.1).
|
placeholder: real tuning is DoE work (item 5.1).
|
||||||
|
|
||||||
|
config STADIUM_BLOCK_HEAT_QUANTUM
|
||||||
|
int "Q48.16 heat quantum moved per block touch/starter-grant (STADIUM_BLOCK_HEAT_QUANTUM)"
|
||||||
|
default 2048
|
||||||
|
help
|
||||||
|
FABRIC-3.md §B (MIGRATE punch-list item): same role as
|
||||||
|
STADIUM_WORD_HEAT_QUANTUM above, for block patrons touched via
|
||||||
|
BLOCK/BUFFER/UPDATE (stadium_block_dispatch(), stadium_blocks.c).
|
||||||
|
Default matches the word quantum -- no evidence yet that blocks need a
|
||||||
|
different starter share, and diverging without data would just be a
|
||||||
|
second untuned guess instead of one. Real tuning is future DoE work,
|
||||||
|
same as the word quantum.
|
||||||
|
|
||||||
|
config STADIUM_BLOCK_COOL_RATE_Q48
|
||||||
|
int "Q48.16 fraction of resident block heat removed per tick (STADIUM_BLOCK_COOL_RATE_Q48)"
|
||||||
|
default 21845
|
||||||
|
help
|
||||||
|
FABRIC-3.md §B (MIGRATE punch-list item): same role as
|
||||||
|
STADIUM_WORD_COOL_RATE_Q48 above, for block patrons. Default matches
|
||||||
|
the word cool rate for the same reason the heat quantum above does --
|
||||||
|
untuned placeholder, not a derived optimum.
|
||||||
|
|
||||||
|
config STADIUM_BLOCK_TRACK_CAP_MULT
|
||||||
|
int "Multiplier on stadium_cell_count() for the block residency table's capacity"
|
||||||
|
default 2
|
||||||
|
help
|
||||||
|
FABRIC-3.md §B (MIGRATE punch-list item): stadium_blocks.c's
|
||||||
|
(quota_slot, lbn) -> cell_index hash table is a fixed-capacity
|
||||||
|
open-addressing table sized at stadium_blocks_init() as
|
||||||
|
stadium_cell_count() * this value. At most stadium_cell_count() blocks
|
||||||
|
can ever be genuinely Stadium-resident at once (cells are shared across
|
||||||
|
every patron kind), so a multiplier above 1 exists only to keep probe
|
||||||
|
chains short under normal open-addressing load factors, not to track
|
||||||
|
more blocks than could ever be admitted. 2 keeps the load factor at or
|
||||||
|
below 50%. Untuned placeholder, same as the two constants above.
|
||||||
|
|
||||||
endif # STARFORTH_VARIANT_KERNEL
|
endif # STARFORTH_VARIANT_KERNEL
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|||||||
+5
-1
@@ -295,6 +295,9 @@ $(eval $(call kconfig_int,STADIUM_CAPACITY_TICK,1000))
|
|||||||
$(eval $(call kconfig_int,STADIUM_MEMORY_PERCENT,1))
|
$(eval $(call kconfig_int,STADIUM_MEMORY_PERCENT,1))
|
||||||
$(eval $(call kconfig_int,STADIUM_WORD_HEAT_QUANTUM,2048))
|
$(eval $(call kconfig_int,STADIUM_WORD_HEAT_QUANTUM,2048))
|
||||||
$(eval $(call kconfig_int,STADIUM_WORD_COOL_RATE_Q48,21845))
|
$(eval $(call kconfig_int,STADIUM_WORD_COOL_RATE_Q48,21845))
|
||||||
|
$(eval $(call kconfig_int,STADIUM_BLOCK_HEAT_QUANTUM,2048))
|
||||||
|
$(eval $(call kconfig_int,STADIUM_BLOCK_COOL_RATE_Q48,21845))
|
||||||
|
$(eval $(call kconfig_int,STADIUM_BLOCK_TRACK_CAP_MULT,2))
|
||||||
$(eval $(call kconfig_int,SSM_ENTROPY_HIGH_THRESHOLD,0.75))
|
$(eval $(call kconfig_int,SSM_ENTROPY_HIGH_THRESHOLD,0.75))
|
||||||
$(eval $(call kconfig_int,SSM_CV_HIGH_THRESHOLD,0.15))
|
$(eval $(call kconfig_int,SSM_CV_HIGH_THRESHOLD,0.15))
|
||||||
$(eval $(call kconfig_int,SSM_TEMPORAL_DECAY_THRESHOLD,0.5))
|
$(eval $(call kconfig_int,SSM_TEMPORAL_DECAY_THRESHOLD,0.5))
|
||||||
@@ -484,7 +487,8 @@ LOADER_EXTRA_SRCS := \
|
|||||||
$(KERNEL_SRC)/vm/alloc_kernel.c \
|
$(KERNEL_SRC)/vm/alloc_kernel.c \
|
||||||
$(KERNEL_SRC)/vm/q48_stubs.c \
|
$(KERNEL_SRC)/vm/q48_stubs.c \
|
||||||
$(KERNEL_SRC)/vm/stadium.c \
|
$(KERNEL_SRC)/vm/stadium.c \
|
||||||
$(KERNEL_SRC)/vm/stadium_words.c
|
$(KERNEL_SRC)/vm/stadium_words.c \
|
||||||
|
$(KERNEL_SRC)/vm/stadium_blocks.c
|
||||||
|
|
||||||
KERNEL_EXTRA_SRCS := $(LOADER_EXTRA_SRCS)
|
KERNEL_EXTRA_SRCS := $(LOADER_EXTRA_SRCS)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Capsule Block Manifest — Auto-generated
|
# Capsule Block Manifest — Auto-generated
|
||||||
<!-- Generated by mkcapsule --manifest 2026-08-25T23:11:30Z -->
|
<!-- Generated by mkcapsule --manifest 2026-08-26T03:26:40Z -->
|
||||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||||
<!-- Hand-written justifications and immutability notes live -->
|
<!-- Hand-written justifications and immutability notes live -->
|
||||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||||
|
|||||||
Binary file not shown.
@@ -76,6 +76,9 @@
|
|||||||
#define STARFORTH_CONFIG_STADIUM_MEMORY_PERCENT_DEFAULT 1
|
#define STARFORTH_CONFIG_STADIUM_MEMORY_PERCENT_DEFAULT 1
|
||||||
#define STARFORTH_CONFIG_STADIUM_WORD_HEAT_QUANTUM_DEFAULT 2048
|
#define STARFORTH_CONFIG_STADIUM_WORD_HEAT_QUANTUM_DEFAULT 2048
|
||||||
#define STARFORTH_CONFIG_STADIUM_WORD_COOL_RATE_Q48_DEFAULT 21845
|
#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_CHECK_FREQUENCY_DEFAULT 256
|
||||||
#define STARFORTH_CONFIG_HEARTBEAT_WINDOW_TUNING_FREQUENCY_DEFAULT 1000
|
#define STARFORTH_CONFIG_HEARTBEAT_WINDOW_TUNING_FREQUENCY_DEFAULT 1000
|
||||||
#define STARFORTH_CONFIG_HEARTBEAT_SLOPE_VALIDATION_FREQUENCY_DEFAULT 5000
|
#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
|
#define STADIUM_WORD_COOL_RATE_Q48 STARFORTH_CONFIG_STADIUM_WORD_COOL_RATE_Q48_DEFAULT
|
||||||
#endif
|
#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
|
#ifndef HEARTBEAT_CHECK_FREQUENCY
|
||||||
#define HEARTBEAT_CHECK_FREQUENCY STARFORTH_CONFIG_HEARTBEAT_CHECK_FREQUENCY_DEFAULT
|
#define HEARTBEAT_CHECK_FREQUENCY STARFORTH_CONFIG_HEARTBEAT_CHECK_FREQUENCY_DEFAULT
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
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 */
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -56,6 +56,7 @@ EFI_RUNTIME_SERVICES *g_sk_runtime_services = NULL;
|
|||||||
#include "starkernel/vm/parity.h"
|
#include "starkernel/vm/parity.h"
|
||||||
#include "starkernel/vm/stadium.h"
|
#include "starkernel/vm/stadium.h"
|
||||||
#include "starkernel/vm/stadium_words.h"
|
#include "starkernel/vm/stadium_words.h"
|
||||||
|
#include "starkernel/vm/stadium_blocks.h"
|
||||||
#include "starkernel/capsule_generated.h"
|
#include "starkernel/capsule_generated.h"
|
||||||
#include "starkernel/capsule_loader.h"
|
#include "starkernel/capsule_loader.h"
|
||||||
#include "starkernel/capsule_birth.h" /* capsule_birth_mama, capsule_find_mama_init */
|
#include "starkernel/capsule_birth.h" /* capsule_birth_mama, capsule_find_mama_init */
|
||||||
@@ -538,6 +539,7 @@ static void kernel_main_deep(BootInfo *boot_info) {
|
|||||||
* words. */
|
* words. */
|
||||||
(void)stadium_birth_hera();
|
(void)stadium_birth_hera();
|
||||||
stadium_words_init();
|
stadium_words_init();
|
||||||
|
stadium_blocks_init(); /* FABRIC-3.md §B: block-patron layer, same ordering as words */
|
||||||
|
|
||||||
/* M7: VM Bootstrap and Parity Validation */
|
/* M7: VM Bootstrap and Parity Validation */
|
||||||
console_println("VM: bootstrap parity...");
|
console_println("VM: bootstrap parity...");
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
#include "starkernel/hal/hal.h"
|
#include "starkernel/hal/hal.h"
|
||||||
#include "starkernel/q48_16.h" /* Q48_ONE -- item 4.1's reservoir starts each VM's quota at 1.0 */
|
#include "starkernel/q48_16.h" /* Q48_ONE -- item 4.1's reservoir starts each VM's quota at 1.0 */
|
||||||
#include "vm.h" /* VM_MEMORY_SIZE -- the per-VM footprint stadium_max_vm_count() budgets against */
|
#include "vm.h" /* VM_MEMORY_SIZE -- the per-VM footprint stadium_max_vm_count() budgets against */
|
||||||
|
#include "block_subsystem.h" /* blk_flush() -- STADIUM_BEHAVIOUR_MIGRATE's real write-back action */
|
||||||
|
|
||||||
static StadiumCell *stadium_cell_array = (StadiumCell *)0;
|
static StadiumCell *stadium_cell_array = (StadiumCell *)0;
|
||||||
static uint8_t *stadium_bitmap = (uint8_t *)0;
|
static uint8_t *stadium_bitmap = (uint8_t *)0;
|
||||||
@@ -255,18 +256,26 @@ uint8_t *stadium_header_bitmap(void) {
|
|||||||
* for a tag that exists is a build failure, not a silent gap -- the compiler
|
* for a tag that exists is a build failure, not a silent gap -- the compiler
|
||||||
* enforces closedness, not just this comment.
|
* enforces closedness, not just this comment.
|
||||||
*
|
*
|
||||||
* Handlers are stubs: the real actions belong to subsystems not yet migrated
|
* MIGRATE (blocks) is real, FABRIC-3.md §B (MIGRATE punch-list item):
|
||||||
* onto the Stadium (Phase 4, §25.5). Nothing calls this yet either -- item
|
* write-back via blk_flush(), called with the departing patron's identity
|
||||||
* 3.5 is the first consumer.
|
* (the LBN) -- stadium_evict() calls this BEFORE zeroing the header, so
|
||||||
|
* `identity` is still valid here. DELIVER/EXPIRE/VM-COOL remain stubs --
|
||||||
|
* their own subsystems (Hermes, ACL, Tripod VM lifecycle) have not been
|
||||||
|
* migrated onto the Stadium yet, each its own future pass.
|
||||||
*/
|
*/
|
||||||
void stadium_dispatch(size_t cell_index, StadiumBehaviour behaviour) {
|
void stadium_dispatch(size_t cell_index, StadiumBehaviour behaviour) {
|
||||||
console_puts("Stadium: dispatch cell=");
|
console_puts("Stadium: dispatch cell=");
|
||||||
console_put_u64((uint64_t)cell_index);
|
console_put_u64((uint64_t)cell_index);
|
||||||
console_puts(" behaviour=");
|
console_puts(" behaviour=");
|
||||||
switch (behaviour) {
|
switch (behaviour) {
|
||||||
case STADIUM_BEHAVIOUR_MIGRATE:
|
case STADIUM_BEHAVIOUR_MIGRATE: {
|
||||||
console_println("MIGRATE (stub)");
|
uint32_t lbn = (uint32_t)stadium_cell_array[cell_index].header.identity;
|
||||||
|
console_puts("MIGRATE lbn=");
|
||||||
|
console_put_u64((uint64_t)lbn);
|
||||||
|
console_println("");
|
||||||
|
blk_flush(lbn);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case STADIUM_BEHAVIOUR_DELIVER:
|
case STADIUM_BEHAVIOUR_DELIVER:
|
||||||
console_println("DELIVER (stub)");
|
console_println("DELIVER (stub)");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -0,0 +1,352 @@
|
|||||||
|
/*
|
||||||
|
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.c - Block patrons on the Stadium (FABRIC-3.md §B/§D,
|
||||||
|
* MIGRATE punch-list item). See stadium_blocks.h for the design rationale.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "starkernel/vm/stadium_blocks.h"
|
||||||
|
|
||||||
|
#ifdef __STARKERNEL__
|
||||||
|
|
||||||
|
#include "starkernel/vm/stadium.h"
|
||||||
|
#include "starkernel/console.h"
|
||||||
|
#include "starkernel/kmalloc.h"
|
||||||
|
#include "starkernel/q48_16.h" /* Q48_ONE */
|
||||||
|
#include "starforth_config.h" /* STADIUM_BLOCK_HEAT_QUANTUM, STADIUM_BLOCK_COOL_RATE_Q48,
|
||||||
|
STADIUM_BLOCK_TRACK_CAP_MULT */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* StadiumBlockSlot - one entry in the fixed-capacity open-addressing hash
|
||||||
|
* table. Keyed by (quota_slot, lbn) together -- LBN numbering is global
|
||||||
|
* across VMs (unlike word_id), but two VMs touching the SAME lbn must still
|
||||||
|
* land in different Stadium cells under different quotas, so both fields
|
||||||
|
* are part of the key (same lesson stadium_words.c's item 4.2 fix already
|
||||||
|
* applied for word_id).
|
||||||
|
*
|
||||||
|
* `state` is three-valued, not a bool: standard open-addressing deletion
|
||||||
|
* needs EMPTY vs. TOMBSTONE distinguished from OCCUPIED, or reclaiming a
|
||||||
|
* stale entry's slot (self-healing, see stadium_word_dispatch()'s identical
|
||||||
|
* problem for the dense word map) would silently break the probe chain for
|
||||||
|
* any other key that happened to hash into the same bucket and probed past
|
||||||
|
* this slot to find its own home.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
BLOCK_SLOT_EMPTY = 0,
|
||||||
|
BLOCK_SLOT_OCCUPIED,
|
||||||
|
BLOCK_SLOT_TOMBSTONE
|
||||||
|
} StadiumBlockSlotState;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t state;
|
||||||
|
int16_t quota_slot;
|
||||||
|
uint32_t lbn;
|
||||||
|
size_t cell_index;
|
||||||
|
uint64_t last_decay_tick;
|
||||||
|
} StadiumBlockSlot;
|
||||||
|
|
||||||
|
static StadiumBlockSlot *block_slots = (StadiumBlockSlot *)0;
|
||||||
|
static size_t block_slots_cap = 0;
|
||||||
|
static int blocks_initialized = 0;
|
||||||
|
|
||||||
|
/* Per-quota-slot counters, kmalloc'd to stadium_max_vm_count() entries --
|
||||||
|
* same reasoning as stadium_words.c's stat_promotions/stat_evictions: a
|
||||||
|
* single global counter can't answer "how is vm_id's own table doing"
|
||||||
|
* once more than one VM holds a quota. */
|
||||||
|
static uint64_t *stat_promotions = (uint64_t *)0;
|
||||||
|
static uint64_t *stat_evictions = (uint64_t *)0;
|
||||||
|
|
||||||
|
void stadium_blocks_init(void) {
|
||||||
|
size_t vm_count;
|
||||||
|
size_t cell_count;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (blocks_initialized) return; /* not re-entrant -- see stadium_blocks.h */
|
||||||
|
|
||||||
|
cell_count = stadium_cell_count();
|
||||||
|
vm_count = stadium_max_vm_count();
|
||||||
|
if (cell_count == 0 || vm_count == 0) {
|
||||||
|
console_println("Stadium blocks: init skipped (Stadium not initialized)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
block_slots_cap = cell_count * (size_t)STADIUM_BLOCK_TRACK_CAP_MULT;
|
||||||
|
|
||||||
|
block_slots = (StadiumBlockSlot *)kmalloc(block_slots_cap * sizeof(StadiumBlockSlot));
|
||||||
|
stat_promotions = (uint64_t *)kmalloc(vm_count * sizeof(uint64_t));
|
||||||
|
stat_evictions = (uint64_t *)kmalloc(vm_count * sizeof(uint64_t));
|
||||||
|
if (!block_slots || !stat_promotions || !stat_evictions) {
|
||||||
|
console_println("Stadium blocks: kmalloc failed for tracking tables");
|
||||||
|
if (block_slots) kfree(block_slots);
|
||||||
|
if (stat_promotions) kfree(stat_promotions);
|
||||||
|
if (stat_evictions) kfree(stat_evictions);
|
||||||
|
block_slots = (StadiumBlockSlot *)0;
|
||||||
|
block_slots_cap = 0;
|
||||||
|
stat_promotions = (uint64_t *)0;
|
||||||
|
stat_evictions = (uint64_t *)0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < block_slots_cap; i++) {
|
||||||
|
block_slots[i].state = BLOCK_SLOT_EMPTY;
|
||||||
|
block_slots[i].quota_slot = -1;
|
||||||
|
block_slots[i].lbn = 0;
|
||||||
|
block_slots[i].cell_index = STADIUM_CELL_NONE;
|
||||||
|
block_slots[i].last_decay_tick = 0;
|
||||||
|
}
|
||||||
|
for (i = 0; i < vm_count; i++) {
|
||||||
|
stat_promotions[i] = 0;
|
||||||
|
stat_evictions[i] = 0;
|
||||||
|
}
|
||||||
|
blocks_initialized = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cell_is_resident(size_t idx) {
|
||||||
|
const uint8_t *bm = stadium_header_bitmap();
|
||||||
|
if (!bm) return 0;
|
||||||
|
return (bm[idx / 8u] >> (idx % 8u)) & 1u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* block_hash - Multiplicative mix of (quota_slot, lbn) into [0, block_slots_cap).
|
||||||
|
* No libc hash dependency (freestanding kernel); Knuth's multiplicative
|
||||||
|
* method (constant 2654435761, the standard 32-bit golden-ratio prime) is
|
||||||
|
* enough to spread a monotonically-touched LBN sequence across buckets --
|
||||||
|
* this table's job is short probe chains under normal access patterns, not
|
||||||
|
* cryptographic distribution.
|
||||||
|
*/
|
||||||
|
static size_t block_hash(int16_t quota_slot, uint32_t lbn) {
|
||||||
|
uint32_t key = lbn ^ ((uint32_t)(uint16_t)quota_slot << 16) ^ ((uint32_t)(uint16_t)quota_slot);
|
||||||
|
key = key * 2654435761u;
|
||||||
|
return (size_t)(key % (uint32_t)block_slots_cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* table_probe - Single linear-probe pass serving both lookup and insertion.
|
||||||
|
* Walks at most block_slots_cap slots starting at the key's hash bucket.
|
||||||
|
*
|
||||||
|
* - If an OCCUPIED slot matching (quota_slot, lbn) is found: returns its
|
||||||
|
* index, *out_found = 1.
|
||||||
|
* - Else, if any EMPTY or TOMBSTONE slot was seen along the way: returns
|
||||||
|
* the FIRST such slot's index (so insertion reuses the earliest opening,
|
||||||
|
* standard open-addressing practice), *out_found = 0.
|
||||||
|
* - Else (table genuinely full, no opening and no match): returns
|
||||||
|
* block_slots_cap, *out_found = 0.
|
||||||
|
*
|
||||||
|
* Probing must continue past TOMBSTONE slots when searching for a match --
|
||||||
|
* a tombstone marks "something used to live here," not "the chain ends
|
||||||
|
* here," or a live entry further down the same chain would become
|
||||||
|
* unreachable.
|
||||||
|
*/
|
||||||
|
static size_t table_probe(int16_t quota_slot, uint32_t lbn, int *out_found) {
|
||||||
|
size_t start = block_hash(quota_slot, lbn);
|
||||||
|
size_t first_open = block_slots_cap; /* sentinel: none seen yet */
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < block_slots_cap; i++) {
|
||||||
|
size_t idx = (start + i) % block_slots_cap;
|
||||||
|
StadiumBlockSlot *s = &block_slots[idx];
|
||||||
|
|
||||||
|
if (s->state == BLOCK_SLOT_EMPTY) {
|
||||||
|
if (first_open == block_slots_cap) first_open = idx;
|
||||||
|
break; /* empty slot ends any possible chain for this key */
|
||||||
|
}
|
||||||
|
if (s->state == BLOCK_SLOT_TOMBSTONE) {
|
||||||
|
if (first_open == block_slots_cap) first_open = idx;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* OCCUPIED */
|
||||||
|
if (s->quota_slot == quota_slot && s->lbn == lbn) {
|
||||||
|
*out_found = 1;
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_found = 0;
|
||||||
|
return first_open; /* block_slots_cap if truly full */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* block_dispatch_pull - Reservoir pull for block-touch admission, same
|
||||||
|
* Q48_ONE / 3 floor stadium_word_dispatch()'s word_dispatch_pull() enforces
|
||||||
|
* and for the identical reason (FABRIC.md §25.5/§25.7): block-touch
|
||||||
|
* admission alone must never be able to drain a VM's reservoir below the
|
||||||
|
* floor other reservoir consumers (word execution, application-level
|
||||||
|
* pulls) depend on. Own copy, not shared code across files -- same
|
||||||
|
* decision stadium_words.c's own version already made.
|
||||||
|
*/
|
||||||
|
static uint64_t block_dispatch_pull(VMUuid vm_id, uint64_t want) {
|
||||||
|
uint64_t available = stadium_reservoir_peek(vm_id);
|
||||||
|
uint64_t floor = Q48_ONE / 3;
|
||||||
|
uint64_t pullable = (available > floor) ? (available - floor) : 0;
|
||||||
|
uint64_t capped = (want < pullable) ? want : pullable;
|
||||||
|
return stadium_reservoir_pull(vm_id, capped);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stadium_block_dispatch(VMUuid vm_id, uint32_t lbn, uint64_t heartbeat_ticks) {
|
||||||
|
int slot;
|
||||||
|
int found;
|
||||||
|
size_t idx;
|
||||||
|
int stale = 0;
|
||||||
|
|
||||||
|
if (!blocks_initialized) return;
|
||||||
|
|
||||||
|
slot = stadium_quota_slot_for_vm(vm_id);
|
||||||
|
if (slot < 0) return;
|
||||||
|
|
||||||
|
idx = table_probe((int16_t)slot, lbn, &found);
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
StadiumBlockSlot *bs = &block_slots[idx];
|
||||||
|
size_t cell = bs->cell_index;
|
||||||
|
|
||||||
|
if (cell == STADIUM_CELL_NONE || cell >= stadium_cell_count() || !cell_is_resident(cell)) {
|
||||||
|
stale = 1;
|
||||||
|
} else if (stadium_cells()[cell].header.identity != (uint64_t)lbn) {
|
||||||
|
stale = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stale) {
|
||||||
|
StadiumPatronHeader *h = &stadium_cells()[cell].header;
|
||||||
|
uint64_t elapsed = heartbeat_ticks - bs->last_decay_tick;
|
||||||
|
|
||||||
|
if (elapsed > 0) {
|
||||||
|
uint64_t per_tick = (h->heat * (uint64_t)STADIUM_BLOCK_COOL_RATE_Q48) >> 16;
|
||||||
|
uint64_t cooled = per_tick * elapsed;
|
||||||
|
if (cooled > h->heat) cooled = h->heat;
|
||||||
|
if (cooled > 0) {
|
||||||
|
h->heat -= cooled;
|
||||||
|
stadium_reservoir_push(vm_id, cooled);
|
||||||
|
}
|
||||||
|
bs->last_decay_tick = heartbeat_ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
h->heat += block_dispatch_pull(vm_id, (uint64_t)STADIUM_BLOCK_HEAT_QUANTUM);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stale: reclaim this exact slot -- idx is still this key's home. */
|
||||||
|
bs->state = BLOCK_SLOT_TOMBSTONE;
|
||||||
|
stat_evictions[slot]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx >= block_slots_cap) return; /* table full: skip tracking this touch */
|
||||||
|
|
||||||
|
{
|
||||||
|
uint64_t pulled = block_dispatch_pull(vm_id, (uint64_t)STADIUM_BLOCK_HEAT_QUANTUM);
|
||||||
|
StadiumPatronHeader candidate;
|
||||||
|
uint8_t *raw = (uint8_t *)&candidate;
|
||||||
|
size_t i;
|
||||||
|
size_t cell_idx;
|
||||||
|
|
||||||
|
for (i = 0; i < sizeof(candidate); i++) raw[i] = 0;
|
||||||
|
candidate.identity = (uint64_t)lbn;
|
||||||
|
candidate.heat = pulled;
|
||||||
|
candidate.ttl = 0;
|
||||||
|
candidate.link = 0; /* unused for block patrons, same as words */
|
||||||
|
candidate.contains = STADIUM_CONTAINS_NONE;
|
||||||
|
candidate.mass = 1;
|
||||||
|
candidate.flags = 0; /* unpinned */
|
||||||
|
candidate.behaviour = (uint8_t)STADIUM_BEHAVIOUR_MIGRATE;
|
||||||
|
|
||||||
|
cell_idx = stadium_admit(vm_id, &candidate);
|
||||||
|
if (cell_idx == STADIUM_CELL_NONE) {
|
||||||
|
stadium_reservoir_push(vm_id, pulled); /* rollback: preserve conservation */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
block_slots[idx].state = BLOCK_SLOT_OCCUPIED;
|
||||||
|
block_slots[idx].quota_slot = (int16_t)slot;
|
||||||
|
block_slots[idx].lbn = lbn;
|
||||||
|
block_slots[idx].cell_index = cell_idx;
|
||||||
|
block_slots[idx].last_decay_tick = heartbeat_ticks;
|
||||||
|
stat_promotions[slot]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Freestanding: no libc printf. Same small utility stadium_words.c
|
||||||
|
* duplicates locally (and stadium.c before it) -- prints an unsigned
|
||||||
|
* decimal, no leading zeros. */
|
||||||
|
static void console_put_u64(uint64_t v) {
|
||||||
|
char buf[21];
|
||||||
|
int i = 20;
|
||||||
|
buf[20] = '\0';
|
||||||
|
if (v == 0) {
|
||||||
|
console_puts("0");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (v > 0 && i > 0) {
|
||||||
|
buf[--i] = (char)('0' + (v % 10));
|
||||||
|
v /= 10;
|
||||||
|
}
|
||||||
|
console_puts(&buf[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stadium_blocks_print_boot_diagnostics(VMUuid vm_id) {
|
||||||
|
int slot = stadium_quota_slot_for_vm(vm_id);
|
||||||
|
uint64_t promotions = 0, evictions = 0;
|
||||||
|
uint64_t resident_sum = 0;
|
||||||
|
uint64_t reservoir;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (!blocks_initialized || slot < 0) {
|
||||||
|
console_println("Stadium blocks: not initialized or vm_id holds no quota");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promotions = stat_promotions[slot];
|
||||||
|
evictions = stat_evictions[slot];
|
||||||
|
|
||||||
|
/* Block-only resident sum: walk this VM's own table entries, same
|
||||||
|
* self-healing residency check stadium_block_dispatch() uses -- a
|
||||||
|
* stale entry contributes 0, not garbage. Not exposed as a separate
|
||||||
|
* API (unlike stadium_words_resident_heat()) since nothing else needs
|
||||||
|
* it yet; add one if a caller shows up. */
|
||||||
|
for (i = 0; i < block_slots_cap; i++) {
|
||||||
|
StadiumBlockSlot *bs = &block_slots[i];
|
||||||
|
size_t cell;
|
||||||
|
|
||||||
|
if (bs->state != BLOCK_SLOT_OCCUPIED || bs->quota_slot != (int16_t)slot) continue;
|
||||||
|
cell = bs->cell_index;
|
||||||
|
if (cell == STADIUM_CELL_NONE || cell >= stadium_cell_count() || !cell_is_resident(cell)) continue;
|
||||||
|
if (stadium_cells()[cell].header.identity != (uint64_t)bs->lbn) continue;
|
||||||
|
resident_sum += stadium_cells()[cell].header.heat;
|
||||||
|
}
|
||||||
|
|
||||||
|
reservoir = stadium_reservoir_peek(vm_id);
|
||||||
|
|
||||||
|
console_puts("Stadium blocks: promotions=");
|
||||||
|
console_put_u64(promotions);
|
||||||
|
console_puts(" evictions=");
|
||||||
|
console_put_u64(evictions);
|
||||||
|
console_println("");
|
||||||
|
|
||||||
|
console_puts("Stadium blocks resident_sum=");
|
||||||
|
console_put_u64(resident_sum);
|
||||||
|
console_puts(" reservoir=");
|
||||||
|
console_put_u64(reservoir);
|
||||||
|
console_println(" (block-only term, not a standalone Q48_ONE check -- reservoir is shared)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __STARKERNEL__ */
|
||||||
@@ -57,6 +57,10 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#ifdef __STARKERNEL__
|
||||||
|
#include "starkernel/vm/stadium_blocks.h" /* stadium_block_dispatch() -- FABRIC-3.md §B */
|
||||||
|
#endif
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------
|
/* ----------------------------------------------------------------------
|
||||||
* Architecture:
|
* Architecture:
|
||||||
* - Layer 1: blkio (vtable abstraction)
|
* - Layer 1: blkio (vtable abstraction)
|
||||||
@@ -305,6 +309,9 @@ void block_word_block(VM *vm) {
|
|||||||
if (vm->dsp < 0) { vm->error = 1; return; }
|
if (vm->dsp < 0) { vm->error = 1; return; }
|
||||||
cell_t blk = vm_pop(vm);
|
cell_t blk = vm_pop(vm);
|
||||||
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
|
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
|
||||||
|
#ifdef __STARKERNEL__
|
||||||
|
stadium_block_dispatch(vm->stadium_vm_id, (uint32_t) blk, vm->heartbeat.tick_count);
|
||||||
|
#endif
|
||||||
vaddr_t vaddr = blk_vm_load(vm, (uint32_t) blk, 0);
|
vaddr_t vaddr = blk_vm_load(vm, (uint32_t) blk, 0);
|
||||||
if (!vaddr) { vm->error = 1; return; }
|
if (!vaddr) { vm->error = 1; return; }
|
||||||
set_scr(vm, blk);
|
set_scr(vm, blk);
|
||||||
@@ -316,6 +323,9 @@ void block_word_buffer(VM *vm) {
|
|||||||
if (vm->dsp < 0) { vm->error = 1; return; }
|
if (vm->dsp < 0) { vm->error = 1; return; }
|
||||||
cell_t blk = vm_pop(vm);
|
cell_t blk = vm_pop(vm);
|
||||||
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
|
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
|
||||||
|
#ifdef __STARKERNEL__
|
||||||
|
stadium_block_dispatch(vm->stadium_vm_id, (uint32_t) blk, vm->heartbeat.tick_count);
|
||||||
|
#endif
|
||||||
vaddr_t vaddr = blk_vm_assign(vm, (uint32_t) blk);
|
vaddr_t vaddr = blk_vm_assign(vm, (uint32_t) blk);
|
||||||
if (!vaddr) { vm->error = 1; return; }
|
if (!vaddr) { vm->error = 1; return; }
|
||||||
set_scr(vm, blk);
|
set_scr(vm, blk);
|
||||||
@@ -328,6 +338,9 @@ void block_word_buffer(VM *vm) {
|
|||||||
void block_word_update(VM *vm) {
|
void block_word_update(VM *vm) {
|
||||||
cell_t blk = vm_load_cell(vm, vm->scr_addr);
|
cell_t blk = vm_load_cell(vm, vm->scr_addr);
|
||||||
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
|
if (blk == 0 || !blk_is_valid((uint32_t) blk)) { vm->error = 1; return; }
|
||||||
|
#ifdef __STARKERNEL__
|
||||||
|
stadium_block_dispatch(vm->stadium_vm_id, (uint32_t) blk, vm->heartbeat.tick_count);
|
||||||
|
#endif
|
||||||
int s = blk_vm_find(vm, (uint32_t) blk);
|
int s = blk_vm_find(vm, (uint32_t) blk);
|
||||||
if (s < 0) { vm->error = 1; return; }
|
if (s < 0) { vm->error = 1; return; }
|
||||||
vaddr_t base = BLK_VM_WINDOW_BASE + (vaddr_t)s * BLOCK_SIZE;
|
vaddr_t base = BLK_VM_WINDOW_BASE + (vaddr_t)s * BLOCK_SIZE;
|
||||||
|
|||||||
Reference in New Issue
Block a user