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>
This commit is contained in:
Robert Allan James
2026-09-23 17:53:55 -04:00
co-authored by Claude Opus 5.5
parent 7c3736841d
commit 06bcab504d
27 changed files with 64636 additions and 218 deletions
+59 -27
View File
@@ -68,6 +68,8 @@
#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
@@ -97,44 +99,74 @@ int sk_vm_switch_signal_register(VMUuid vm_id);
* 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);
/*
* 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.
*/
/* 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);
/* 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);
/* 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. */
/* 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);
/* 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. */
/* 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) -- all of this state already existed for the switch
* decision itself; these just make it observable. */
* 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);
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 */
/* 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
+4
View File
@@ -165,6 +165,10 @@ typedef struct {
uint32_t orig_type;
int in_use;
VMUuid owner; /* VM whose reservoir funded this message (task 2.7) */
uint64_t wait_stamp; /* FABRIC-3.8.md §3.13.3 (Stage C-b): unique
* admission stamp (sk_vm_switch_signal_next_stamp())
* -- how long the recipient has been waiting,
* for the switch decision's tie-break */
} SkHermesMessage;
/*
+13
View File
@@ -55,6 +55,19 @@ int sk_vm_context_switch(struct VM *from, struct VM *to);
struct VM *sk_vm_switch_current_vm(void);
void sk_vm_switch_set_current(struct VM *vm);
/* FABRIC-3.8.md §3.13.3 (Stage C-b): the word checkpoint's switch point.
* Call only where `vm` owns its outermost frame (vm_core.c gates this with
* sk_vm_owns_outermost_frame()). Re-decides only if something physical
* changed since the last decision (a beat of the running VM, or mail);
* switches to a strictly cooler VM with work, if there is one. */
void sk_vm_switch_checkpoint(struct VM *vm);
/* FABRIC-3.8.md §3.13.5 (Stage C-b, ruling C-5): the idle point. Called
* from the console's input poll loops (repl.c) each pass with no input.
* Acts only when Hera is running with no frame live (between lines); then
* switches to the coolest VM with work, if any. */
void sk_vm_idle_point(void);
#endif /* __STARKERNEL__ */
#endif /* STARKERNEL_VM_SWITCH_H */