Increment 2: WIREBIND user VMs (the ones that actually run FORTH work; console VMs are pure REPL proxies and never participate) register as Stage 3 switch-signal participants at attach, unregister at teardown. Slot table bumped 8 -> 16, matching messaging.4th's own VM-MAX -- a real, already-agreed ceiling, not an invented number. Added sk_vm_switch_signal_unregister() (compaction-based; Tripod VMs never needed removal, WIREBIND VMs cycle constantly and would otherwise exhaust the bounded table). Increment 3: implements the plan's own ratified option (A) for the async-detach UAF risk -- mark-and-defer via a new pending_reap flag on VMRegistryEntry, deliberately not a new VMState (capsule_vm_kill() already treats VM_STATE_DEAD as idempotent success, which would silently swallow a reap attempt; SWITCHED_OUT still accurately describes a tombstoned VM until the moment it's actually freed). unclean_detach() sets it when capsule_vm_kill() refuses a SWITCHED_OUT target; the Stage 3 checkpoint (vm_core.c) checks it before ever attempting to resume a pending switch target, and calls the new capsule_vm_force_reap() instead -- the one caller allowed to bypass capsule_vm_kill()'s own refusal, because it runs at the exact safe cooperative point the switcher itself controls. A new idle-tick sweep cleans up the WIREBIND live-table entry once the reap has actually happened. Verified clean on all 3 architectures (baseline regression -- no WIREBIND attach happens in a plain boot). The reap mechanism's own correctness under a genuinely parked context is verified separately, next, via a temporary deterministic probe. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
121 lines
5.7 KiB
C
121 lines
5.7 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);
|
||
|
||
/* Remove a switch-signal participant (FABRIC-3.md §XXVIII Stage 4,
|
||
* 2026-09-14) -- Tripod VMs never need this (they live forever), but
|
||
* WIREBIND-birthed identity VMs cycle through attach/detach repeatedly
|
||
* and must free their slot for reuse, or the bounded table exhausts
|
||
* after SK_SWITCH_MAX_SLOTS attach/detach cycles. Compacts the table
|
||
* (small, bounded, mutated only at attach/detach -- not a hot path).
|
||
* Clears a pending switch targeting this VM, if any, so the checkpoint
|
||
* never attempts to switch into a no-longer-registered participant.
|
||
* No-op (returns -1) if vm_id was never registered. */
|
||
int sk_vm_switch_signal_unregister(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. Also resets `target_id`'s own readiness/has_work directly
|
||
* (Stage 3 follow-on correction, 2026-09-14) -- see the .c file's own doc
|
||
* comment on why this can't be left to tick()'s current-slot polling. */
|
||
void sk_vm_switch_signal_note_switch_performed(VMUuid target_id);
|
||
|
||
/* Message-arrival eligibility hook (FABRIC-3.md §XXVIII Stage 3 follow-on,
|
||
* 2026-09-14): mark that VM `vm_id` was just sent a message (the FORTH-side
|
||
* MSG-SEND hook in capsules/common/messaging.4th calls this via the new
|
||
* SWITCH-MARK-WORK primitive, mainline, single-writer). Consulted by
|
||
* sk_vm_switch_signal_tick()'s own readiness->pending decision so a VM
|
||
* with nothing recently sent to it never becomes a switch target purely by
|
||
* sitting idle long enough -- closes the wasteful (but, since the Stage 3
|
||
* stack-ownership fix, no longer corrupting) trampoline-bounce cycle for
|
||
* an idle participant. No-op for an unregistered vm_id. */
|
||
void sk_vm_switch_signal_mark_work(VMUuid vm_id);
|
||
|
||
/* 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 */
|