Dynamic switch table (B2) -- FABRIC-3.6.md task 3.1

Replaces the fixed SK_SWITCH_MAX_SLOTS=16 compile-time array with a
boot-time, RAM-derived allocation via a new sk_vm_switch_signal_boot_init(),
kmalloc'd to stadium_max_vm_count() entries -- the same pattern
session_boot_init() already established for Stadium-derived sizing.
Every switch-signal participant is a Stadium VM, so this reuses that
bound directly rather than deriving a separate one.

Verified live on all three architectures: switch table sized to 50
slots (amd64), 202 slots (aarch64), 50 slots (riscv64) -- all well
past the old fixed cap. All three boot to [zuse@Hera] ok> cleanly;
dict_hash for Hermes/Hestia identical across architectures, unmoved
from pre-task values.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-21 23:59:50 -04:00
co-authored by Claude Sonnet 5
parent 66ea4a5e74
commit c19ef365fe
9 changed files with 37220 additions and 15 deletions
@@ -30,16 +30,22 @@
#include "starkernel/capsule_vm_switch_signal.h"
#include "vm.h"
#include "starkernel/vm/switch.h" /* sk_vm_switch_current_vm() -- see below */
#include "starkernel/vm/stadium.h" /* stadium_max_vm_count() -- task 3.1's sizing bound */
#include "starkernel/kmalloc.h"
#include "starkernel/console.h"
#include <stddef.h>
/* FABRIC-3.md §XXVIII Stage 4 (2026-09-14): bumped from 8 to 16 to admit
* WIREBIND identity VMs alongside the fixed Tripod fleet. 16 is not a new
* guess -- it matches messaging.4th's own VM-MAX (the real, already-
* agreed system-wide ceiling: 3 permanent Tripod slots + 13 for
* identities, "identities get NEW slots 3-10, never renumbered" plus
* headroom to VM-MAX itself). See feedback_no_hardcoded_small_scale_bounds
* in project memory -- an invented cap here was rejected before. */
#define SK_SWITCH_MAX_SLOTS 16
/* FABRIC-3.6.md task 3.1 (2026-09-21, ruled B2 / FABRIC-3.5.md §XLV.2):
* replaces the old #define SK_SWITCH_MAX_SLOTS 16 (FABRIC-3.md §XXVIII
* Stage 4, 2026-09-14 -- itself a bump from 8, pinned to messaging.4th's
* VM-MAX constant). The table is now kmalloc'd at
* sk_vm_switch_signal_boot_init() to stadium_max_vm_count() entries --
* every switch-signal participant is a Stadium VM, so this reuses that
* bound directly rather than re-deriving a separate RAM budget, mirroring
* session.c's session_boot_init() (the direct precedent for this pattern,
* itself mirroring stadium.c's own StadiumVMQuota table). See
* feedback_no_hardcoded_small_scale_bounds in project memory -- a fixed
* cap here was rejected before; this closes the same class of bound. */
/* Ticks a non-running participant must accumulate readiness before a
* switch to it is requested. Simple linear accumulate-then-threshold,
@@ -61,7 +67,8 @@ typedef struct {
* function's own doc comment in the header. */
} sk_switch_slot_entry_t;
static sk_switch_slot_entry_t g_slots[SK_SWITCH_MAX_SLOTS];
static sk_switch_slot_entry_t *g_slots = (sk_switch_slot_entry_t *)0;
static int g_slot_capacity; /* 0 until sk_vm_switch_signal_boot_init() succeeds */
static int g_slot_count;
static int g_pending;
static VMUuid g_pending_target;
@@ -71,6 +78,24 @@ static int g_current_slot_cached = -1;
static uint64_t g_switch_count;
static uint32_t g_ticks_since_switch;
/* Freestanding: no libc printf. Prints an unsigned decimal, no leading
* zeros -- same small helper stadium.c/stadium_blocks.c each carry their
* own copy of. */
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]);
}
static int slot_for_vm_id(VMUuid vm_id) {
int i;
for (i = 0; i < g_slot_count; i++) {
@@ -79,8 +104,35 @@ static int slot_for_vm_id(VMUuid vm_id) {
return -1;
}
int sk_vm_switch_signal_boot_init(void) {
size_t max_vm_count = stadium_max_vm_count();
sk_switch_slot_entry_t *slots;
size_t i;
if (max_vm_count == 0) return -1; /* Stadium not yet initialized */
slots = (sk_switch_slot_entry_t *)kmalloc(max_vm_count * sizeof(sk_switch_slot_entry_t));
if (!slots) return -1;
for (i = 0; i < max_vm_count; i++) {
slots[i].vm_id = vm_uuid_none();
slots[i].readiness = 0;
slots[i].has_work = 0;
}
g_slots = slots;
g_slot_capacity = (int)max_vm_count;
g_slot_count = 0;
console_puts("Switch-signal: ");
console_put_u64((uint64_t)max_vm_count);
console_println(" slots");
return 0;
}
int sk_vm_switch_signal_register(VMUuid vm_id) {
if (g_slot_count >= SK_SWITCH_MAX_SLOTS) return -1;
if (g_slot_count >= g_slot_capacity) return -1;
g_slots[g_slot_count].vm_id = vm_id;
g_slots[g_slot_count].readiness = 0;
g_slots[g_slot_count].has_work = 0;
@@ -212,6 +264,10 @@ int sk_vm_switch_signal_slot_count(void) {
return g_slot_count;
}
int sk_vm_switch_signal_slot_capacity(void) {
return g_slot_capacity;
}
uint32_t sk_vm_switch_signal_readiness(int slot) {
if (slot < 0 || slot >= g_slot_count) return 0;
return g_slots[slot].readiness;
+9
View File
@@ -526,6 +526,15 @@ static void kernel_main_deep(BootInfo *boot_info) {
* failed session_register() rather than treating it as fatal. */
(void)session_boot_init();
/* Switch-signal slot table: boot-time allocation (FABRIC-3.6.md task
* 3.1, 2026-09-21), sized from stadium_max_vm_count() so it must run
* after stadium_boot_init() above and before the first
* sk_vm_switch_signal_register() call below (Tripod fleet
* registration). Soft failure, same reasoning as stadium_boot_init()/
* session_boot_init() -- register() simply refuses every registration
* (capacity 0) rather than treating this as fatal. */
(void)sk_vm_switch_signal_boot_init();
/* item 4.1, FABRIC-0.md item 3.6/§17.7: actually enforce "Hera is patron
* zero" before anything else can land on cell 0 via the free list, then
* bring up the word layer's map. Both must happen before the first word