Punch list §25 item 3.6 complete. Phase 3 (§25.4) core is now done: items 3.1-3.6 all closed. stadium_evict() now panics via sk_hal_panic() if a resident cell 0 (Hera, patron zero by construction of §6's boot order) is ever selected for eviction. Placement is deliberate: the check runs before the pin/contains refusal checks, not after -- if it ran after, a wrongly-cleared pin would let the ordinary refusal path quietly return -1 instead of ever reaching the panic, defeating the point of a check that's supposed to be independent of pin holding. Per §20.5 #3's explicit wording, not implemented as a filter: stadium_admit()'s least-dense search is unchanged, still relying on the general pin skip from item 3.5. Adding a second filter there would have done exactly what that section warns against ("filtering hides the bug, asserting reports it"). The panic path is, and will remain, unexercised by the acceptance mechanism: sk_hal_panic() halts the machine, and triggering it deliberately is incompatible with the three-arch boot being this project's sole acceptance test. Correctness rests on the placement argument, not a test -- same honesty precedent as items 3.4 and 3.5's other unexercised paths. Verified: three-architecture boot (amd64, aarch64, riscv64), all reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the item-3.5 baseline. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
295 lines
14 KiB
C
295 lines
14 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.
|
||
|
||
*/
|
||
|
||
/**
|
||
* stadium.h - The Stadium cell and header (FABRIC.md §3, punch list item 3.1)
|
||
*
|
||
* A cell is one of exactly two things: a patron header, or a continuation
|
||
* cell owned by exactly one patron. The union is closed, two-valued, and
|
||
* fixed at build time -- not a type field. See FABRIC.md §3.
|
||
*/
|
||
|
||
#ifndef STARKERNEL_VM_STADIUM_H
|
||
#define STARKERNEL_VM_STADIUM_H
|
||
|
||
#ifdef __STARKERNEL__
|
||
|
||
#include <stddef.h>
|
||
#include <stdint.h>
|
||
#include "starforth_config.h" /* STADIUM_CONTAINS_DEPTH_MAX, STADIUM_CAPACITY_TICK, STADIUM_MEMORY_PERCENT */
|
||
|
||
#define STADIUM_CELL_BYTES 64
|
||
|
||
/* Sentinel for `contains` meaning "holds no patron." Not 0 -- cell index 0 is
|
||
* a valid index (Hera, item 3.6), so 0 cannot double as "none" without
|
||
* conflating "contains Hera" with "contains nothing." */
|
||
#define STADIUM_CONTAINS_NONE ((uint32_t)-1)
|
||
|
||
/*
|
||
* StadiumPatronHeader - one member of the closed two-valued cell union
|
||
* (FABRIC.md §3). Nine wires: identity, heat, TTL, pin (a bit in `flags`),
|
||
* link, code field (`behaviour`), mass, payload, contains. `flags` bit 0 is
|
||
* `pin`; the remaining bits are reserved. `behaviour` is the closed code-field
|
||
* enumeration (§18.3) -- not yet defined, item 3.3's scope.
|
||
*
|
||
* Field order is largest-to-smallest so natural C99 alignment adds zero
|
||
* padding: every offset below is already a multiple of that field's own
|
||
* alignment, and the struct's total size (64) is a multiple of its max
|
||
* alignment (8), so no compiler inserts trailing padding either. Do not
|
||
* reorder without re-checking this holds on all three ISAs.
|
||
*/
|
||
typedef struct {
|
||
uint64_t identity; /* offset 0 -- handle or name, never a content hash while resident (§24.4) */
|
||
uint64_t heat; /* offset 8 -- Q48.16, conserved share of 1.0 (§19.1) */
|
||
uint32_t ttl; /* offset 16 -- remaining lifetime; messages and ACLs only (§17.1) */
|
||
uint32_t link; /* offset 20 -- index into the Stadium, not a pointer */
|
||
uint32_t contains; /* offset 24 -- index of the patron held inside this one, or
|
||
* STADIUM_CONTAINS_NONE (item 1.1). Cell index 0 is a valid
|
||
* index (Hera, item 3.6) so 0 cannot mean "none" -- item 3.5
|
||
* caught this and picked UINT32_MAX instead. Chains up to
|
||
* STADIUM_CONTAINS_DEPTH_MAX deep; reap-gating enforcement of
|
||
* that bound is item 3.5's scope. */
|
||
uint16_t mass; /* offset 28 -- cells this patron occupies (§19.2) */
|
||
uint8_t flags; /* offset 30 -- bit 0 = pin; remaining bits reserved */
|
||
uint8_t behaviour; /* offset 31 -- code field. Valid values are StadiumBehaviour (§18.3)
|
||
* tags cast to uint8_t -- kept as uint8_t rather than the enum type
|
||
* itself since C does not guarantee an enum's underlying type, and
|
||
* this field's offset is load-bearing for the 64-byte layout item
|
||
* 3.1 validated. */
|
||
uint8_t payload[32]; /* offset 32 -- inline payload, used when mass == 1 */
|
||
} StadiumPatronHeader;
|
||
|
||
/*
|
||
* StadiumContinuationCell - the other member of the union. Owned by exactly
|
||
* one patron header, chained by `next`. Never ranked, never reaped, never
|
||
* dispatched (§3) -- pure floor space, accounted for in its owner's mass.
|
||
*/
|
||
typedef struct {
|
||
uint32_t next; /* offset 0 -- index of the next continuation cell, or none */
|
||
uint8_t payload[60]; /* offset 4 */
|
||
} StadiumContinuationCell;
|
||
|
||
/*
|
||
* StadiumCell - the closed two-valued union itself (§3). Which member is
|
||
* valid for a given array slot is NOT stored in the cell -- FABRIC.md's item
|
||
* 3.1 amendment to §3 rules this an external side bitmap, one bit per cell,
|
||
* kept outside the cell array. Declared here as the indexing contract this
|
||
* type expects; item 3.2 (boot-time allocation) allocates the bitmap itself.
|
||
*/
|
||
typedef union {
|
||
StadiumPatronHeader header;
|
||
StadiumContinuationCell continuation;
|
||
} StadiumCell;
|
||
|
||
/* C99-portable compile-time size assertions (no _Static_assert -- that's C11). */
|
||
typedef char stadium_header_size_check[(sizeof(StadiumPatronHeader) == STADIUM_CELL_BYTES) ? 1 : -1];
|
||
typedef char stadium_continuation_size_check[(sizeof(StadiumContinuationCell) == STADIUM_CELL_BYTES) ? 1 : -1];
|
||
typedef char stadium_cell_size_check[(sizeof(StadiumCell) == STADIUM_CELL_BYTES) ? 1 : -1];
|
||
|
||
/*
|
||
* Items 1.1 and 1.4 named this item as where their Kconfig symbols would be
|
||
* implemented. Neither has a consumer yet (item 3.5 for the depth cap,
|
||
* capacity arbitration -- not yet on the punch list -- for the tick); these
|
||
* checks only prove the symbols are defined and sane, the same discipline
|
||
* already applied to the byte-count checks above.
|
||
*/
|
||
typedef char stadium_contains_depth_configured_check[(STADIUM_CONTAINS_DEPTH_MAX > 0) ? 1 : -1];
|
||
typedef char stadium_capacity_tick_configured_check[(STADIUM_CAPACITY_TICK > 0) ? 1 : -1];
|
||
|
||
/*
|
||
* stadium_boot_init - Boot-time allocation (FABRIC.md item 3.2, §17.6 position
|
||
* (b)). Sizes the global cell array from the memory budget actually observed
|
||
* at boot -- STADIUM_MEMORY_PERCENT of pmm_get_stats().free_bytes at the
|
||
* point of the call, rounded down to whole STADIUM_CELL_BYTES cells -- rather
|
||
* than a hardcoded count. Also allocates the header/continuation discriminator
|
||
* bitmap item 3.1 declared but did not allocate: one bit per cell, bit set
|
||
* means the cell at that index is a patron header, clear means continuation
|
||
* or not yet in use. Both are kmalloc'd (freestanding kernel, no separate
|
||
* PMM-backed region needed for this) and explicitly zero-filled, since
|
||
* kmalloc does not zero.
|
||
*
|
||
* Must be called after M6 (kmalloc_init) and before any VM is born (§6). Does
|
||
* not halt boot on failure -- nothing downstream consumes the Stadium yet.
|
||
*
|
||
* @return 0 on success, -1 if kmalloc failed for either allocation.
|
||
*/
|
||
int stadium_boot_init(void);
|
||
|
||
/* stadium_is_initialized - Whether stadium_boot_init() has succeeded. */
|
||
int stadium_is_initialized(void);
|
||
|
||
/* stadium_cell_count - Number of cells in the array, 0 if not initialized. */
|
||
size_t stadium_cell_count(void);
|
||
|
||
/* stadium_cells - Pointer to the cell array, NULL if not initialized. */
|
||
StadiumCell *stadium_cells(void);
|
||
|
||
/*
|
||
* stadium_header_bitmap - Pointer to the discriminator bitmap declared in
|
||
* item 3.1, NULL if not initialized. ceil(stadium_cell_count() / 8) bytes.
|
||
*/
|
||
uint8_t *stadium_header_bitmap(void);
|
||
|
||
/*
|
||
* StadiumBehaviour - the closed code-field enumeration (FABRIC.md §13, §18.3).
|
||
* The engine dispatches on this tag and never asks a patron what kind it is
|
||
* -- §3's entire point. Two patrons may share a tag: a VM's tag is COOL, the
|
||
* same tag a word carries (§18.3). Mapped from §17.1's patron table:
|
||
*
|
||
* MIGRATE -- blocks: reap event is migration back to Artemis (§17.2)
|
||
* DELIVER -- messages: reap event is delivery
|
||
* EXPIRE -- ACLs: reap event is TTL expiry
|
||
* COOL -- words and VMs: reap event is cooling off the floor
|
||
*
|
||
* Closed and fixed at build time -- see stadium_dispatch()'s exhaustive
|
||
* switch for how the compiler enforces that.
|
||
*/
|
||
typedef enum {
|
||
STADIUM_BEHAVIOUR_MIGRATE = 0,
|
||
STADIUM_BEHAVIOUR_DELIVER,
|
||
STADIUM_BEHAVIOUR_EXPIRE,
|
||
STADIUM_BEHAVIOUR_COOL
|
||
} StadiumBehaviour;
|
||
|
||
/*
|
||
* stadium_dispatch - Calls the behaviour handler for a patron's code field.
|
||
* The engine calls this at reap and never asks what kind of patron departed
|
||
* (§3, §18.3) -- only cell_index and behaviour cross this boundary.
|
||
*
|
||
* Handlers are stubs today: the real migrate-to-Artemis / deliver / expire /
|
||
* cool actions belong to their own subsystems, which have not been migrated
|
||
* onto the Stadium yet (Phase 4, §25.5). Nothing calls stadium_dispatch()
|
||
* yet either -- that begins with item 3.5 (admission and eviction).
|
||
*
|
||
* @param cell_index Index into the Stadium of the patron header dispatching.
|
||
* @param behaviour Which of the closed tag set to invoke.
|
||
*/
|
||
void stadium_dispatch(size_t cell_index, StadiumBehaviour behaviour);
|
||
|
||
/*
|
||
* stadium_density - Heat / mass for the patron header at cell_index (FABRIC.md
|
||
* §19.2, §19.3). Read, not computed by a scheduler: both operands already
|
||
* live in the header, so this is a division on demand, not maintained
|
||
* bookkeeping. Result stays valid Q48.16, since heat is already Q48.16 and
|
||
* mass is a plain integer divisor.
|
||
*
|
||
* Returns 0 if mass is 0 -- an empty or never-admitted slot (everything is
|
||
* zero-initialized by stadium_boot_init() until something is actually born
|
||
* into the Stadium, which nothing yet does) has no footprint to be dense
|
||
* within, rather than a division by zero.
|
||
*
|
||
* Does not validate that cell_index actually holds a header rather than a
|
||
* continuation cell or an out-of-range index -- callers are expected to
|
||
* consult the item-3.1 discriminator bitmap first. Ranking (finding the
|
||
* densest or least-dense resident) is item 3.5's scope, not this one's;
|
||
* this function only supplies the per-cell value that comparison reads.
|
||
*
|
||
* @param cell_index Index into the Stadium of the patron header to measure.
|
||
* @return Density in Q48.16, or 0 if the header's mass is 0.
|
||
*/
|
||
uint64_t stadium_density(size_t cell_index);
|
||
|
||
/* Sentinel returned by stadium_admit() on refusal -- no cell index is this large. */
|
||
#define STADIUM_CELL_NONE ((size_t)-1)
|
||
|
||
/*
|
||
* Hera is patron zero by construction of §6's boot order: she is the first
|
||
* entry admitted into the Stadium. This is a positional invariant, not a
|
||
* runtime check of who currently occupies cell 0 -- nothing yet births
|
||
* anything, Hera included, so today this index is never actually occupied.
|
||
* Used only by stadium_evict()'s item-3.6 assertion below.
|
||
*/
|
||
#define STADIUM_HERA_CELL_INDEX ((size_t)0)
|
||
|
||
/*
|
||
* stadium_evict - Reap the patron header at cell_index (FABRIC.md §17.2:
|
||
* "reap means leaves the floor, not destroyed"). Dispatches its behaviour
|
||
* (§18.3), clears its item-3.1 discriminator bit, zeroes its header.
|
||
*
|
||
* PANICS (does not return) if cell_index == STADIUM_HERA_CELL_INDEX and the
|
||
* cell is actually resident -- FABRIC.md §20.5 #3: Hera is pinned (§3), but
|
||
* pinning alone is a silent guarantee, and item 3.6 requires a hard
|
||
* assertion at the eviction site rather than relying on pin holding. This
|
||
* check runs BEFORE the pin/contains checks below, deliberately: if pin were
|
||
* ever wrongly cleared, the ordinary pin-refusal path would quietly return
|
||
* -1 instead of surfacing the break, defeating the point of a second,
|
||
* independent check. Selecting patron zero for eviction means the invariant
|
||
* is already broken; continuing would run the system without a governor.
|
||
*
|
||
* Refuses (returns -1, does not panic) if the header is pinned (`flags` bit
|
||
* 0, §3's invariance wire) or has a non-none `contains` (item 1.1: a patron
|
||
* holding another cannot be reaped, full stop). Also refuses for an
|
||
* out-of-range index or a cell whose discriminator bit is not set (nothing
|
||
* resident there to reap).
|
||
*
|
||
* @param cell_index Index of the patron header to reap.
|
||
* @return 0 on success, -1 if refused.
|
||
*/
|
||
int stadium_evict(size_t cell_index);
|
||
|
||
/*
|
||
* stadium_admit - Place a candidate patron header into the Stadium (FABRIC.md
|
||
* §19.3).
|
||
*
|
||
* First scans for an unused cell (discriminator bit clear and mass == 0) and
|
||
* places the candidate there directly -- §19.3's density comparison only
|
||
* governs the full case, not this one. If none is free, finds the
|
||
* least-dense resident (discriminator bit set, mass > 0, not pinned, not
|
||
* `contains`-gated -- pinned and gated residents are never eviction
|
||
* candidates, per §3 and item 1.1) and evicts it via stadium_evict() only if
|
||
* the candidate is strictly denser (§19.3: "denser than," not "at least as
|
||
* dense as"). Otherwise refuses.
|
||
*
|
||
* Does not itself assert anything about which resident this turns out to be
|
||
* -- the item-3.6 rule that patron zero (Hera) must never actually be
|
||
* selected is a separate, later check at the eviction site.
|
||
*
|
||
* REFUSES any candidate with mass != 1. A multi-cell patron (mass > 1, e.g.
|
||
* §23.3's 1024-byte block at mass 19) needs its continuation chain allocated
|
||
* too, which needs the per-VM free lists §22.3 describes -- item 3.2's DONE
|
||
* note already deferred those (not this item's scope, they are granted when
|
||
* Hera assigns a VM its quota). Admitting only the header and leaking the
|
||
* rest would break capacity conservation, so this refuses rather than doing
|
||
* that. Revisit when the free lists exist.
|
||
*
|
||
* REQUIRES candidate->contains to be either STADIUM_CONTAINS_NONE or a valid
|
||
* index (< the current cell count) -- refuses otherwise. This does NOT catch
|
||
* a zero-initialized candidate that was meant to contain nothing: 0 is a
|
||
* valid index (Hera), so a caller that forgets to set `contains` explicitly
|
||
* to STADIUM_CONTAINS_NONE will admit a patron that reads as "contains
|
||
* Hera" and is therefore permanently un-evictable. There is no way to tell
|
||
* "meant to be 0" from "forgot to set it" from inside this function --
|
||
* callers must set every field, `contains` included.
|
||
*
|
||
* @param candidate Header to admit. Copied into the winning cell as-is;
|
||
* caller fills in every field including mass and heat.
|
||
* @return The cell index admitted into, or STADIUM_CELL_NONE if refused
|
||
* (mass != 1, invalid contains, Stadium full and candidate not
|
||
* denser than the least-dense evictable resident, or no evictable
|
||
* resident exists at all).
|
||
*/
|
||
size_t stadium_admit(const StadiumPatronHeader *candidate);
|
||
|
||
#endif /* __STARKERNEL__ */
|
||
|
||
#endif /* STARKERNEL_VM_STADIUM_H */ |