/* 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. */ /** * switch.c - Cooperative VM context switch glue (FABRIC-3.md §XXVIII, * Stage 2, 2026-09-13). See switch.h for the contract. */ #ifdef __STARKERNEL__ #include "starkernel/vm/switch.h" #include "vm.h" #include "starkernel/capsule_birth.h" /* capsule_vm_set_state */ #include /* Arch-specific (switch.S in each arch dir). sk_vm_switch_prime() builds * the exact synthetic first-entry frame sk_vm_switch_to() expects -- kept * in assembly, not duplicated here, so the layout can never drift out of * sync between the two. */ extern void sk_vm_switch_to(uint64_t *save_sp_out, uint64_t new_sp); extern uint64_t sk_vm_switch_prime(uint64_t stack_top, void *entry); /* Single-writer-mainline global, safe under this stage's cooperative-only, * one-VM-runs-at-a-time-on-one-hart discipline (same class of bookkeeping * as vm_core.c's g_log_attrib_vm) -- identifies which VM a freshly-primed * native stack belongs to. Read exactly once, synchronously, at the very * top of sk_vm_switch_entry() before any other first-entry could occur. * Safe to keep as a plain global (unlike the old g_switch_back_to this * file used to also carry -- see native_switch_back_to's own doc comment * in vm.h for why that one had to move to per-VM state): a VM's identity * never changes across the lifetime of its own trampoline instance, only * who most recently switched into it does. */ static VM *g_switch_entry_vm; /* 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 * nested vm_interpret() calls for log-source attribution and has no way * to know a VM is "running" while parked inside this file's own raw C * trampoline (sk_vm_switch_entry()), never itself calling vm_interpret(). * sk_vm_switch_signal_tick() (capsule_vm_switch_signal.c) consults this * instead of vm_log_attributed_vm() for exactly that reason -- see its * own updated comment. Single-writer-mainline, same discipline as * g_switch_entry_vm above: set unconditionally on every switch, by the * only code path that can ever change who is physically running. * Seeded once at boot via sk_vm_switch_set_current() for whichever VM is * running before any switch has ever happened (Hera, at Stage 3 * registration time -- kernel_main.c). */ static VM *g_switch_current_vm; 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. * * 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 * 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. * * 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. */ void sk_vm_switch_entry(void) { VM *self = g_switch_entry_vm; for (;;) { sk_vm_context_switch(self, self->native_switch_back_to); } } int sk_vm_context_switch(VM *from, VM *to) { uint64_t new_sp; if (!from || !to || to->native_stack_top == 0) { return -1; } /* Recorded unconditionally (first-ever entry or a resume) so whoever * is parked at `to`'s native stack -- most often this placeholder * trampoline -- always knows who most recently switched into it. */ to->native_switch_back_to = from; if (to->native_stack_saved_sp == 0) { /* First-ever entry: synthesize the initial frame. */ g_switch_entry_vm = to; new_sp = sk_vm_switch_prime(to->native_stack_top, (void *)&sk_vm_switch_entry); } else { new_sp = to->native_stack_saved_sp; to->native_stack_saved_sp = 0; /* about to be running, not parked */ } capsule_vm_set_state(to->stadium_vm_id, VM_STATE_LIVE); capsule_vm_set_state(from->stadium_vm_id, VM_STATE_SWITCHED_OUT); g_switch_current_vm = to; sk_vm_switch_to(&from->native_stack_saved_sp, new_sp); /* Resumes here only once something later switches back to `from` -- * from `from`'s own point of view, this call simply took a while. */ capsule_vm_set_state(from->stadium_vm_id, VM_STATE_LIVE); g_switch_current_vm = from; return 0; } #endif /* __STARKERNEL__ */