Real finding, caught before building release on a foundation that
couldn't support it: task 2.2's first cut of sk_hermes_alloc() pulled
reservoir heat but never admitted a real Stadium-floor patron -- just a
local in_use flag. FABRIC-3.5.md SXXXIII.4 item 1 says MSG-FREE-NODE
returns heat "via STADIUM-EVICT", which only means something if
allocation admitted something. SXL.4's own invariant, Sigma(resident
patron heat) + reservoir + consumed == Q48_ONE, cannot balance if held
heat is invisible to every term while held. Flagged to Captain Bob
before proceeding; authorized to correct 2.2 in the same pass as
building 2.3 on top of the fix.
sk_hermes_alloc() now calls stadium_admit() with heat = the pulled
amount, behaviour = STADIUM_BEHAVIOUR_DELIVER (matching messaging.4th's
own SB-DELIVER STADIUM-ADMIT exactly), and identity = the message's own
slot index (matching the FORTH precedent -- caught live in the first
boot of this fix that omitting this made stadium_dispatch()'s existing
DELIVER diagnostic print msg_idx=0 for every message instead of a
distinct value). The returned cell index is stored in the message's
own stadium_cell field. Stadium-floor refusal (independent of reservoir
affordability) rolls back the pull the same way the other refusal
paths already do.
sk_hermes_release() -- the function task 2.3 actually asks for -- calls
stadium_evict() on that cell, which itself returns the departing
patron's remaining heat to its owning VM's reservoir, matching
MSG-FREE-NODE's exact shape. Release does not touch the reservoir
directly.
Self-test (kernel_main.c) extended: keeps every allocated message's
pointer, allocates to exhaustion as before, releases all of them, and
checks the reservoir returns to precisely its starting value.
"Undecayed" is true by construction (no decay/TTL logic exists yet,
task 2.5) -- exact restoration, not approximate.
Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, dict_hash unmoved from task 2.2. All three print
identical PASS arithmetic: reservoir0=65536, reservoir_after_alloc=0,
reservoir_final=65536. No compiler warnings.
stadium_dispatch()'s DELIVER-case console output (one line per
eviction) is pre-existing instrumentation, not new -- confirmed real
and load-bearing per stadium.c's own comment, verbose but expected.
Authorized by Captain Bob ("yes").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
137 lines
5.2 KiB
C
137 lines
5.2 KiB
C
/*
|
||
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.
|
||
|
||
*/
|
||
|
||
/**
|
||
* kernel_hermes.c - Kernel-resident Hermes: the heat-coupled allocator
|
||
* and its release path (FABRIC-3.6.md tasks 2.2/2.3, item 28).
|
||
*
|
||
* CORRECTION (task 2.3, found while starting it): task 2.2's first cut of
|
||
* sk_hermes_alloc() pulled reservoir heat but never admitted a real
|
||
* Stadium-floor patron -- it just set a local `in_use` flag. That is
|
||
* wrong: FABRIC-3.5.md SXXXIII.4 item 1 states plainly that
|
||
* "MSG-FREE-NODE returns it via STADIUM-EVICT", which only has something
|
||
* to evict if allocation admitted something. More importantly, SXL.4's
|
||
* invariant is Sigma(resident patron heat) + reservoir + consumed ==
|
||
* Q48_ONE -- if held message heat is not a resident patron, it is
|
||
* invisible to every term of that equation while held, which cannot be
|
||
* right. Corrected here: sk_hermes_alloc() now calls stadium_admit()
|
||
* with the pulled heat, storing the returned cell index in the message's
|
||
* own stadium_cell field for release to evict later. Behaviour
|
||
* DELIVER (1), matching messaging.4th's own `SB-DELIVER STADIUM-ADMIT`
|
||
* at its MSG-ALLOC site.
|
||
*/
|
||
|
||
#ifdef __STARKERNEL__
|
||
|
||
#include <string.h>
|
||
#include "starkernel/vm/kernel_hermes.h"
|
||
#include "starkernel/vm/stadium.h"
|
||
|
||
static SkHermesMessage sk_hermes_msgs[SK_HERMES_MSG_MAX];
|
||
|
||
static int sk_hermes_find_free_slot(void) {
|
||
int i;
|
||
for (i = 0; i < SK_HERMES_MSG_MAX; i++) {
|
||
if (!sk_hermes_msgs[i].in_use) return i;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
int sk_hermes_alloc(VMUuid vm_id, SkHermesMessage **out_msg) {
|
||
int slot;
|
||
uint64_t pulled;
|
||
StadiumPatronHeader candidate;
|
||
size_t cell;
|
||
|
||
if (!out_msg) return -1;
|
||
|
||
/* Check affordability before touching the reservoir at all -- this is
|
||
* what "roll back on refusal" reduces to when the check happens
|
||
* first: there is nothing to roll back. */
|
||
if (stadium_reservoir_peek(vm_id) < SK_HERMES_Q_SLOT) return -1;
|
||
|
||
slot = sk_hermes_find_free_slot();
|
||
if (slot < 0) return -1; /* arena full, reservoir untouched */
|
||
|
||
pulled = stadium_reservoir_pull(vm_id, SK_HERMES_Q_SLOT);
|
||
if (pulled < SK_HERMES_Q_SLOT) {
|
||
/* Should not happen given the peek check above (nothing else runs
|
||
* between the two calls on this single-core, cooperative kernel),
|
||
* but roll back explicitly rather than trust that invariant
|
||
* silently. */
|
||
if (pulled > 0) stadium_reservoir_push(vm_id, pulled);
|
||
return -1;
|
||
}
|
||
|
||
memset(&candidate, 0, sizeof(candidate));
|
||
/* Matches messaging.4th's own MSG-ALLOC: the slot's own index becomes
|
||
* the admitted patron's identity, which stadium_dispatch()'s DELIVER
|
||
* case later prints back as "msg_idx=" on release -- an arbitrary
|
||
* distinct value would satisfy the conservation math just as well,
|
||
* but this keeps the diagnostic meaningful instead of every message
|
||
* printing identity 0. */
|
||
candidate.identity = (uint64_t)slot;
|
||
candidate.heat = pulled; /* the reservoir pull itself, unmodified */
|
||
candidate.ttl = 0; /* decay/TTL is task 2.5's scope, not this one's */
|
||
candidate.link = 0;
|
||
candidate.contains = STADIUM_CONTAINS_NONE;
|
||
candidate.mass = 1; /* message body lives in sk_hermes_msgs[], not here */
|
||
candidate.flags = 0;
|
||
candidate.behaviour = (uint8_t)STADIUM_BEHAVIOUR_DELIVER;
|
||
|
||
cell = stadium_admit(vm_id, &candidate);
|
||
if (cell == STADIUM_CELL_NONE) {
|
||
/* Stadium floor itself refused (unrelated to heat affordability,
|
||
* already confirmed above) -- roll back the pull, same "leave
|
||
* everything exactly as found" discipline as every other refusal
|
||
* path here. */
|
||
stadium_reservoir_push(vm_id, pulled);
|
||
return -1;
|
||
}
|
||
|
||
memset(&sk_hermes_msgs[slot], 0, sizeof(SkHermesMessage));
|
||
sk_hermes_msgs[slot].in_use = 1;
|
||
sk_hermes_msgs[slot].stadium_cell = (int32_t)cell;
|
||
*out_msg = &sk_hermes_msgs[slot];
|
||
return 0;
|
||
}
|
||
|
||
int sk_hermes_release(SkHermesMessage *msg) {
|
||
int rc;
|
||
|
||
if (!msg || !msg->in_use) return -1;
|
||
|
||
/* stadium_evict() returns the departing patron's remaining heat to
|
||
* its owner's reservoir itself (stadium.c: "the departing patron's
|
||
* remaining heat must flow back to its owner's reservoir before the
|
||
* cell returns to the free list") -- release does not touch the
|
||
* reservoir directly, the eviction path does it, matching
|
||
* MSG-FREE-NODE's own shape ("DUP 5 CELLS + @ STADIUM-EVICT DROP"). */
|
||
rc = (msg->stadium_cell >= 0) ? stadium_evict((size_t)msg->stadium_cell) : -1;
|
||
|
||
memset(msg, 0, sizeof(*msg));
|
||
return rc;
|
||
}
|
||
|
||
#endif /* __STARKERNEL__ */
|