Drain at the outermost checkpoint -- FABRIC-3.6.md task 3.4
sk_hermes_drain_checkpoint() interprets one queued payload per checkpoint (ruled: one message per checkpoint), reusing sk_vm_at_outermost_interpret() and placed before the switch-signal block in vm_core.c's existing cooperative checkpoint (sk_vm_context_switch() doesn't return until switched back to, so drain must come first or it silently never runs on a switching checkpoint). Amends FABRIC-3.5.md SXLIII.5, caught by advisor() before writing the naive version: "recursive drain is prevented for free" via g_vm_interpret_depth is true but only for same-message re-drain -- it doesn't cover the separate same-VM reentrancy hazard FABRIC-3.md SXX already named for Hera specifically (VMCallState saves rsp/exit_colon/ ecw_nesting only, never input_buffer/input_length/input_pos). Draining calls vm_interpret() on the same vm whose own vm_interpret() call is still paused mid-word at the checkpoint; without saving and restoring the cursor by hand, the enclosing REPL line or LOAD block would be silently truncated. sk_hermes_drain_checkpoint() snapshots and restores input_buffer/input_length/input_pos/mode/error/abort_requested around the call. Not a divergence from the ruling -- cursor preservation is the implementer's own obligation inside the ruled mechanism. Gated behind a system-wide pending-total counter so the common no-message-in-flight case costs one integer read per word dispatch, not a stadium_max_vm_count()-sized queue scan (also flagged by advisor() as a real hot-path cost, not deferred). Verified live on all three architectures: a self-test publishes a real payload to Hermes, proves the depth gate via VM-EXEC-ing an existing harmless colon word into Hermes (genuine nested vm_interpret(), depth 2, must not drain), then drains directly from genuinely-outermost context and confirms exactly one clean drain. dict_hash unmoved and identical across architectures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
1f6343bc03
commit
2b1ba031a5
@@ -62,6 +62,7 @@
|
||||
#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/vm/kernel_hermes.h" /* FABRIC-3.6.md task 3.4 -- sk_hermes_drain_checkpoint() */
|
||||
#include "starkernel/capsule_birth.h" /* capsule_vm_registry_get() */
|
||||
|
||||
/* FABRIC-3.md §XXVIII Stage 3 correction (2026-09-13): forward-declared
|
||||
@@ -910,6 +911,19 @@ void execute_colon_word(VM* vm)
|
||||
}
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
/* FABRIC-3.6.md task 3.4 (SXLIII.3-.5): the target drains its own
|
||||
* pending queue here, in its own context, at its own outermost
|
||||
* interpret checkpoint -- same gate, same placement, same
|
||||
* defer-if-nested discipline the switch-signal checkpoint just
|
||||
* below already uses (SXLIII.2/.4: this IS that machinery,
|
||||
* generalized). Placed BEFORE the switch-signal block, not after:
|
||||
* sk_vm_context_switch() below does not return until something
|
||||
* later switches back to `vm`, so a drain placed after it would
|
||||
* silently never run on any checkpoint that switches. */
|
||||
if (sk_vm_at_outermost_interpret()) {
|
||||
(void)sk_hermes_drain_checkpoint(vm);
|
||||
}
|
||||
|
||||
/* 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
|
||||
|
||||
Reference in New Issue
Block a user