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
+63
@@ -3635,3 +3635,66 @@ Stage 3 (timer-driven preemption, Tripod fleet only, the new run-readiness signa
|
|||||||
the biggest remaining stage, and the first one that actually amends the §21.1
|
the biggest remaining stage, and the first one that actually amends the §21.1
|
||||||
nothing-is-concurrent-on-one-hart ruling for real.
|
nothing-is-concurrent-on-one-hart ruling for real.
|
||||||
|
|
||||||
|
**Stage 3 CLOSED, same day.** LithosAnanke now genuinely, continuously preempts between Hera,
|
||||||
|
Hermes, and Artemis, timer-driven, for real -- not a bounded probe window like Stage 2, but live
|
||||||
|
for the entire remainder of every boot from the moment the Tripod fleet registers onward.
|
||||||
|
Verified interactively at the console on all 3 architectures (`1 1 + .` -> `2` on amd64,
|
||||||
|
`1 1 + dup DUP * . CR` -> `4` on aarch64), with continuous background switching running the
|
||||||
|
whole time underneath -- the REPL stayed fully responsive and computed correctly throughout.
|
||||||
|
|
||||||
|
**A real design fork was resolved before writing any code, not glossed over:** the naive
|
||||||
|
approach -- the timer ISR's C handler calling Stage 2's `sk_vm_context_switch()` directly --
|
||||||
|
is broken. Stage 0's trap frame lives on whatever stack was active when the interrupt fired;
|
||||||
|
calling into Stage 2's *own, independent* stack-swap from inside that handler would abandon the
|
||||||
|
ISR's own trap frame mid-flight, unresumed, while jumping to a completely different stack via a
|
||||||
|
*second*, unrelated swap -- guaranteed corruption on the first tick. Two ways to do this
|
||||||
|
correctly were named and one deliberately chosen over the other: (1) the ISR only ever sets a
|
||||||
|
flag ("switch requested, target X") and returns completely normally through its own full Stage-0
|
||||||
|
epilogue, with the actual switch happening moments later via Stage 2's already-proven
|
||||||
|
`sk_vm_context_switch()` at a safe cooperative checkpoint on the mainline; vs. (2) true
|
||||||
|
mid-interrupt preemption, where the ISR epilogue itself swaps to a *different* VM's own trap
|
||||||
|
frame before returning -- genuinely preemptable at any instruction, but requires a second,
|
||||||
|
different parked-context representation from Stage 2's and new logic in three arches' raw
|
||||||
|
assembly epilogues, real risk of a silent, unrecoverable crash if subtly wrong. Chose (1):
|
||||||
|
confirmed with Bob that word-level granularity is "fine grained enough," given the eventual Zynq
|
||||||
|
FPGA target where a word is a mnemonic -- the checkpoint fires on literally every single word
|
||||||
|
dispatch (not throttled to the existing 256-word heartbeat-tuning cadence), so in practice the
|
||||||
|
observable latency between "the clock wants a switch" and "the switch happens" is at most one
|
||||||
|
word's worth of execution.
|
||||||
|
|
||||||
|
**The new run-readiness signal** (`capsule_vm_switch_signal.c`/`.h`, new files beside
|
||||||
|
`capsule_vm_physics.c`, deliberately NOT folded into it -- that engine's own header explicitly
|
||||||
|
documents itself as never touched from interrupt context, by design, unlocked): a slot table
|
||||||
|
sized with headroom (8 slots) rather than hardcoded to exactly today's 3 participants, so
|
||||||
|
extending participation later (Stage 4+, or any future VM) is another
|
||||||
|
`sk_vm_switch_signal_register()` call, not a redesign -- a deliberate choice made after Bob asked
|
||||||
|
for room to swap the participant set later, not just the fixed Tripod trio. Simple linear
|
||||||
|
accumulate-then-threshold for this first cut (50 ticks idle before a switch is requested, reset
|
||||||
|
to 0 the instant a slot becomes current) -- correctness and a clean single-writer(ISR)/
|
||||||
|
single-reader(mainline) story mattered more than the exact law; a fancier relaxation curve is
|
||||||
|
available to a later pass without touching the mechanism around it. `heartbeat_tick()` gains its
|
||||||
|
one deliberate, documented amendment to this file's own top-half/bottom-half discipline: it now
|
||||||
|
calls `sk_vm_switch_signal_tick()`, genuinely reaching into VM-scheduling state from real ISR
|
||||||
|
context for the first time in this codebase's history -- named explicitly in the code, not
|
||||||
|
silently slipped in, and kept safe by the same single-hart/nothing-concurrent property §21.1
|
||||||
|
already established for everything else here.
|
||||||
|
|
||||||
|
**The MSG-TICK ownership question the plan flagged turned out to be less sharp than expected**
|
||||||
|
under approach (1): since the actual switch is itself an ordinary cooperative call (just
|
||||||
|
timer-triggered rather than hand-written), it doesn't fight MSG-TICK's own idle-pump for a
|
||||||
|
stack-swap the way true interrupt-driven switching would have. Not fully reconciled this
|
||||||
|
pass -- both mechanisms can still independently decide to move control between the same three
|
||||||
|
VMs, which is a live, accepted rough edge for this first cut, flagged here rather than silently
|
||||||
|
assumed fine. Noted for a later pass, not blocking: the two have not been observed to interact
|
||||||
|
badly in verification so far.
|
||||||
|
|
||||||
|
Registration deliberately happens only after all three VMs are fully confirmed born (right
|
||||||
|
before the interactive REPL starts) -- this stage has no critical-section protection against
|
||||||
|
being switched away mid-birth-setup, so registering any earlier was rejected as a real risk, not
|
||||||
|
a hypothetical one.
|
||||||
|
|
||||||
|
All 3 architectures: clean build, clean boot to `ok>`, sustained runtime with continuous live
|
||||||
|
switching and zero fault indicators, interactive console commands computed correctly. Stage 4
|
||||||
|
(WIREBIND-scope extension) remains a ratified-decision-only step, not attempted -- the async
|
||||||
|
unclean-detach UAF risk it names is unchanged by anything built in Stages 0-3.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Capsule Block Manifest — Auto-generated
|
# Capsule Block Manifest — Auto-generated
|
||||||
<!-- Generated by mkcapsule --manifest 2026-09-13T19:23:06Z -->
|
<!-- Generated by mkcapsule --manifest 2026-09-13T23:29:44Z -->
|
||||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||||
<!-- Hand-written justifications and immutability notes live -->
|
<!-- Hand-written justifications and immutability notes live -->
|
||||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef STARKERNEL_CAPSULE_VM_SWITCH_SIGNAL_H
|
||||||
|
#define STARKERNEL_CAPSULE_VM_SWITCH_SIGNAL_H
|
||||||
|
|
||||||
|
#ifdef __STARKERNEL__
|
||||||
|
|
||||||
|
#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);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
#endif /* __STARKERNEL__ */
|
||||||
|
|
||||||
|
#endif /* STARKERNEL_CAPSULE_VM_SWITCH_SIGNAL_H */
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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__ */
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "starkernel/timer.h"
|
#include "starkernel/timer.h"
|
||||||
|
#include "starkernel/capsule_vm_switch_signal.h" /* FABRIC-3.md §XXVIII Stage 3 */
|
||||||
|
|
||||||
#if defined(ARCH_AMD64)
|
#if defined(ARCH_AMD64)
|
||||||
#define HEARTBEAT_HAS_VARIANCE 1
|
#define HEARTBEAT_HAS_VARIANCE 1
|
||||||
@@ -162,6 +163,20 @@ void heartbeat_tick(void)
|
|||||||
g_pending_counter = heartbeat_read_counter();
|
g_pending_counter = heartbeat_read_counter();
|
||||||
g_heartbeat.ticks++;
|
g_heartbeat.ticks++;
|
||||||
g_pending_valid = 1;
|
g_pending_valid = 1;
|
||||||
|
|
||||||
|
/* FABRIC-3.md §XXVIII, Stage 3 (2026-09-13): the one deliberate,
|
||||||
|
* documented amendment to this file's own top-half/bottom-half
|
||||||
|
* discipline above. sk_vm_switch_signal_tick() reaches into VM
|
||||||
|
* scheduling state from real ISR context -- something this file's
|
||||||
|
* header comment previously said nothing here did. It stays cheap
|
||||||
|
* (bounded iteration over a small slot table, no locking needed for
|
||||||
|
* the same reason nothing else here needs it: single hart, nothing
|
||||||
|
* concurrent with this ISR while it runs) and it never itself
|
||||||
|
* switches anything -- it only ever sets a flag, consumed later at a
|
||||||
|
* safe cooperative checkpoint on the mainline
|
||||||
|
* (execute_colon_word()). The GAP-A1 split itself is not overturned:
|
||||||
|
* everything else in this function is exactly as minimal as before. */
|
||||||
|
sk_vm_switch_signal_tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
void heartbeat_service(void)
|
void heartbeat_service(void)
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ EFI_RUNTIME_SERVICES *g_sk_runtime_services = NULL;
|
|||||||
#include "starkernel/capsule_loader.h"
|
#include "starkernel/capsule_loader.h"
|
||||||
#include "starkernel/capsule_birth.h" /* capsule_birth_mama, capsule_find_mama_init */
|
#include "starkernel/capsule_birth.h" /* capsule_birth_mama, capsule_find_mama_init */
|
||||||
#include "starkernel/capsule_zuse_boot.h" /* capsule_zuse_boot_load_root_pubkey */
|
#include "starkernel/capsule_zuse_boot.h" /* capsule_zuse_boot_load_root_pubkey */
|
||||||
|
#include "starkernel/capsule_vm_switch_signal.h" /* FABRIC-3.md §XXVIII Stage 3 */
|
||||||
#include "starkernel/artemis_sig.h" /* artemis_sig_check/genesis_stamp */
|
#include "starkernel/artemis_sig.h" /* artemis_sig_check/genesis_stamp */
|
||||||
#include "starkernel/kmalloc.h"
|
#include "starkernel/kmalloc.h"
|
||||||
#include "starkernel/repl.h"
|
#include "starkernel/repl.h"
|
||||||
@@ -986,6 +987,27 @@ static void kernel_main_deep(BootInfo *boot_info) {
|
|||||||
}
|
}
|
||||||
console_println("");
|
console_println("");
|
||||||
|
|
||||||
|
/* FABRIC-3.md §XXVIII, Stage 3 (2026-09-13): register the Tripod fleet
|
||||||
|
* as preemptive-switch-signal participants now, only after all three
|
||||||
|
* are confirmed fully born above -- never earlier. This stage has no
|
||||||
|
* critical-section protection against being switched away mid-setup,
|
||||||
|
* so registering any earlier would risk the signal firing during
|
||||||
|
* Hermes/Artemis's own birth sequencing. */
|
||||||
|
{
|
||||||
|
VMRegistryEntry hera_entry, hermes_entry, artemis_entry;
|
||||||
|
if (capsule_vm_registry_get(vm_uuid_hera(), &hera_entry) == 0) {
|
||||||
|
sk_vm_switch_signal_register(hera_entry.vm_id);
|
||||||
|
}
|
||||||
|
if (capsule_vm_find_by_name_nocase("Hermes", &hermes_entry) == 0 &&
|
||||||
|
hermes_entry.state == VM_STATE_LIVE) {
|
||||||
|
sk_vm_switch_signal_register(hermes_entry.vm_id);
|
||||||
|
}
|
||||||
|
if (capsule_vm_find_by_name_nocase("Artemis", &artemis_entry) == 0 &&
|
||||||
|
artemis_entry.state == VM_STATE_LIVE) {
|
||||||
|
sk_vm_switch_signal_register(artemis_entry.vm_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Decided 2026-09-05: no console for the running system unless a
|
/* Decided 2026-09-05: no console for the running system unless a
|
||||||
* thumbdrive is present -- headless by default (EMERGENCY_CONSOLE_
|
* thumbdrive is present -- headless by default (EMERGENCY_CONSOLE_
|
||||||
* ENABLED off), reusing that flag's own existing "does this build
|
* ENABLED off), reusing that flag's own existing "does this build
|
||||||
|
|||||||
@@ -60,6 +60,9 @@
|
|||||||
#include "starkernel/console.h" /* g_sk_fault_word */
|
#include "starkernel/console.h" /* g_sk_fault_word */
|
||||||
#include "platform_alloc.h" /* sf_free for call_stack */
|
#include "platform_alloc.h" /* sf_free for call_stack */
|
||||||
#include "starkernel/vm/stadium_words.h" /* item 4.1: word patrons on the Stadium */
|
#include "starkernel/vm/stadium_words.h" /* item 4.1: word patrons on the Stadium */
|
||||||
|
#include "starkernel/capsule_vm_switch_signal.h" /* FABRIC-3.md §XXVIII Stage 3 */
|
||||||
|
#include "starkernel/vm/switch.h" /* FABRIC-3.md §XXVIII Stage 2 -- sk_vm_context_switch() */
|
||||||
|
#include "starkernel/capsule_birth.h" /* capsule_vm_registry_get() */
|
||||||
#endif
|
#endif
|
||||||
#include "word_source/include/vocabulary_words.h"
|
#include "word_source/include/vocabulary_words.h"
|
||||||
#include "vm_internal.h"
|
#include "vm_internal.h"
|
||||||
@@ -880,6 +883,30 @@ void execute_colon_word(VM* vm)
|
|||||||
vm->heartbeat.check_counter = 0;
|
vm->heartbeat.check_counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __STARKERNEL__
|
||||||
|
/* FABRIC-3.md §XXVIII, Stage 3 (2026-09-13): the cooperative
|
||||||
|
* preemption checkpoint. Deliberately unconditional (every word,
|
||||||
|
* not throttled like the heartbeat cycle above) -- the check
|
||||||
|
* itself is a single flag read, cheap regardless of frequency,
|
||||||
|
* and "a word is a mnemonic" (this project's own eventual FPGA
|
||||||
|
* framing) is the granularity preemption should approximate here.
|
||||||
|
* Only ever acts when this word's own execution left the VM in a
|
||||||
|
* clean, resumable state (past the error/abort/exit_colon checks
|
||||||
|
* below would be too late -- this runs before them, deliberately,
|
||||||
|
* so a switch never happens mid-unwind). */
|
||||||
|
{
|
||||||
|
VMUuid target_id = sk_vm_switch_signal_take_pending();
|
||||||
|
if (!vm_uuid_equal(target_id, vm_uuid_none())) {
|
||||||
|
VMRegistryEntry target_entry;
|
||||||
|
if (capsule_vm_registry_get(target_id, &target_entry) == 0 &&
|
||||||
|
target_entry.vm_ptr && target_entry.vm_ptr != (void *)vm) {
|
||||||
|
sk_vm_context_switch(vm, (VM *)target_entry.vm_ptr);
|
||||||
|
/* Resumes here once something later switches back. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (vm->error) { vm->ecw_nesting--; return; }
|
if (vm->error) { vm->ecw_nesting--; return; }
|
||||||
|
|
||||||
/* ABORT clears stacks and unwinds to QUIT: leave the flag set so
|
/* ABORT clears stacks and unwinds to QUIT: leave the flag set so
|
||||||
|
|||||||
Reference in New Issue
Block a user