Stage 3 follow-on: message-arrival eligibility hook + trampoline-blind switch-storm fix (FABRIC-3.md §XXVIII.2)
Implements the message-arrival eligibility signal FABRIC-3.md §XXVIII.1 left open (has_work per-slot flag, set via new SWITCH-MARK-WORK primitive from MSG-SEND) so an idle VM never becomes a switch target purely by waiting out the readiness threshold. Also root-causes and fixes a second, independent switch-storm: the tick's "who is current" check used vm_log_attributed_vm(), which can't see a VM parked in switch.c's own raw trampoline. Replaced with a dedicated g_switch_current_vm tracked by the switch mechanism itself, and moved target-slot eligibility reset to the switch decision point instead of relying on ISR polling to observe a window that can be only a few instructions wide. Verified live on all 3 architectures: clean boot to zuse@Hera] ok>, live cross-VM message dispatch, and (since a quiet log looks identical to a livelocked storm once the DoE probe is gone) confirmed genuine REPL liveness via QMP send-key + screendump on aarch64/riscv64, not log inspection alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
862d7d9c48
commit
66beae7fd4
@@ -29,7 +29,7 @@
|
||||
|
||||
#include "starkernel/capsule_vm_switch_signal.h"
|
||||
#include "vm.h"
|
||||
#include "starkernel/log_attrib.h" /* vm_log_attributed_vm() */
|
||||
#include "starkernel/vm/switch.h" /* sk_vm_switch_current_vm() -- see below */
|
||||
#include <stddef.h>
|
||||
|
||||
/* Headroom beyond today's 3 participants (Hera/Hermes/Artemis) -- see
|
||||
@@ -49,6 +49,11 @@
|
||||
typedef struct {
|
||||
VMUuid vm_id;
|
||||
uint32_t readiness;
|
||||
int has_work; /* Stage 3 follow-on (2026-09-14): set by
|
||||
* sk_vm_switch_signal_mark_work() (mainline,
|
||||
* MSG-SEND hook), cleared here in tick() the
|
||||
* instant this slot becomes current -- see that
|
||||
* 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];
|
||||
@@ -73,6 +78,7 @@ int sk_vm_switch_signal_register(VMUuid vm_id) {
|
||||
if (g_slot_count >= SK_SWITCH_MAX_SLOTS) 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;
|
||||
return g_slot_count++;
|
||||
}
|
||||
|
||||
@@ -80,8 +86,21 @@ void sk_vm_switch_signal_tick(void) {
|
||||
VM *current;
|
||||
int current_slot, i;
|
||||
|
||||
current = vm_log_attributed_vm();
|
||||
if (!current) return; /* nothing interpreting right now (boot/driver/HAL context) */
|
||||
/* FABRIC-3.md §XXVIII Stage 3 follow-on correction (2026-09-14): was
|
||||
* vm_log_attributed_vm() -- wrong source. That tracks nested
|
||||
* vm_interpret() calls for log attribution and has no way to know a
|
||||
* VM is running while parked inside switch.c's own raw-C trampoline
|
||||
* (sk_vm_switch_entry()), which never itself calls vm_interpret(). A
|
||||
* VM that only ever ran the trampoline was therefore never seen as
|
||||
* "current," so its own readiness/has_work below never reset --
|
||||
* tick() re-armed the very next timer tick, forever, once nothing
|
||||
* else happened to be independently flipping attribution around it
|
||||
* (found live: a switch storm, 101% CPU, log frozen). sk_vm_switch_
|
||||
* current_vm() is authoritative instead: the switch mechanism itself
|
||||
* updates it on every switch, so it reflects who is physically
|
||||
* running regardless of whether that VM ever calls vm_interpret(). */
|
||||
current = sk_vm_switch_current_vm();
|
||||
if (!current) return; /* boot: no switch has happened yet, not seeded */
|
||||
|
||||
current_slot = slot_for_vm_id(current->stadium_vm_id);
|
||||
if (current_slot < 0) return; /* current VM isn't a registered participant */
|
||||
@@ -92,6 +111,7 @@ void sk_vm_switch_signal_tick(void) {
|
||||
for (i = 0; i < g_slot_count; i++) {
|
||||
if (i == current_slot) {
|
||||
g_slots[i].readiness = 0;
|
||||
g_slots[i].has_work = 0;
|
||||
} else {
|
||||
g_slots[i].readiness++;
|
||||
}
|
||||
@@ -99,7 +119,9 @@ void sk_vm_switch_signal_tick(void) {
|
||||
|
||||
if (!g_pending) {
|
||||
for (i = 0; i < g_slot_count; i++) {
|
||||
if (i != current_slot && g_slots[i].readiness >= SK_SWITCH_READINESS_THRESHOLD) {
|
||||
if (i != current_slot &&
|
||||
g_slots[i].readiness >= SK_SWITCH_READINESS_THRESHOLD &&
|
||||
g_slots[i].has_work) {
|
||||
g_pending = 1;
|
||||
g_pending_target = g_slots[i].vm_id;
|
||||
break;
|
||||
@@ -114,11 +136,38 @@ VMUuid sk_vm_switch_signal_take_pending(void) {
|
||||
return g_pending_target;
|
||||
}
|
||||
|
||||
void sk_vm_switch_signal_note_switch_performed(void) {
|
||||
void sk_vm_switch_signal_note_switch_performed(VMUuid target_id) {
|
||||
/* FABRIC-3.md §XXVIII Stage 3 follow-on correction (2026-09-14):
|
||||
* reset the target's own readiness/has_work HERE, deterministically,
|
||||
* at the switch decision point -- not by waiting for tick()'s
|
||||
* current-slot polling to observe it running. That polling depends on
|
||||
* sk_vm_switch_current_vm() catching a real ISR tick during whatever
|
||||
* window the target is genuinely "current" for, and for a VM whose
|
||||
* only production behavior is switch.c's placeholder trampoline (an
|
||||
* immediate yield-back), that window can be as small as a few
|
||||
* instructions -- too narrow for a timer tick to reliably land in.
|
||||
* Without this, the target's eligibility never clears, tick() re-arms
|
||||
* on the very next timer tick, and the result is a continuous switch
|
||||
* storm (found live: 101% CPU, zero forward progress). This makes
|
||||
* correctness independent of ISR timing; current_slot (via
|
||||
* sk_vm_switch_current_vm()/tick()) remains useful as a DoE-observable
|
||||
* value and for excluding the running slot from readiness accrual,
|
||||
* but is no longer load-bearing for storm prevention. */
|
||||
int slot = slot_for_vm_id(target_id);
|
||||
if (slot >= 0) {
|
||||
g_slots[slot].readiness = 0;
|
||||
g_slots[slot].has_work = 0;
|
||||
}
|
||||
g_switch_count++;
|
||||
g_ticks_since_switch = 0;
|
||||
}
|
||||
|
||||
void sk_vm_switch_signal_mark_work(VMUuid vm_id) {
|
||||
int slot = slot_for_vm_id(vm_id);
|
||||
if (slot < 0) return;
|
||||
g_slots[slot].has_work = 1;
|
||||
}
|
||||
|
||||
uint64_t sk_vm_switch_signal_switch_count(void) {
|
||||
return g_switch_count;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#include "starkernel/capsule_vm_physics.h"
|
||||
#include "starkernel/vm/vm_internal.h"
|
||||
#include "starkernel/vm/stadium.h" /* item 4.2 -- STADIUM-* words */
|
||||
#include "starkernel/capsule_vm_switch_signal.h" /* FABRIC-3.md §XXVIII Stage 3 follow-on -- SWITCH-MARK-WORK */
|
||||
#include "starkernel/vm/stadium_words.h" /* item 4.2 -- stadium_words_resident_heat() */
|
||||
#include "starkernel/repl.h"
|
||||
#include "starkernel/capsule_generated.h"
|
||||
@@ -785,6 +786,43 @@ static void mama_word_vm_exec(VM *vm)
|
||||
/* Stack clean on exit */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SWITCH-MARK-WORK ( name-caddr name-u -- )
|
||||
* FABRIC-3.md §XXVIII Stage 3 follow-on (2026-09-14): message-arrival
|
||||
* eligibility hook. Called from MSG-SEND (capsules/common/messaging.4th)
|
||||
* every time a message is sent to a VM, so the Stage 3 switch signal can
|
||||
* tell "has recently been sent something" apart from "has just been idle
|
||||
* long enough" before ever selecting it as a switch target. Silently
|
||||
* ignores a malformed or unknown name -- this fires on the hot path of
|
||||
* every message send, not a user-facing command, so it must never error
|
||||
* or spam the console.
|
||||
*/
|
||||
static void mama_word_switch_mark_work(VM *vm)
|
||||
{
|
||||
char name[VM_NAME_MAX];
|
||||
cell_t u, caddr;
|
||||
uint32_t i;
|
||||
VMRegistryEntry entry;
|
||||
|
||||
if (vm->dsp < 2) { vm->error = 1; return; }
|
||||
|
||||
u = vm_pop(vm);
|
||||
caddr = vm_pop(vm);
|
||||
|
||||
if (u <= 0 || (uint32_t)u >= VM_NAME_MAX) return;
|
||||
|
||||
{
|
||||
const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr);
|
||||
if (!p) return;
|
||||
for (i = 0; i < (uint32_t)u; i++) name[i] = (char)p[i];
|
||||
name[u] = '\0';
|
||||
}
|
||||
|
||||
if (capsule_vm_find_by_name_nocase(name, &entry) == 0) {
|
||||
sk_vm_switch_signal_mark_work(entry.vm_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief VM-CALL ( cmd-caddr cmd-u vm-name-caddr vm-name-u -- n )
|
||||
* Like VM-EXEC but pops one cell from the target VM's TOS and pushes
|
||||
@@ -1653,6 +1691,7 @@ void register_mama_forth_words(VM *vm)
|
||||
register_word(vm, "VM-PHYSICS-STATUS", mama_word_vm_physics_status);
|
||||
register_word(vm, "VM-STEP", mama_word_vm_step);
|
||||
register_word(vm, "VM-EXEC", mama_word_vm_exec);
|
||||
register_word(vm, "SWITCH-MARK-WORK", mama_word_switch_mark_work);
|
||||
register_word(vm, "VM-CALL", mama_word_vm_call);
|
||||
register_word(vm, "VM-HEAT", mama_word_vm_heat);
|
||||
/* FABRIC-3.md SXX: the 8 STADIUM-* primitives register_child_vm_words()
|
||||
@@ -1711,6 +1750,7 @@ void register_mama_forth_words(VM *vm)
|
||||
register_word(vm, "VM-PHYSICS-STATUS", mama_word_vm_physics_status);
|
||||
register_word(vm, "VM-STEP", mama_word_vm_step);
|
||||
register_word(vm, "VM-EXEC", mama_word_vm_exec);
|
||||
register_word(vm, "SWITCH-MARK-WORK", mama_word_switch_mark_work);
|
||||
register_word(vm, "VM-CALL", mama_word_vm_call);
|
||||
register_word(vm, "VM-HEAT", mama_word_vm_heat);
|
||||
/* FABRIC-3.md SXX: the 8 STADIUM-* primitives register_child_vm_words()
|
||||
@@ -1946,6 +1986,7 @@ void register_child_vm_words(VM *vm)
|
||||
register_word(vm, "STOP", mama_word_stop);
|
||||
register_word(vm, "EXEC", mama_word_exec);
|
||||
register_word(vm, "VM-EXEC", mama_word_vm_exec);
|
||||
register_word(vm, "SWITCH-MARK-WORK", mama_word_switch_mark_work);
|
||||
register_word(vm, "VM-CALL", mama_word_vm_call);
|
||||
register_word(vm, "VM-HEAT", mama_word_vm_heat);
|
||||
/* USE (FABRIC-2.md §F.24): not console-specific -- any VM can
|
||||
|
||||
Reference in New Issue
Block a user