Files
LithosAnanake/include/starkernel/capsule_vm_switch_signal.h
T
Robert Allan JamesandClaude Opus 5.5 06bcab504d Stage C-b: compudynamic switch decision, idle point, timer path removed (FABRIC-3.8 §3.13.3-§3.13.5)
Who runs next is now sk_vm_switch_decide(): among participants with work
(pending kernel-Hermes mail, parked mid-task, or Hera with console input),
the coolest by VM-physics heat; ties with the incumbent keep the incumbent,
ties among candidates go to the longest waiting (unique wait stamps). No
scheduler state, no timer: heartbeat_tick() no longer touches switching,
readiness/threshold/has_work/take_pending removed. Decided at owned word
checkpoints (colon words and, new, between top-level words) after a beat
or new mail, at the trampoline between messages, and at Hera's idle point
in the console poll loops. The idle pump skips participants (G3 closed for
them): the WIREBIND relay is delivered by a real switch to rajames~user.

Probe (restored away): relay via switch Hera->rajames~user->Hera on all
three ISAs, parked saved_sp inside own stack, walk-order disagreements 0
over tens of millions of decisions, 0 bytes idle log growth. Acceptance:
dict_hash unchanged, relay 3, no faults. Rulings: C-3, C-5 as proposed;
C-4 taken as (alpha), consequence recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 17:53:55 -04:00

180 lines
8.4 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) 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.
*
* FABRIC-3.6.md task 3.1 (2026-09-21, ruled B2 / FABRIC-3.5.md §XLV.2): the
* slot table is no longer a fixed SK_SWITCH_MAX_SLOTS=16 compile-time array.
* It is kmalloc'd at boot by sk_vm_switch_signal_boot_init(), sized from
* stadium_max_vm_count() -- the same RAM-derived population bound Stadium
* and session.c already use (session_boot_init() is the direct precedent
* mirrored here). Every switch-signal participant is a Stadium VM, so
* reusing that bound directly (rather than re-deriving a separate RAM
* budget) needs no new sizing formula.
*/
#ifndef STARKERNEL_CAPSULE_VM_SWITCH_SIGNAL_H
#define STARKERNEL_CAPSULE_VM_SWITCH_SIGNAL_H
#ifdef __STARKERNEL__
#include <stdint.h>
#include "starkernel/vm_uuid.h"
struct VM;
/* Boot-time allocation (FABRIC-3.6.md task 3.1, 2026-09-21): kmalloc's the
* slot table to stadium_max_vm_count() entries. Must run after
* stadium_boot_init() (that bound is 0, and this fails, until Stadium has
* computed it) and before the first sk_vm_switch_signal_register() call.
* Soft failure -- returns -1 and leaves the table unallocated (capacity 0,
* so register() below simply refuses every registration) rather than
* halting boot, same posture as stadium_boot_init()/session_boot_init().
* Idempotent-unsafe: calling twice leaks the first allocation, so callers
* must call it exactly once. */
int sk_vm_switch_signal_boot_init(void);
/* Register a VM as a switch-signal participant. Returns its slot index,
* or -1 if the slot table is full (or sk_vm_switch_signal_boot_init() was
* never called / failed). 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_vm_switch_signal_slot_capacity() 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);
/*
* FABRIC-3.8.md §3.13 (Stage C-b, 2026-09-23): the ISR half of this module
* (sk_vm_switch_signal_tick() from heartbeat_tick(), readiness accrual
* against a fixed 50-tick threshold, a pending flag) is removed -- the
* timer no longer decides anything. This module's own header comment above
* (not a repurposing of VM-physics heat) is reversed by Captain Bob's
* §3.11 ruling: the decision below IS a VM-physics heat comparison, made
* workable at switch granularity by Stage C-a's per-beat charge.
*/
/* 1 if vm_id is a registered participant (the idle pump skips these: their
* mail is delivered on their own stacks, by a switch). */
int sk_vm_switch_signal_is_participant(VMUuid vm_id);
/* A fresh, unique wait stamp (mainline, single writer). kernel_hermes.c
* stamps each admitted message with one. */
uint64_t sk_vm_switch_signal_next_stamp(void);
/* vm_id just stopped running (sk_vm_context_switch(), for `from`): record
* when, for the "waiting longest" tie-break. */
void sk_vm_switch_signal_note_parked(VMUuid vm_id);
/* Physical events after which the decision may differ: the running VM
* produced a beat (vm_tick(), right after its Stage C-a charge), or mail
* was admitted (sk_hermes_send_one()). take_decision_due() consumes the
* flag at the next owned checkpoint. */
void sk_vm_switch_signal_note_beat(void);
void sk_vm_switch_signal_note_mail(void);
int sk_vm_switch_signal_take_decision_due(void);
/*
* sk_vm_switch_decide - who runs next, by physical comparison (§3.13.3).
*
* Among registered participants other than `self` that have work -- a
* pending kernel-Hermes message, or parked on their own stack mid-task
* (top frame not VM_FRAME_IDLE), or (Hera only) pending console input --
* returns the one with the lowest VM-physics heat; among equally cool
* candidates, the one waiting longest (unique wait stamps). With
* self_competes set (the caller still has work), returns a candidate only
* if it is strictly cooler than `self`. Returns NULL when nobody should
* run instead. A pure function of physical state: it keeps nothing
* between calls, and its answer does not depend on table walk order. Not
* a scheduler -- no queue, no priority list, nothing on a clock.
*/
struct VM *sk_vm_switch_decide(struct VM *self, int self_competes);
/* Reap one participant marked pending_reap (a WIREBIND identity whose
* device detached while it was parked), if any. Call only at a safe point
* (checkpoint, trampoline, idle point) -- same contract the old Stage 3
* checkpoint's inline reap had. */
void sk_vm_switch_reap_pending(void);
/* Call once, immediately before a decided switch executes -- feeds the DoE
* CSV counters below. */
void sk_vm_switch_signal_note_switch_performed(VMUuid target_id);
/* SWITCH-MARK-WORK's body. Stage C-b: no flag to set any more (the queue
* is the eligibility); only asks for a fresh decision. */
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). Stage C-b: ticks_since_switch now counts beats of the
* running VM since the last switch (§3.13.4), not ISR ticks; the per-VM
* readiness getters are gone with the readiness itself. */
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);
/* FABRIC-3.6.md task 3.1 (2026-09-21): the table's boot-time-computed
* capacity (0 if sk_vm_switch_signal_boot_init() was never called or
* failed) -- the dynamic replacement for the old compile-time
* SK_SWITCH_MAX_SLOTS=16. */
int sk_vm_switch_signal_slot_capacity(void);
#endif /* __STARKERNEL__ */
#endif /* STARKERNEL_CAPSULE_VM_SWITCH_SIGNAL_H */