Stage 3: timer-driven preemptive switching, live on all 3 arches (FABRIC-3.md §XXVIII)
Fourth stage of the preemptive context-switching plan, and the biggest. LithosAnanke now genuinely, continuously preempts between Hera, Hermes, and Artemis -- timer-driven, running live for the entire remainder of every boot once the Tripod fleet registers, not a bounded probe. A real design fork was resolved before writing code: the naive approach (the timer ISR calling Stage 2's sk_vm_context_switch() directly) is broken -- Stage 0's trap frame lives on whatever stack was active at interrupt time, and jumping to a different stack via Stage 2's own independent swap mid-handler would abandon that trap frame unresumed, guaranteed corruption on the first tick. Chose the safer of two named options: the ISR only ever sets a flag and returns completely normally through its own full epilogue; the actual switch happens moments later, via Stage 2's already-proven mechanism, at a safe cooperative checkpoint on the mainline (execute_colon_word()'s per-word dispatch loop, checked on literally every word, not throttled to the existing 256-word heartbeat-tuning cadence) -- confirmed with the user that word-level granularity is fine-grained enough given the eventual Zynq FPGA target where a word is a mnemonic. New capsule_vm_switch_signal.c/.h: a purpose-built run-readiness signal, deliberately separate from capsule_vm_physics.c's execution-heat engine (that one's own header documents itself as never touched from interrupt context, by design). Slot table sized with headroom (8) rather than hardcoded to today's 3 participants, so extending participation later is another register() call, not a redesign -- per direct request to leave room for swapping the participant set. Simple linear accumulate-then- threshold for this first cut; a fancier law can replace it later without touching the mechanism around it. heartbeat_tick() gains its one deliberate, documented amendment to this file's own top-half/bottom-half discipline -- the first time this codebase reaches into VM-scheduling state from real ISR context. Registration happens only after all three VMs are fully born, right before the REPL starts -- no critical-section protection yet against being switched away mid-birth-setup. Known, flagged rough edge (not reconciled this pass): MSG-TICK's own idle-pump and this new mechanism can still independently move control between the same VMs; not observed to interact badly in verification, but not fully unified either. Verified interactively at the console on all 3 architectures with continuous background preemption running throughout -- amd64 computed `1 1 + .` -> 2, aarch64 computed `1 1 + dup DUP * . CR` -> 4, both correct, REPL fully responsive, zero fault indicators over sustained runtime. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016UNhH1mhi52i6Qihh7ZV5S
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f790d0995e
commit
986d042aa7
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
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.c - see the header for the full contract
|
||||
* (FABRIC-3.md §XXVIII, Stage 3, 2026-09-13).
|
||||
*/
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include "starkernel/capsule_vm_switch_signal.h"
|
||||
#include "vm.h"
|
||||
#include "starkernel/log_attrib.h" /* vm_log_attributed_vm() */
|
||||
#include <stddef.h>
|
||||
|
||||
/* Headroom beyond today's 3 participants (Hera/Hermes/Artemis) -- see
|
||||
* the header's own doc comment on why this isn't hardcoded to 3. */
|
||||
#define SK_SWITCH_MAX_SLOTS 8
|
||||
|
||||
/* Ticks a non-running participant must accumulate readiness before a
|
||||
* switch to it is requested. Simple linear accumulate-then-threshold,
|
||||
* not the fancier relaxation law a later pass might want -- correctness
|
||||
* and a clean single-writer/single-reader story mattered more than the
|
||||
* exact law for this first cut (this stage's own design-review latitude,
|
||||
* per the plan). Reset to 0 the instant a slot becomes current, so a
|
||||
* VM that just ran needs a full threshold's worth of ticks idle before
|
||||
* it can be selected again -- a crude but real fairness property. */
|
||||
#define SK_SWITCH_READINESS_THRESHOLD 50u
|
||||
|
||||
typedef struct {
|
||||
VMUuid vm_id;
|
||||
uint32_t readiness;
|
||||
} sk_switch_slot_entry_t;
|
||||
|
||||
static sk_switch_slot_entry_t g_slots[SK_SWITCH_MAX_SLOTS];
|
||||
static int g_slot_count;
|
||||
static int g_pending;
|
||||
static VMUuid g_pending_target;
|
||||
|
||||
static int slot_for_vm_id(VMUuid vm_id) {
|
||||
int i;
|
||||
for (i = 0; i < g_slot_count; i++) {
|
||||
if (vm_uuid_equal(g_slots[i].vm_id, vm_id)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sk_vm_switch_signal_register(VMUuid vm_id) {
|
||||
if (g_slot_count >= SK_SWITCH_MAX_SLOTS) return -1;
|
||||
g_slots[g_slot_count].vm_id = vm_id;
|
||||
g_slots[g_slot_count].readiness = 0;
|
||||
return g_slot_count++;
|
||||
}
|
||||
|
||||
void sk_vm_switch_signal_tick(void) {
|
||||
VM *current;
|
||||
int current_slot, i;
|
||||
|
||||
current = vm_log_attributed_vm();
|
||||
if (!current) return; /* nothing interpreting right now (boot/driver/HAL context) */
|
||||
|
||||
current_slot = slot_for_vm_id(current->stadium_vm_id);
|
||||
if (current_slot < 0) return; /* current VM isn't a registered participant */
|
||||
|
||||
for (i = 0; i < g_slot_count; i++) {
|
||||
if (i == current_slot) {
|
||||
g_slots[i].readiness = 0;
|
||||
} else {
|
||||
g_slots[i].readiness++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_pending) {
|
||||
for (i = 0; i < g_slot_count; i++) {
|
||||
if (i != current_slot && g_slots[i].readiness >= SK_SWITCH_READINESS_THRESHOLD) {
|
||||
g_pending = 1;
|
||||
g_pending_target = g_slots[i].vm_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VMUuid sk_vm_switch_signal_take_pending(void) {
|
||||
if (!g_pending) return vm_uuid_none();
|
||||
g_pending = 0;
|
||||
return g_pending_target;
|
||||
}
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
Reference in New Issue
Block a user