/* 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 globals, 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) -- set immediately before a * first-ever switch into a VM, read exactly once, synchronously, at the * very top of sk_vm_switch_entry() before any other switch could occur. * Preemption (Stage 3) will need to revisit this; flagged there, not here. */ static VM *g_switch_entry_vm; static VM *g_switch_back_to; /** * 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 * switched to it, forever. Must never fall off the end -- there is no * legitimate return address below a synthetic first-entry frame, unlike * an ordinary function. */ void sk_vm_switch_entry(void) { VM *self = g_switch_entry_vm; VM *back = g_switch_back_to; for (;;) { sk_vm_context_switch(self, back); } } int sk_vm_context_switch(VM *from, VM *to) { uint64_t new_sp; if (!from || !to || to->native_stack_top == 0) { return -1; } if (to->native_stack_saved_sp == 0) { /* First-ever entry: synthesize the initial frame. */ g_switch_entry_vm = to; g_switch_back_to = from; 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); 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); return 0; } #endif /* __STARKERNEL__ */