Files
LithosAnanake/include/starkernel/capsule_vm_switch_signal.h
T
Robert Allan JamesandClaude Sonnet 5 d9da82b065 Stage 4 increments 2+3: WIREBIND VMs as switch-signal participants + mark-and-defer tombstone reap (FABRIC-3.md §XXVIII Stage 4)
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
2026-09-15 00:58:34 -04:00

121 lines
5.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 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 */