FABRIC-3.8.md Stage A: per-VM stack-ownership gate for drain and switch

Replaces the global g_vm_interpret_depth test at both execute_colon_word()
checkpoints (kernel-Hermes drain, switch-signal take) with
sk_vm_owns_outermost_frame(vm): the VM physically owns the stack
(sk_vm_switch_current_vm() == vm), is at its own outermost vm_interpret()
frame (new per-VM VM.interpret_depth), and is not compiling. Once VMs
interleave on their own native stacks, a global depth count across all VMs
no longer means "this VM owns this stack"; this is the invariant it was
approximating. The global and the old function stay until Stage I.

Acceptance, all three ISAs, foreground with the framebuffer visible, coupled
disk images reset before each: zero UNKNOWN WORD, zero self-test failures,
dict_hash identical across ISAs and unchanged from the pre-Stage-A boot, and
the live WIREBIND mint -> attach -> USE -> 1 2 + . -> 3 relay with
stadium_conserved(self)=true. Logs: 20260923-140120 (amd64), -140654
(aarch64), -141303 (riscv64).

FABRIC-3.8.md also records Captain Bob's ruling that preemption comes from
the compudynamics, not the timer: principle 5 is corrected and the design
question is opened as SS3.11, to be ruled before Stage C.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-23 14:18:55 -04:00
co-authored by Claude Opus 5.5
parent 0532c1d8b8
commit 2aa338483a
12 changed files with 27609 additions and 36968 deletions
+34 -4
View File
@@ -70,6 +70,7 @@
* than g_vm_interpret_depth's definition (kept next to g_log_attrib_vm,
* near vm_interpret() -- see that doc comment for the full rationale). */
int sk_vm_at_outermost_interpret(void);
int sk_vm_owns_outermost_frame(VM *vm); /* FABRIC-3.8.md §3.1 -- defined near vm_interpret() */
#endif
#include "word_source/include/vocabulary_words.h"
#include "vm_internal.h"
@@ -919,8 +920,10 @@ void execute_colon_word(VM* vm)
* 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()) {
* silently never run on any checkpoint that switches.
* FABRIC-3.8.md §3.1 (Stage A): gated on per-VM stack ownership,
* not the global depth count. */
if (sk_vm_owns_outermost_frame(vm)) {
(void)sk_hermes_drain_checkpoint(vm);
}
@@ -956,8 +959,14 @@ void execute_colon_word(VM* vm)
* switch_ticks_since to the wrong (resume) event instead of the
* actual switch-out this counts. It also resets target_id's own
* eligibility state directly (2026-09-14 correction) -- see its
* own doc comment for why that can't be left to ISR polling. */
if (sk_vm_at_outermost_interpret()) {
* own doc comment for why that can't be left to ISR polling.
*
* FABRIC-3.8.md §3.1 (Stage A, 2026-09-23): gated on
* sk_vm_owns_outermost_frame(vm) -- `vm` physically owns this
* stack AND is at its own outermost frame AND is not compiling --
* instead of the global depth count, which stops being meaningful
* once VMs genuinely interleave on their own native stacks. */
if (sk_vm_owns_outermost_frame(vm)) {
VMUuid target_id = sk_vm_switch_signal_take_pending();
if (!vm_uuid_equal(target_id, vm_uuid_none())) {
VMRegistryEntry target_entry;
@@ -1177,6 +1186,25 @@ static int g_vm_interpret_depth;
int sk_vm_at_outermost_interpret(void) { return g_vm_interpret_depth <= 1; }
/* FABRIC-3.8.md §3.1 (Stage A, 2026-09-23): the real ownership invariant
* the global depth test above was approximating. A checkpoint may drain or
* switch only when `vm` physically owns the stack it is standing on
* (sk_vm_switch_current_vm() -- switch.c tracks the physical owner on every
* switch, seeded for Hera at boot), is at its OWN outermost vm_interpret()
* frame (per-VM depth, not a count across every VM), and is not mid-
* definition (a payload or a switch must never land inside a `:` ... `;`
* body -- FABRIC-3.8.md §3.10). A nested VM-EXEC/VM-CALL into another VM
* fails the first clause by construction (that VM does not own the stack),
* so the existing defer-if-nested behavior (§XXVIII.1) is preserved. Before
* the switch mechanism is seeded at boot (current VM == NULL), nothing
* passes -- matching "no switch has happened yet." */
int sk_vm_owns_outermost_frame(VM *vm) {
return vm &&
sk_vm_switch_current_vm() == vm &&
vm->interpret_depth == 1 &&
vm->mode != MODE_COMPILE;
}
void vm_interpret(VM* vm, const char* input)
{
if (!vm || !input) return;
@@ -1185,6 +1213,7 @@ void vm_interpret(VM* vm, const char* input)
VM *prev_attrib_vm = g_log_attrib_vm;
g_log_attrib_vm = vm;
g_vm_interpret_depth++;
vm->interpret_depth++; /* FABRIC-3.8.md §3.1: per-VM, see sk_vm_owns_outermost_frame() */
/* Load into input buffer (cap + NUL) */
size_t n = 0, cap = INPUT_BUFFER_SIZE ? INPUT_BUFFER_SIZE - 1 : 0;
@@ -1218,6 +1247,7 @@ void vm_interpret(VM* vm, const char* input)
}
g_vm_interpret_depth--;
vm->interpret_depth--;
g_log_attrib_vm = prev_attrib_vm;
}