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>
180 lines
8.4 KiB
C
180 lines
8.4 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.
|
||
*
|
||
* 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 */
|