DoE CSV gained 6 switch-signal columns (switch_count_cumulative, switch_current_slot, switch_*_readiness, switch_ticks_since), and verifying them with a boot-time HB-ON probe surfaced a real livelock: the preemption checkpoint could fire inside a VM-EXEC-nested execute_colon_word() call and switch away from a stack it didn't own, parking a borrowed region of the caller's stack under the wrong VM's saved-context pointer. The trampoline bounce was the visible (safe) half of this; the corruption was the quiet half, live in every prior "clean" Stage 3 boot without ever showing up in the log. Fixed by gating the checkpoint on being at the outermost vm_interpret() call (g_vm_interpret_depth / sk_vm_at_outermost_interpret(), vm_core.c), per Bob's decision. Also fixed two related bugs found in the same pass: g_switch_back_to was a single global stale after first entry, now per-VM state (native_switch_back_to); note_switch_performed() fired on resume instead of switch-out, now called before the switch. Verified on all 3 architectures: steady log growth (no freeze), zero leaked QEMU processes, DoE columns internally consistent, Hermes/Artemis confirmed genuinely executing (not just trampoline-bouncing). Temporary HB-ON boot probe reverted after capture. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016UNhH1mhi52i6Qihh7ZV5S
97 lines
4.2 KiB
C
97 lines
4.2 KiB
C
/*
|
||
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.
|
||
*/
|
||
|
||
/**
|
||
* capsule_vm_switch_signal.h - New, purpose-built "who runs next" signal
|
||
* for preemptive context switching (FABRIC-3.md §XXVIII, Stage 3,
|
||
* 2026-09-13).
|
||
*
|
||
* Deliberately NOT a repurposing of capsule_vm_physics.c's execution-heat
|
||
* engine -- that measures word-level dispatch fairness over millions of
|
||
* executions on a different timescale, and its own header explicitly
|
||
* documents it as never touched from interrupt context (unlocked, by
|
||
* design). This is a different physical quantity: instant-by-instant
|
||
* run-readiness, consulted from real ISR context (heartbeat_tick()) every
|
||
* timer tick.
|
||
*
|
||
* Concurrency discipline mirrors heartbeat.c's own §21.1-sanctioned
|
||
* pattern for heartbeat_next_period_ns(): single-writer-ISR (tick()) /
|
||
* single-reader-mainline (take_pending(), called from the cooperative
|
||
* checkpoint in execute_colon_word()), no lock, because nothing on this
|
||
* single hart is concurrent with the ISR while it runs.
|
||
*
|
||
* NOT truly interrupt-driven register/stack swapping (that was
|
||
* considered and rejected for this stage -- see FABRIC-3.md §XXVIII
|
||
* Stage 3 for why): the ISR only ever sets a flag. The actual switch
|
||
* (Stage 2's already-proven sk_vm_context_switch()) happens later, at a
|
||
* safe cooperative checkpoint on the mainline, once per word dispatch.
|
||
*
|
||
* Slot table is sized with headroom, not hardcoded to exactly today's 3
|
||
* participants (Hera/Hermes/Artemis) -- extending participation later
|
||
* (Stage 4+) is another sk_vm_switch_signal_register() call, not a
|
||
* redesign.
|
||
*/
|
||
|
||
#ifndef STARKERNEL_CAPSULE_VM_SWITCH_SIGNAL_H
|
||
#define STARKERNEL_CAPSULE_VM_SWITCH_SIGNAL_H
|
||
|
||
#ifdef __STARKERNEL__
|
||
|
||
#include <stdint.h>
|
||
#include "starkernel/vm_uuid.h"
|
||
|
||
/* Register a VM as a switch-signal participant. Returns its slot index,
|
||
* or -1 if the slot table is full. Call once per participating VM,
|
||
* after that VM is fully born (never mid-birth -- this stage has no
|
||
* critical-section protection against being switched away mid-setup). */
|
||
int sk_vm_switch_signal_register(VMUuid vm_id);
|
||
|
||
/* Called from heartbeat_tick() (ISR context) every timer tick. Cheap:
|
||
* iterates only the registered slots (bounded, small). */
|
||
void sk_vm_switch_signal_tick(void);
|
||
|
||
/* Called from the cooperative checkpoint (execute_colon_word(), mainline,
|
||
* once per word dispatch). Returns the VMUuid of a VM that should now be
|
||
* switched to, or vm_uuid_none() if nothing is pending. Clears the
|
||
* pending flag as a side effect -- call at most once per checkpoint. */
|
||
VMUuid sk_vm_switch_signal_take_pending(void);
|
||
|
||
/* Call once, from the same checkpoint, immediately after a switch
|
||
* sk_vm_switch_signal_take_pending() requested actually executes (not if
|
||
* the target turned out invalid/self) -- feeds the DoE CSV counters
|
||
* below. */
|
||
void sk_vm_switch_signal_note_switch_performed(void);
|
||
|
||
/* DoE CSV read-only exposure (FABRIC-3.md §XXVIII Stage 3 follow-on,
|
||
* 2026-09-13) -- all of this state already existed for the switch
|
||
* decision itself; these just make it observable. */
|
||
uint64_t sk_vm_switch_signal_switch_count(void); /* cumulative, since boot */
|
||
uint32_t sk_vm_switch_signal_ticks_since_switch(void);
|
||
int sk_vm_switch_signal_current_slot(void); /* -1 = none/unregistered */
|
||
int sk_vm_switch_signal_slot_count(void);
|
||
uint32_t sk_vm_switch_signal_readiness(int slot); /* 0 if slot out of range */
|
||
uint32_t sk_vm_switch_signal_readiness_of(VMUuid vm_id); /* 0 if not registered */
|
||
|
||
#endif /* __STARKERNEL__ */
|
||
|
||
#endif /* STARKERNEL_CAPSULE_VM_SWITCH_SIGNAL_H */
|