/* 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 #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__ */