fix(hermes): one delivery path -- a VM takes its own messages on its own turn, never on another's stack
FABRIC-3.5 SXXXII, closing FABRIC-3 SXXIX bug 2 (SXXXVI). A send marks the receiver for the switch signal; the VM's own loop on its native stack takes its messages and yields; Hera's idle beat makes the pending switch instead of running other VMs' messages on her stack; every VM gets switcher turns from birth; the drain refuses any call outside the VM's own context (the test: it held every relayed line before, none now). Found on the way: amd64's first-entry frame left rsp 8 bytes off the SysV entry alignment (#GP on the first interpreter run on a VM's own stack), and a VM yielding from its loop was marked SWITCHED_OUT, which stopped console relay after one message and would have made KILL refuse it. Relay of four lines to a WIREBIND identity on all three ISAs, five hot-plugged identities, and standard acceptance (POST 1052, hash 0x08873e0f44b7cb2a) all pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b7e8e4b058
commit
029a8ed9cf
+74
-24
@@ -30,6 +30,8 @@
|
||||
#include "starkernel/vm/switch.h"
|
||||
#include "vm.h"
|
||||
#include "starkernel/capsule_birth.h" /* capsule_vm_set_state */
|
||||
#include "starkernel/capsule_vm_switch_signal.h" /* one path: take_pending(), mark_work() */
|
||||
#include "starkernel/vm/kernel_hermes.h" /* one path: sk_hermes_drain_checkpoint() */
|
||||
#include <stdint.h>
|
||||
|
||||
/* Arch-specific (switch.S in each arch dir). sk_vm_switch_prime() builds
|
||||
@@ -51,6 +53,8 @@ extern uint64_t sk_vm_switch_prime(uint64_t stack_top, void *entry);
|
||||
* who most recently switched into it does. */
|
||||
static VM *g_switch_entry_vm;
|
||||
|
||||
static int switch_core(struct VM *from, struct VM *to, VMState from_state);
|
||||
|
||||
/* FABRIC-3.md §XXVIII Stage 3 follow-on (2026-09-14): which VM is
|
||||
* currently running, from the switch mechanism's own point of view --
|
||||
* independent of vm_log_attributed_vm() (vm_core.c), which is scoped to
|
||||
@@ -72,39 +76,81 @@ VM *sk_vm_switch_current_vm(void) { return g_switch_current_vm; }
|
||||
void sk_vm_switch_set_current(VM *vm) { g_switch_current_vm = vm; }
|
||||
|
||||
/**
|
||||
* sk_vm_switch_entry - trampoline for a VM's first-ever entry.
|
||||
* sk_vm_switch_entry - what a VM runs on its own native stack.
|
||||
*
|
||||
* A freshly-entered VM has no production behavior defined yet -- what a
|
||||
* switched-to VM should actually DO once running is later-stage work
|
||||
* (message dispatch, an idle loop, whatever Stage 3+ needs). For now this
|
||||
* is a minimal, permanent placeholder: immediately yield back to whoever
|
||||
* most recently switched to it, forever. Reads self->native_switch_back_to
|
||||
* Entered once, at a VM's first-ever switch-in, and never left: each turn
|
||||
* it takes its own pending kernel-Hermes messages, then yields back to
|
||||
* whoever most recently switched to it. Reads self->native_switch_back_to
|
||||
* fresh every iteration (not a value captured once) so a later switch-in
|
||||
* from a different VM is honored, not silently bounced to whoever entered
|
||||
* first. Must never fall off the end -- there is no legitimate return
|
||||
* address below a synthetic first-entry frame, unlike an ordinary
|
||||
* function.
|
||||
* from a different VM is honored. Must never fall off the end -- there is
|
||||
* no legitimate return address below a synthetic first-entry frame.
|
||||
*
|
||||
* FABRIC-3.md §XXVIII Stage 3 follow-on (2026-09-14): an attempt to make
|
||||
* this attributable by calling vm_interpret(self, "MSG-TICK") here was
|
||||
* tried and reverted -- it introduced a second, distinct hang (a stall in
|
||||
* sk_repl_headless_wait()'s WIREBIND-attachment wait, not root-caused) in
|
||||
* place of the switch-storm it was meant to fix. The switch-storm's real
|
||||
* cause (sk_vm_switch_signal_tick()'s "who is current" check via
|
||||
* vm_log_attributed_vm() never seeing a VM that only ever runs this raw
|
||||
* C trampoline) is instead fixed at the source in switch.c/
|
||||
* capsule_vm_switch_signal.c: sk_vm_context_switch() now tracks "current"
|
||||
* itself, independent of vm_interpret()'s own attribution bookkeeping.
|
||||
* This placeholder's behavior is therefore unchanged from Stage 2.
|
||||
* History. Until 2026-10-14 this only yielded (a Stage 2 placeholder), and
|
||||
* a VM's messages were run for it from Hera's idle beat, on her stack --
|
||||
* the second of the two delivery paths FABRIC-3.5.md §XXXII ruled down to
|
||||
* one. A first attempt at giving it work (2026-09-14: vm_interpret(self,
|
||||
* "MSG-TICK") here) was reverted after a stall in
|
||||
* sk_repl_headless_wait()'s WIREBIND wait, never root-caused; the switch
|
||||
* storm it was aimed at was fixed at its source instead (switch.c and
|
||||
* capsule_vm_switch_signal.c track "current" themselves).
|
||||
*/
|
||||
void sk_vm_switch_entry(void) {
|
||||
VM *self = g_switch_entry_vm;
|
||||
for (;;) {
|
||||
sk_vm_context_switch(self, self->native_switch_back_to);
|
||||
/* ONE PATH (FABRIC-3.5.md §XXXII, built 2026-10-14): what a VM does
|
||||
* on its turn is take its own messages, here, on its own stack --
|
||||
* the only place a VM that nothing else drives ever takes one
|
||||
* (Hera's idle beat used to run them for it, on her stack). Not
|
||||
* while its interpreter is mid-line on some other, parked, stack
|
||||
* (a console VM waiting in KEY, say): then they wait, and it asks
|
||||
* for another turn. */
|
||||
if (self->interpret_depth == 0) {
|
||||
while (sk_hermes_drain_checkpoint(self) != 0) { }
|
||||
}
|
||||
if (sk_hermes_pending_count(self->stadium_vm_id) > 0) {
|
||||
sk_vm_switch_signal_mark_work(self->stadium_vm_id);
|
||||
}
|
||||
switch_core(self, self->native_switch_back_to, VM_STATE_LIVE);
|
||||
}
|
||||
}
|
||||
|
||||
int sk_vm_context_switch(VM *from, VM *to) {
|
||||
int sk_vm_switch_service_pending(VM *from) {
|
||||
VMRegistryEntry target;
|
||||
VMUuid target_id = sk_vm_switch_signal_take_pending();
|
||||
|
||||
if (!from || vm_uuid_equal(target_id, vm_uuid_none())) return 0;
|
||||
if (capsule_vm_registry_get(target_id, &target) != 0 ||
|
||||
!target.vm_ptr || target.vm_ptr == (void *)from) {
|
||||
return 0;
|
||||
}
|
||||
/* FABRIC-3.md §XXVIII Stage 4 (2026-09-14): a target marked
|
||||
* pending_reap is a WIREBIND identity whose owning device has
|
||||
* physically detached while it sat parked SWITCHED_OUT --
|
||||
* capsule_vm_kill() already refused to free it when that happened
|
||||
* (see VMRegistryEntry.pending_reap's own doc comment). This is the
|
||||
* safe point the mark-and-defer design calls for: reap instead of
|
||||
* resuming, and skip the switch -- take_pending() already consumed the
|
||||
* pending flag, so no other caller will try this same dead end. */
|
||||
if (target.pending_reap) {
|
||||
capsule_vm_force_reap(target_id);
|
||||
return 0;
|
||||
}
|
||||
sk_vm_switch_signal_note_switch_performed(target_id);
|
||||
sk_vm_context_switch(from, (VM *)target.vm_ptr);
|
||||
/* Resumes here once something later switches back. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* `from_state` is what `from` is while it is parked. VM_STATE_SWITCHED_OUT
|
||||
* for a VM switched out mid-line (the colon-word checkpoint, Hera's idle
|
||||
* beat): its stack holds live frames, and capsule_vm_kill() must refuse
|
||||
* it. VM_STATE_LIVE for a VM yielding from the top of its own loop in
|
||||
* sk_vm_switch_entry() (2026-10-14): its stack holds nothing but that loop,
|
||||
* so it is as alive, as killable and as reachable by name as a VM that has
|
||||
* never had a turn -- marking it SWITCHED_OUT made the console stop
|
||||
* relaying to a WIREBIND identity after its first message, and made KILL
|
||||
* refuse every VM that had ever taken one. */
|
||||
static int switch_core(VM *from, VM *to, VMState from_state) {
|
||||
uint64_t new_sp;
|
||||
|
||||
if (!from || !to || to->native_stack_top == 0) {
|
||||
@@ -126,7 +172,7 @@ int sk_vm_context_switch(VM *from, VM *to) {
|
||||
}
|
||||
|
||||
capsule_vm_set_state(to->stadium_vm_id, VM_STATE_LIVE);
|
||||
capsule_vm_set_state(from->stadium_vm_id, VM_STATE_SWITCHED_OUT);
|
||||
capsule_vm_set_state(from->stadium_vm_id, from_state);
|
||||
g_switch_current_vm = to;
|
||||
|
||||
sk_vm_switch_to(&from->native_stack_saved_sp, new_sp);
|
||||
@@ -138,4 +184,8 @@ int sk_vm_context_switch(VM *from, VM *to) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sk_vm_context_switch(VM *from, VM *to) {
|
||||
return switch_core(from, to, VM_STATE_SWITCHED_OUT);
|
||||
}
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
Reference in New Issue
Block a user