Files
LithosAnanake/include/starkernel/vm/kernel_hermes.h
T
Robert Allan JamesandClaude Sonnet 5 9d129fdb1c Add sk_hermes_alloc(), the heat-coupled allocator -- FABRIC-3.6.md task 2.2, item 28
The piece FABRIC-3.5.md SXXXIII.6 calls "what remains genuinely hard,"
built and proven first per its own recommendation. Added
src/starkernel/vm/kernel_hermes.c (wired into Makefile.starkernel's
LOADER_EXTRA_SRCS -- this repo lists vm/*.c files explicitly, no glob)
and sk_hermes_alloc()'s declaration in kernel_hermes.h.

Checks stadium_reservoir_peek(vm_id) >= SK_HERMES_Q_SLOT before
touching the reservoir at all -- refusal this way needs no rollback,
since nothing was pulled -- with an explicit rollback path
(stadium_reservoir_push) kept defensively for the pull-then-short case,
though nothing in this single-core kernel is expected to reach it.

SK_HERMES_Q_SLOT = Q48_ONE / SK_HERMES_MSG_MAX (2048), deliberately
simpler than messaging.4th's own formula, which reserves a Q.1/3 floor
for COMMON-CH's own Stadium heat -- kernel-Hermes has no such object
(SXXXIII.4/SXXXIII.5's flat membership list carries no heat of its
own), so there is nothing left for that floor to protect.

Self-test in kernel_main.c, same diagnostic-only synthetic-VM pattern
as the existing Stadium quota grant self-test (lo=3, distinct from
that test's lo=1): reads back the actual granted reservoir rather than
assuming a number, derives expected_n from it, allocates to refusal,
and checks the refusal lands at exactly expected_n, the reservoir
doesn't move on the refused attempt (rollback proven, not assumed),
and the final reservoir is exactly reservoir0 minus got_n times
Q_SLOT.

Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, dict_hash unmoved from task 2.1 (pure C, no FORTH
touched). All three print identical self-test arithmetic: reservoir0=
65536 Q_SLOT=2048 expected_n=32 got_n=32 reservoir_after=0. No compiler
warnings.

Noted, not fixed: Q_SLOT's divisor and SK_HERMES_MSG_MAX are the same
32, so reservoir and arena exhaustion land at exactly the same count by
construction -- this test can't distinguish which refusal reason
fired, only that refusal is correct and rolls back correctly.

Deliberately not evidence for stadium_conserved(): allocating alone
(no release yet, task 2.3) leaves pulled heat held off the Stadium
floor, so the two-term check would correctly read false right now if
run mid-hold. That's expected, not a bug -- Stage B (task 2.7) is
defined as "before and after the alloc/free cycle," not "continuously
during." This task's self-test checks reservoir arithmetic directly
instead.

Authorized by Captain Bob ("Yes continue").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 21:00:20 -04:00

177 lines
7.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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.h - Kernel-resident Hermes: message and membership
* structures (FABRIC-3.6.md task 2.1, item 28; design: FABRIC-3.5.md
* SIII/SXXXIII/SXXXIV).
*
* Phase 2, task 2.1 ONLY: type definitions, wired to nothing, drawing no
* heat. No allocator, no send/deliver/reap logic, no registration
* anywhere -- those are tasks 2.2 onward, each its own commit. This file
* existing and compiling changes no VM's dictionary and no runtime
* behaviour; that is deliberate (FABRIC-3.5.md SXXII.4: Phase 2 structures
* come first and prove nothing until the allocator is built on top, SXL.4
* item 41).
*
* SkHermesMessage mirrors capsules/common/messaging.4th's live MSG-CELLS
* layout (9 cells: MSG-TYPE@/FROM@/TO@/PADDR@/PLEN@/STADIUM-CELL@/SEQ@/
* CH@/ORIG-TYPE@) field-for-field, per FABRIC-3.5.md SXXXIII.4 item 1 --
* "roughly half the file is accessors that become struct fields." The
* Stadium-cell field is the heat coupling itself: a message's heat is not
* a field of its own, it IS the Stadium cell it occupies (SXL.4's
* consumption model, SXXXIX.4's per-VM invariant) -- there is deliberately
* no separate heat field here to keep that single-source-of-truth.
*
* SkHermesMembership is the "one broadcast membership list" SXXXIII.4
* item 3 and SXXXIII.5 recommend in place of messaging.4th's 28-word
* channel abstraction (CH-REQUEST/ACCEPT/CONFIRM/CLOSE/MINT-ID and the
* CH-NEGOTIATING/OPEN/CLOSING state machine) -- traced to have exactly one
* live caller, CH-ADD-MBR, everything else channel-shaped is unexercised.
* Whether kernel-Hermes ever adds negotiation on top is item 27, an open
* Phase 3 ruling (FABRIC-3.6.md B1) -- this structure does not answer
* that question, it only holds a flat list, which is correct either way.
*/
#ifndef STARKERNEL_VM_KERNEL_HERMES_H
#define STARKERNEL_VM_KERNEL_HERMES_H
#ifdef __STARKERNEL__
#include <stddef.h>
#include <stdint.h>
#include "starkernel/vm_uuid.h" /* VMUuid */
#include "starkernel/q48_16.h" /* Q48_ONE */
/*
* SK_HERMES_MSG_MAX / SK_HERMES_MEMBER_MAX - sizing. Mirrors
* messaging.4th's own MSG-MAX (32) and MBR-MAX (64) as a starting point --
* kernel-Hermes is a single central pool rather than N per-VM arenas, so
* these may need revisiting once real traffic exists to size against. Not
* a ruling, just where the FORTH precedent already was.
*/
#define SK_HERMES_MSG_MAX 32
#define SK_HERMES_MEMBER_MAX 64
/*
* SK_HERMES_Q_SLOT - per-message admission heat, task 2.2 (item 28).
* messaging.4th:44-46 derives Q.SLOT as the reservoir remaining after
* COMMON-CH's own Q.1/3 floor, split across MSG-MAX + (CH-MAX-1) slots --
* a floor that exists to reserve heat for the one live channel object
* itself. Kernel-Hermes has no such object: SXXXIII.4 item 3 and
* SXXXIII.5 replace the channel abstraction with a flat membership list
* that carries no Stadium heat of its own, so there is nothing left for a
* floor to protect. Deliberately simpler here rather than carrying the
* old formula's now-unmotivated term forward: reservoir split evenly
* across message slots only.
*/
#define SK_HERMES_Q_SLOT ((uint64_t)Q48_ONE / SK_HERMES_MSG_MAX)
/*
* SkHermesMessage - one message slot, field-for-field mirror of
* messaging.4th's 9-cell MSG layout.
*
* @field type Message type code (SPAWN/PAUSE/RESUME/KILL-style
* codes are Category A/dead per task 0.1/0.4; live
* types today are CONSOLE-CMD-EVENT(7),
* ELEVATE-REQUEST(8), BLK-ATTACH-EVENT(9), messaging.4th
* reserved sentinels MSG-NACKED(253)/MSG-DELIVERED(255)).
* @field from Sending VM (mirrors MSG-FROM@).
* @field to Target VM (mirrors MSG-TO@).
* @field payload_addr Out-of-line payload address (mirrors MSG-PADDR@).
* FABRIC-3.5.md SXLIII.6.1 (item 44, still open): a
* payload above INPUT_BUFFER_SIZE-1 (1024) bytes cannot
* be drained in one interpret call -- bound or chunk it
* before Phase 3, not here.
* @field payload_len Payload length in bytes (mirrors MSG-PLEN@).
* @field stadium_cell Index into the Stadium cell array this message's
* heat currently occupies, or a sentinel meaning "none"
* (mirrors MSG-STADIUM-CELL@) -- the heat coupling
* itself; see this file's own top comment.
* @field seq Monotonic send sequence (mirrors MSG-SEQ@).
* @field channel Broadcast/channel marker; unused today (mirrors
* MSG-CH@) -- item 27 territory, not decided here.
* @field orig_type Original type before a NACK/redeliver rewrite
* (mirrors MSG-ORIG-TYPE@).
* @field in_use Free-list occupancy flag for task 2.2's allocator.
* Not present in the FORTH layout (which uses
* MSG-TYPE@ 0<> as its own live/free test) -- kept
* explicit here rather than overloading `type == 0`,
* since kernel-Hermes's held/pulled/returned/consumed
* ledger (task 2.4, SXL.4) needs an unambiguous
* occupancy bit independent of the type field's value.
*/
typedef struct {
uint32_t type;
VMUuid from;
VMUuid to;
void *payload_addr;
uint32_t payload_len;
int32_t stadium_cell;
uint32_t seq;
uint32_t channel;
uint32_t orig_type;
int in_use;
} SkHermesMessage;
/*
* SkHermesMembership - one flat broadcast membership list: every VM that
* has joined, no per-member state beyond identity. Replaces the 28-word
* channel abstraction; see this file's own top comment for why.
*
* @field members Member VM identities, valid for indices < count.
* @field count Number of valid entries in members[].
*/
typedef struct {
VMUuid members[SK_HERMES_MEMBER_MAX];
size_t count;
} SkHermesMembership;
/*
* sk_hermes_alloc - Heat-coupled allocate (task 2.2, item 28): pull
* SK_HERMES_Q_SLOT from vm_id's own Stadium reservoir and claim a free
* message slot. Refuses cleanly, touching neither the reservoir nor the
* arena, if either is unavailable -- "roll back on refusal" is satisfied
* by never pulling until affordability is confirmed, not by pulling then
* undoing (though the rollback path exists too, for the pull-then-fail
* case the check-first ordering is not expected to reach).
*
* Wired to nothing outside this file's own self-test (kernel_main.c) as
* of task 2.2 -- no FORTH word, no capsule interaction, no protocol logic
* (send/deliver/reap are later tasks). This function existing and being
* exercised by a synthetic-VM self-test does not change any real VM's
* dictionary or Stadium state.
*
* @param vm_id Caller whose reservoir is charged.
* @param out_msg On success, set to the claimed slot. Untouched on
* refusal.
* @return 0 on success, -1 on refusal (insufficient reservoir or no free
* slot -- task 2.2 does not distinguish the two in the return
* value; both leave all state exactly as it was).
*/
int sk_hermes_alloc(VMUuid vm_id, SkHermesMessage **out_msg);
#endif /* __STARKERNEL__ */
#endif /* STARKERNEL_VM_KERNEL_HERMES_H */