/* 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. */ /** * doe_region.h - Growable per-trial DoE-campaign-summary ring on Artemis's * own disk (FABRIC-3.md §XXXV.3/.4, built 2026-09-17) * * Records one summary per completed multiuser-doe.4th/per-isa-doe.4th trial * so a campaign's pass/fail and top-line physics picture survives past the * QEMU serial log -- §XXXV.1 identified that doe_log.c's per-tick rows * (console_puts()-only) cannot survive real bare-metal boot at all, and * raw per-tick mirroring to block storage was ruled out entirely on size * grounds (§XXXV.2: 516 MB for 8 trials vs. a ~1 GiB device). This region * persists trial SUMMARIES only (v1, §XXXV.4) -- exactly the fields * MU-EMIT-TRIAL-MARKER already computes, plus three trial-end aggregate * physics reads (vm_physics_fleet_heat_sum(), vm_physics_conserved(), * sk_vm_switch_signal_switch_count()) -- not a raw tick mirror. * * Same fence/accessor discipline log_region.h already established: lives * in Artemis's top-of-device system-metadata fence, reached via * block_subsystem.h's blk_meta_zone_read()/write() (devblock_from_top * addressing), same growable-ring control-header shape (magic/version/CRC, * devblocks/head_slot/tail_slot/record_count). A SEPARATE region from * log_region.c's own -- different shape (structured numeric fields, not * free text), different growth ceiling, and isolation so a runaway DoE * region can never crowd out real log persistence (§XXXV.3's own stated * reasoning, reused verbatim here). * * Fixed devblock_from_top allocation, immediately past log_region.c's own * ceiling (which occupies [65, 65+1+LOG_REGION_MAX_DEVBLOCKS-1] = * [65, 97] at full growth): * 98 -- this region's control header (DOE_REGION_DEVBLOCK_FROM_TOP_BASE) * 99-102 -- this region's slot devblocks, growable up to DOE_REGION_MAX_DEVBLOCKS * * Slot granularity is small (DOE_SLOT_SIZE, 64 bytes) relative to * log_slot_t's 1024 -- a trial summary is a handful of fixed numeric * fields plus an 8-byte ISA tag, nothing free-text-sized. 64 slots/devblock * x DOE_REGION_INITIAL_DEVBLOCKS(1) = 64 already exceeds one full 3-ISA * campaign's 54 trials (§XXXV.4); the growth ceiling (4 devblocks = 256 * slots) is headroom for repeated campaigns, not a sizing risk. * * No priority-eviction logic (unlike log_region.c's LOG_REGION_PROTECTED_ * MAX_LEVEL, §XXXII.4) -- there is no level concept for a trial summary; * plain FIFO eviction of the oldest record when the ring is full AND * already at the growth ceiling, which in practice will not be reached by * any campaign shape this project has run or ratified. */ #ifndef STARKERNEL_DOE_REGION_H #define STARKERNEL_DOE_REGION_H #include #include "starkernel/log_region.h" /* LOG_REGION_DEVBLOCK_FROM_TOP_BASE, LOG_REGION_MAX_DEVBLOCKS -- fence math only */ #ifdef __cplusplus extern "C" { #endif /*=========================================================================== * Fence allocation *===========================================================================*/ #define DOE_REGION_DEVBLOCK_FROM_TOP_BASE \ (LOG_REGION_DEVBLOCK_FROM_TOP_BASE + 1u + LOG_REGION_MAX_DEVBLOCKS) /* 98 */ #define DOE_REGION_INITIAL_DEVBLOCKS 1u /* slot devblocks at first use, excludes control header */ #define DOE_REGION_GROWTH_INCREMENT 1u #define DOE_REGION_MAX_DEVBLOCKS 4u /* ceiling -- devblock_from_top stays within [99,102] */ #define DOE_SLOT_SIZE 64u #define DOE_SLOTS_PER_DEVBLOCK (4096u / DOE_SLOT_SIZE) /* 64 */ /*=========================================================================== * doe_region_ctrl_t - ring control header, one devblock at * DOE_REGION_DEVBLOCK_FROM_TOP_BASE. Same shape as log_region_ctrl_t. *===========================================================================*/ #define DOE_REGION_MAGIC 0x44454F44ull /* 'DOED' */ #define DOE_REGION_VERSION_0 0 #define DOE_REGION_PACK(ver) \ (DOE_REGION_MAGIC | ((uint64_t)(ver) << 32)) #define DOE_REGION_GET_MAGIC(m) ((uint32_t)((m) & 0xFFFFFFFFull)) #define DOE_REGION_GET_VERSION(m) ((uint8_t)(((m) >> 32) & 0xFF)) typedef struct { uint64_t magic; /* DOE_REGION_PACK(...) */ uint32_t devblocks; /* current slot-area size, in devblocks (excludes this header) */ uint32_t head_slot; /* index of the oldest live record */ uint32_t tail_slot; /* index where the NEXT record will be written */ uint32_t record_count; /* live records, <= devblocks * DOE_SLOTS_PER_DEVBLOCK */ uint64_t hdr_crc; /* covers every field above this one */ uint8_t _pad[4096 - (8 + 4 + 4 + 4 + 4 + 8)]; } doe_region_ctrl_t; typedef char doe_region_ctrl_size_check[(sizeof(doe_region_ctrl_t) == 4096) ? 1 : -1]; /*=========================================================================== * doe_trial_slot_t - one trial summary record, exactly DOE_SLOT_SIZE bytes. * Field order: uint64_t pair first (natural 8-byte alignment at offsets * 0/8), then uint32_t fields (4-byte aligned from offset 16 on), then the * ISA tag, then an EXPLICIT alignment field (_align_pad) so slot_crc lands * on an offset the compiler needs no implicit padding to reach -- same * "every field lands on its natural alignment with zero compiler-inserted * padding" discipline log_slot_t's own doc comment states. Leaving this to * an implicit trailing _pad array (as an earlier draft did) put slot_crc * right after an 8-byte char array at a non-8-aligned offset, so the * compiler silently inserted its own padding there instead and the * hand-computed trailing pad size no longer matched sizeof() -- caught by * this file's own static size-check failing to compile. *===========================================================================*/ #define DOE_SLOT_ISA_MAX 8u /* NUL-padded, e.g. "amd64", "aarch64", "riscv64" */ typedef struct { uint64_t fleet_k_q48; /* vm_physics_fleet_heat_sum() at trial end */ uint64_t switch_count_cumulative; /* sk_vm_switch_signal_switch_count() at trial end */ uint32_t run_id; uint32_t cfg; uint32_t rep; uint32_t nw; uint32_t mode; uint32_t fail_count; uint32_t fleet_conserved; /* vm_physics_conserved() at trial end, 0/1 */ char isa[DOE_SLOT_ISA_MAX]; uint32_t _align_pad; /* explicit -- brings the next field to offset 56 (8-aligned) */ uint64_t slot_crc; /* covers every field above this one (§XXXV.12) -- * a devblock holds DOE_SLOTS_PER_DEVBLOCK=64 slots via * one read-modify-write, so a crash mid-write to ANY * slot in a devblock can corrupt its siblings; this is * how a reader tells a genuine record from corruption. */ } doe_trial_slot_t; typedef char doe_trial_slot_size_check[(sizeof(doe_trial_slot_t) == DOE_SLOT_SIZE) ? 1 : -1]; /*=========================================================================== * API *===========================================================================*/ /* * doe_region_append - Write one trial-summary record to the ring, growing * it (within DOE_REGION_MAX_DEVBLOCKS) or evicting the oldest record (ring * full and already at the growth ceiling) as needed. Initializes the ring * on first use (control header blank). * * @param run_id, cfg, rep, nw, mode, fail_count Same fields * MU-EMIT-TRIAL-MARKER already prints (multiuser-doe.4th Block 5051). * @param fleet_k_q48, fleet_conserved, switch_count_cumulative Trial-end * aggregate physics reads (§XXXV.4). * @param isa ISA tag, e.g. "amd64" (may be empty for the pre-per-isa- * doe.4th single-ISA campaign shape). * @param isa_len Length of isa (truncated to DOE_SLOT_ISA_MAX-1). * @return 0 on success, -1 on any read/write failure (ring left however * the failed operation left it -- blk_meta_zone_write() itself * never partially writes a devblock). */ int doe_region_append(uint32_t run_id, uint32_t cfg, uint32_t rep, uint32_t nw, uint32_t mode, uint32_t fail_count, uint64_t fleet_k_q48, uint32_t fleet_conserved, uint64_t switch_count_cumulative, const char *isa, uint32_t isa_len); /* * doe_region_count_for_isa - Count CRC-valid, live records in the ring * whose isa tag matches the given string (§XXXV.12, campaign crash-resume). * A record that fails its slot_crc check does not count -- the caller * (DOE-RESUME-COUNT) treats a corrupted slot as "not actually done," so * that trial safely re-runs rather than being silently skipped. Returns 0 * if the ring has never been initialized (matching doe_region_append()'s * own "blank == not yet initialized" discipline), never negative. * * @param isa ISA tag to match, e.g. "amd64". * @param isa_len Length of isa (truncated to DOE_SLOT_ISA_MAX-1). * @return Count of matching, CRC-valid records (0 or more). */ uint32_t doe_region_count_for_isa(const char *isa, uint32_t isa_len); #ifdef __cplusplus } #endif #endif /* STARKERNEL_DOE_REGION_H */