Build DOE-PERSIST-TRIAL: block-backed DoE campaign persistence, live-verified
FABRIC-3.md §XXXV.3/.4 designed this and it was never built -- two live campaigns got launched relying entirely on a live serial-log tail before this was caught. doe_region.c/.h mirrors log_region.c's own growable-ring pattern (own devblock_from_top fence at 98, past log_region's ceiling at 97) to persist one trial-summary record per completed trial straight to disk/artemis.img, surviving past QEMU exit with no host required. Verified end to end: booted amd64, called DOE-PERSIST-TRIAL at the console, clean BYE, then read the raw disk bytes back with QEMU fully dead -- confirmed the exact values passed in. Wired into both multiuser-doe.4th and per-isa-doe.4th's trial markers. Clean build, zero new warnings, all 3 ISAs; mkcapsule --lint passes both edited capsules. FABRIC-3.md §XXXV.10 documents the fix and the root-cause correction: two campaigns were launched on an unbuilt persistence design before this was caught and fixed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
8b8e8e1405
commit
2da746c01c
+64
@@ -5578,3 +5578,67 @@ even one real trial. Launching the first live ~24.8h run is a separate, later st
|
||||
their baseline identity/auth works (confirmed live), but elevation eligibility, if any of them
|
||||
had it before, was not restored. Left open; re-add via `ZUSE-ELIGIBILITY-ADD` later if needed.
|
||||
|
||||
### XXXV.10 -- §XXXV.3/.4's persistence writer BUILT and live-verified end to end
|
||||
(2026-09-17)
|
||||
|
||||
**Root-cause correction first:** two live campaigns (the original single-ISA 180-trial run and
|
||||
this section's own per-ISA amd64 leg) were launched between §XXXV.8 and this entry on the
|
||||
mistaken assumption that the campaign was ready, without re-checking that §XXXV.9 explicitly
|
||||
flagged the persistence writer as design-only. Both runs depended entirely on a live serial-log
|
||||
tail and were killed mid-flight (one by a colliding peer session, one deliberately, by request)
|
||||
with no durable record beyond the raw serial logs already committed. Flagged directly by Captain
|
||||
Bob ("Just plain dropped the ball and ignored all previous instructions") -- correct. Saved as
|
||||
`feedback_verify_design_implemented_before_launching_campaign.md`: grep the actual source for a
|
||||
named primitive before treating a design section as launch-ready, don't infer readiness from a
|
||||
nearby "ratified"/"executed" header.
|
||||
|
||||
**Built, mirroring `log_region.c`/`log_region.h`'s exact pattern per §XXXV.3:**
|
||||
- `include/starkernel/doe_region.h` / `src/starkernel/doe_region.c` -- new, separate growable
|
||||
ring (own control header, own `devblock_from_top` base at 98, immediately past
|
||||
`log_region.c`'s own ceiling at 97 -- confirmed by direct arithmetic, not the `log_region.h`
|
||||
doc comment's slightly-off "97+" framing, which actually meant "97 is log_region's last used
|
||||
devblock," so 98 is the first genuinely free one). 64-byte slots (`doe_trial_slot_t`: two
|
||||
`uint64_t` aggregates -- `fleet_k_q48`, `switch_count_cumulative` -- six `uint32_t` trial
|
||||
fields matching `MU-EMIT-TRIAL-MARKER` exactly -- `run_id`/`cfg`/`rep`/`nw`/`mode`/
|
||||
`fail_count` -- plus `fleet_conserved` and an 8-byte ISA tag), 64 slots/devblock, growable
|
||||
1->4 devblocks (256 slots ceiling, well over one full 3-ISA campaign's 54 trials). Plain FIFO
|
||||
eviction at the ceiling (no priority-level concept for a trial summary, unlike
|
||||
`log_region.c`'s `LOG_REGION_PROTECTED_MAX_LEVEL`).
|
||||
- `DOE-PERSIST-TRIAL ( run cfg rep nw mode fail isa-addr isa-u -- )`, registered in
|
||||
`src/word_source/log_words.c` (same file as `(LOG-APPEND-RAW)`, same kernel-only pattern).
|
||||
`fleet_k_q48`/`fleet_conserved`/`switch_count_cumulative` are read directly inside the C word
|
||||
(`vm_physics_fleet_heat_sum()`, `vm_physics_conserved()`, `sk_vm_switch_signal_switch_count()`)
|
||||
-- not caller-supplied, same non-spoofable-attribution discipline `(LOG-APPEND-RAW)` already
|
||||
established for its own source field.
|
||||
- `Makefile.starkernel`: `doe_region.c` added to both `LOADER_SRCS_BASE` and `KERNEL_SRCS_BASE`
|
||||
alongside `log_region.c`.
|
||||
- Wired into both `multiuser-doe.4th`'s own `MU-EMIT-TRIAL-MARKER` (Block 5051, empty ISA tag --
|
||||
the base campaign driver has no ISA concept of its own) and `per-isa-doe.4th`'s redefinition
|
||||
(Block 5118, real `PER-ISA-TAG-BUF`/`PER-ISA-TAG-LEN` passed through) -- both fit within the
|
||||
existing block allocation with no new blocks needed (`per-isa-doe.4th` had zero spare blocks
|
||||
per §XXXV.8's own note; fit entirely inside the 4 lines of headroom Block 5118 already had).
|
||||
`mkcapsule --lint` PASS on both files after editing.
|
||||
|
||||
**Verified live, full round trip, not just "it compiles":**
|
||||
1. Clean build, zero new warnings, all 3 ISAs (amd64/aarch64/riscv64) -- confirmed `doe_region.o`
|
||||
present in both loader and kernel object trees.
|
||||
2. Booted amd64, loaded `multiuser-doe.4th` then `per-isa-doe.4th`, called
|
||||
`111 22 3 4 5 0 S" testisa" DOE-PERSIST-TRIAL` directly at the console -- `ok`, no error;
|
||||
`VM-ERROR? .` printed `0`. Clean `BYE`, zero leaked `qemu-system-x86_64` processes.
|
||||
3. **With QEMU fully exited**, read `disk/artemis.img` directly off disk with an independent
|
||||
host-side script (no live process, no serial socket involved) -- confirmed the control header
|
||||
at `devblock_from_top=98` (magic `'DOED'`, `devblocks=1 head_slot=0 tail_slot=1
|
||||
record_count=1`) and the first slot at `devblock_from_top=99` contain exactly
|
||||
`run_id=111 cfg=22 rep=3 nw=4 mode=5 fail=0 conserved=1 isa="testisa"` -- the literal values
|
||||
passed at the console, byte-for-byte, persisted with no host in the loop. This is the actual
|
||||
property §XXXV.1 identified as missing and required.
|
||||
4. Reverted the synthetic test write from `disk/artemis.img` (`git checkout --`) before
|
||||
committing, per this repo's own established convention (§XXXV.8's `mounted_time` precedent)
|
||||
-- the region's first real record should be an actual campaign trial, not test data.
|
||||
|
||||
**Still open, unchanged from §XXXV.9:** the host-side extraction/reader tool (reading the ring
|
||||
back into a CSV, mirroring `scripts/extract_doe.sh`'s role for serial logs) is not built -- v1
|
||||
scope per §XXXV.3 was the writer only, verified here by a one-off Python script reading the raw
|
||||
bytes directly, not a maintained tool. Launching the first live multi-hour campaign is still a
|
||||
separate, later, explicit decision -- this entry closes the persistence-writer gap, nothing more.
|
||||
|
||||
|
||||
+4
-2
@@ -458,7 +458,8 @@ LOADER_SRCS_BASE := \
|
||||
$(KERNEL_SRC)/heartbeat.c \
|
||||
$(KERNEL_SRC)/homeblocks_sig.c \
|
||||
$(KERNEL_SRC)/artemis_sig.c \
|
||||
$(KERNEL_SRC)/log_region.c
|
||||
$(KERNEL_SRC)/log_region.c \
|
||||
$(KERNEL_SRC)/doe_region.c
|
||||
|
||||
LOADER_ASM := \
|
||||
$(KERNEL_SRC)/arch/$(ARCH)/boot.S \
|
||||
@@ -516,7 +517,8 @@ KERNEL_SRCS_BASE := \
|
||||
$(KERNEL_SRC)/heartbeat.c \
|
||||
$(KERNEL_SRC)/homeblocks_sig.c \
|
||||
$(KERNEL_SRC)/artemis_sig.c \
|
||||
$(KERNEL_SRC)/log_region.c
|
||||
$(KERNEL_SRC)/log_region.c \
|
||||
$(KERNEL_SRC)/doe_region.c
|
||||
|
||||
KERNEL_ASM := $(wildcard $(KERNEL_SRC)/arch/$(ARCH)/*.S)
|
||||
|
||||
|
||||
+18
-18
@@ -1,5 +1,5 @@
|
||||
# Capsule Block Manifest — Auto-generated
|
||||
<!-- Generated by mkcapsule --manifest 2026-09-17T16:28:31Z -->
|
||||
<!-- Generated by mkcapsule --manifest 2026-09-17T18:37:43Z -->
|
||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
@@ -27,8 +27,8 @@
|
||||
| `init-l8-volatile.4th` | 4810, 4811, 4812, 4813 | `0x98caabbbd92abac4` | yes |
|
||||
| `init.4th` | 2049, 2050, 2057 | `0x1ef4939ed32ec1e6` | yes |
|
||||
| `lib.4th` | 4050 | `0x4b216635c359ef73` | yes |
|
||||
| `multiuser-doe.4th` | 5044, 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5054, 5055 | `0x22140588ee7c39e6` | yes |
|
||||
| `per-isa-doe.4th` | 5117, 5118, 5119 | `0xe3a6c1352d3ecaed` | yes |
|
||||
| `multiuser-doe.4th` | 5044, 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5054, 5055 | `0xaef05d0fd502fdc4` | yes |
|
||||
| `per-isa-doe.4th` | 5117, 5118, 5119 | `0x369a7c1cad0c3a7b` | yes |
|
||||
| `process.4th` | 4300, 4301 | `0x781afc1dbd0294f7` | yes |
|
||||
| `sdk.4th` | 5109, 5110, 5111, 5112, 5113, 5114, 5115 | `0x008fdbbb62c94a3a` | yes |
|
||||
| `turtle.4th` | 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5107, 5108 | `0x4d470418ca543365` | yes |
|
||||
@@ -377,18 +377,18 @@
|
||||
| 5041 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
|
||||
| 5042 | `common:messaging.4th` | `0x201cfd6d39fcb22d` | ok |
|
||||
| 5043 | `workload-calib1.4th` | `0x3b9f2d17b554fabc` | ok |
|
||||
| 5044 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
|
||||
| 5045 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
|
||||
| 5046 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
|
||||
| 5047 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
|
||||
| 5048 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
|
||||
| 5049 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
|
||||
| 5050 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
|
||||
| 5051 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
|
||||
| 5052 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
|
||||
| 5053 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
|
||||
| 5054 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
|
||||
| 5055 | `multiuser-doe.4th` | `0x22140588ee7c39e6` | ok |
|
||||
| 5044 | `multiuser-doe.4th` | `0xaef05d0fd502fdc4` | ok |
|
||||
| 5045 | `multiuser-doe.4th` | `0xaef05d0fd502fdc4` | ok |
|
||||
| 5046 | `multiuser-doe.4th` | `0xaef05d0fd502fdc4` | ok |
|
||||
| 5047 | `multiuser-doe.4th` | `0xaef05d0fd502fdc4` | ok |
|
||||
| 5048 | `multiuser-doe.4th` | `0xaef05d0fd502fdc4` | ok |
|
||||
| 5049 | `multiuser-doe.4th` | `0xaef05d0fd502fdc4` | ok |
|
||||
| 5050 | `multiuser-doe.4th` | `0xaef05d0fd502fdc4` | ok |
|
||||
| 5051 | `multiuser-doe.4th` | `0xaef05d0fd502fdc4` | ok |
|
||||
| 5052 | `multiuser-doe.4th` | `0xaef05d0fd502fdc4` | ok |
|
||||
| 5053 | `multiuser-doe.4th` | `0xaef05d0fd502fdc4` | ok |
|
||||
| 5054 | `multiuser-doe.4th` | `0xaef05d0fd502fdc4` | ok |
|
||||
| 5055 | `multiuser-doe.4th` | `0xaef05d0fd502fdc4` | ok |
|
||||
| 5056 | `workload-5-lite.4th` | `0xea0303f38824f254` | ok |
|
||||
| 5057 | `workload-5-lite.4th` | `0xea0303f38824f254` | ok |
|
||||
| 5058 | `workload-1-lite.4th` | `0x44a7a7e3176dcc8d` | ok |
|
||||
@@ -410,9 +410,9 @@
|
||||
| 5114 | `sdk.4th` | `0x008fdbbb62c94a3a` | ok |
|
||||
| 5115 | `sdk.4th` | `0x008fdbbb62c94a3a` | ok |
|
||||
| 5116 | `hermes:init.4th` | `0x2df61924448a6812` | ok |
|
||||
| 5117 | `per-isa-doe.4th` | `0xe3a6c1352d3ecaed` | ok |
|
||||
| 5118 | `per-isa-doe.4th` | `0xe3a6c1352d3ecaed` | ok |
|
||||
| 5119 | `per-isa-doe.4th` | `0xe3a6c1352d3ecaed` | ok |
|
||||
| 5117 | `per-isa-doe.4th` | `0x369a7c1cad0c3a7b` | ok |
|
||||
| 5118 | `per-isa-doe.4th` | `0x369a7c1cad0c3a7b` | ok |
|
||||
| 5119 | `per-isa-doe.4th` | `0x369a7c1cad0c3a7b` | ok |
|
||||
|
||||
## Conflicts
|
||||
|
||||
|
||||
@@ -110,7 +110,9 @@ VARIABLE MU-MODE VARIABLE MU-NREPS
|
||||
." rep=" MU-REP @ .
|
||||
." nw=" MU-NW @ .
|
||||
." mode=" MU-MODE @ .
|
||||
." fail=" MU-FAIL-COUNT @ . CR ;
|
||||
." fail=" MU-FAIL-COUNT @ . CR
|
||||
MU-RUN-ID @ MU-CFG @ MU-REP @ MU-NW @
|
||||
MU-MODE @ MU-FAIL-COUNT @ 0 0 DOE-PERSIST-TRIAL ;
|
||||
|
||||
Block 5052
|
||||
: MU-BIRTH-ALL ( -- )
|
||||
|
||||
@@ -27,7 +27,10 @@ Block 5118
|
||||
." rep=" MU-REP @ .
|
||||
." nw=" MU-NW @ .
|
||||
." mode=" MU-MODE @ .
|
||||
." fail=" MU-FAIL-COUNT @ . CR ;
|
||||
." fail=" MU-FAIL-COUNT @ . CR
|
||||
MU-RUN-ID @ MU-CFG @ MU-REP @ MU-NW @
|
||||
MU-MODE @ MU-FAIL-COUNT @
|
||||
PER-ISA-TAG-BUF PER-ISA-TAG-LEN @ DOE-PERSIST-TRIAL ;
|
||||
|
||||
Block 5119
|
||||
( Per-ISA entry points, single word, no args -- for boot )
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
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 <stdint.h>
|
||||
#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 trailing pad -- same alignment discipline log_slot_t uses.
|
||||
*===========================================================================*/
|
||||
|
||||
#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];
|
||||
uint8_t _pad[DOE_SLOT_SIZE - (8 + 8 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + DOE_SLOT_ISA_MAX)];
|
||||
} 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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_DOE_REGION_H */
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
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.c - Growable per-trial DoE-campaign-summary ring on Artemis's
|
||||
* disk. See starkernel/doe_region.h for the format/interface design.
|
||||
*
|
||||
* Mirrors log_region.c's own read/write/grow/evict shape exactly (same
|
||||
* blk_meta_zone_read()/write() accessor, same control-header CRC
|
||||
* discipline), minus the priority-eviction logic that doesn't apply here
|
||||
* (no level concept for a trial summary -- plain FIFO eviction only).
|
||||
*/
|
||||
|
||||
#include "starkernel/doe_region.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "block_subsystem.h" /* blk_meta_zone_read/write, compute_crc64 */
|
||||
|
||||
static uint64_t doe_region_ctrl_compute_crc(const doe_region_ctrl_t *ctrl) {
|
||||
size_t crc_span = offsetof(doe_region_ctrl_t, hdr_crc);
|
||||
return compute_crc64((const uint8_t *)ctrl, crc_span);
|
||||
}
|
||||
|
||||
/* Reads the control header; returns 0 and *out populated only if magic,
|
||||
* version, and CRC all check out. Any other result (blank, foreign,
|
||||
* corrupt, or a read failure) is treated identically by the caller: "not
|
||||
* yet initialized," matching log_region_ctrl_read()'s own discipline. */
|
||||
static int doe_region_ctrl_read(doe_region_ctrl_t *out) {
|
||||
if (blk_meta_zone_read(DOE_REGION_DEVBLOCK_FROM_TOP_BASE, (uint8_t *)out) != 0) return -1;
|
||||
if (DOE_REGION_GET_MAGIC(out->magic) != (uint32_t)(DOE_REGION_MAGIC & 0xFFFFFFFFull)) return -1;
|
||||
if (DOE_REGION_GET_VERSION(out->magic) != DOE_REGION_VERSION_0) return -1;
|
||||
uint64_t want_crc = doe_region_ctrl_compute_crc(out);
|
||||
if (want_crc != out->hdr_crc) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int doe_region_ctrl_write(doe_region_ctrl_t *ctrl) {
|
||||
ctrl->hdr_crc = doe_region_ctrl_compute_crc(ctrl);
|
||||
return blk_meta_zone_write(DOE_REGION_DEVBLOCK_FROM_TOP_BASE, (const uint8_t *)ctrl) == 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
static void doe_region_ctrl_init_fresh(doe_region_ctrl_t *ctrl) {
|
||||
memset(ctrl, 0, sizeof(*ctrl));
|
||||
ctrl->magic = DOE_REGION_PACK(DOE_REGION_VERSION_0);
|
||||
ctrl->devblocks = DOE_REGION_INITIAL_DEVBLOCKS;
|
||||
ctrl->head_slot = 0;
|
||||
ctrl->tail_slot = 0;
|
||||
ctrl->record_count = 0;
|
||||
}
|
||||
|
||||
/* Reads/writes the devblock holding slot_index (DOE_SLOTS_PER_DEVBLOCK
|
||||
* slots per devblock), one devblock_from_top past the control header for
|
||||
* every DOE_SLOTS_PER_DEVBLOCK slots. */
|
||||
static uint32_t doe_region_slot_devblock_from_top(uint32_t slot_index) {
|
||||
return DOE_REGION_DEVBLOCK_FROM_TOP_BASE + 1u + (slot_index / DOE_SLOTS_PER_DEVBLOCK);
|
||||
}
|
||||
|
||||
static int doe_region_write_slot(uint32_t slot_index, const doe_trial_slot_t *slot) {
|
||||
uint8_t devblock_buf[4096];
|
||||
uint32_t dft = doe_region_slot_devblock_from_top(slot_index);
|
||||
uint32_t slot_in_devblock = slot_index % DOE_SLOTS_PER_DEVBLOCK;
|
||||
|
||||
/* Read-modify-write: blk_meta_zone_*() only operates at whole-devblock
|
||||
* granularity, and a devblock holds DOE_SLOTS_PER_DEVBLOCK slots, so a
|
||||
* single-slot write must preserve its siblings. A read failure on a
|
||||
* never-yet-written devblock (blank fence content) is not fatal here --
|
||||
* proceed with a zeroed buffer, matching log_region_write_slot()'s own
|
||||
* discipline. */
|
||||
if (blk_meta_zone_read(dft, devblock_buf) != 0) {
|
||||
memset(devblock_buf, 0, sizeof(devblock_buf));
|
||||
}
|
||||
memcpy(devblock_buf + (size_t)slot_in_devblock * DOE_SLOT_SIZE, slot, sizeof(*slot));
|
||||
return blk_meta_zone_write(dft, devblock_buf) == 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
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_ctrl_t ctrl;
|
||||
if (doe_region_ctrl_read(&ctrl) != 0) {
|
||||
doe_region_ctrl_init_fresh(&ctrl);
|
||||
}
|
||||
|
||||
uint32_t total_slots = ctrl.devblocks * DOE_SLOTS_PER_DEVBLOCK;
|
||||
if (ctrl.record_count >= total_slots) {
|
||||
if (ctrl.devblocks < DOE_REGION_MAX_DEVBLOCKS) {
|
||||
/* Grow: new slots appear at indices [old_total_slots ..
|
||||
* new_total_slots-1], nothing already written moves -- safe
|
||||
* at any point in the ring's lifecycle (see log_region.c's
|
||||
* own identical reasoning). */
|
||||
ctrl.devblocks += DOE_REGION_GROWTH_INCREMENT;
|
||||
if (ctrl.devblocks > DOE_REGION_MAX_DEVBLOCKS) ctrl.devblocks = DOE_REGION_MAX_DEVBLOCKS;
|
||||
total_slots = ctrl.devblocks * DOE_SLOTS_PER_DEVBLOCK;
|
||||
}
|
||||
if (ctrl.record_count >= total_slots) {
|
||||
/* Still full (growth ceiling already reached) -- plain FIFO
|
||||
* eviction, no priority concept for a trial summary. */
|
||||
ctrl.head_slot = (ctrl.head_slot + 1u) % total_slots;
|
||||
ctrl.record_count--;
|
||||
}
|
||||
}
|
||||
|
||||
doe_trial_slot_t slot;
|
||||
memset(&slot, 0, sizeof(slot));
|
||||
slot.fleet_k_q48 = fleet_k_q48;
|
||||
slot.switch_count_cumulative = switch_count_cumulative;
|
||||
slot.run_id = run_id;
|
||||
slot.cfg = cfg;
|
||||
slot.rep = rep;
|
||||
slot.nw = nw;
|
||||
slot.mode = mode;
|
||||
slot.fail_count = fail_count;
|
||||
slot.fleet_conserved = fleet_conserved;
|
||||
|
||||
uint32_t isa_n = isa_len;
|
||||
if (isa_n > DOE_SLOT_ISA_MAX) isa_n = DOE_SLOT_ISA_MAX;
|
||||
if (isa && isa_n) memcpy(slot.isa, isa, isa_n);
|
||||
|
||||
if (doe_region_write_slot(ctrl.tail_slot, &slot) != 0) return -1;
|
||||
|
||||
ctrl.tail_slot = (ctrl.tail_slot + 1u) % total_slots;
|
||||
if (ctrl.record_count < total_slots) ctrl.record_count++;
|
||||
|
||||
return doe_region_ctrl_write(&ctrl) == 0 ? 0 : -1;
|
||||
}
|
||||
@@ -19,6 +19,9 @@
|
||||
#include "starkernel/log_attrib.h" /* vm_log_attributed_vm() */
|
||||
#include "starkernel/capsule_birth.h" /* capsule_vm_registry_get, VMRegistryEntry */
|
||||
#include "starkernel/console.h" /* console_println() -- (LOG-APPEND-RAW) diagnostics */
|
||||
#include "starkernel/doe_region.h" /* doe_region_append() -- DOE-PERSIST-TRIAL */
|
||||
#include "starkernel/capsule_vm_physics.h" /* vm_physics_fleet_heat_sum(), vm_physics_conserved() */
|
||||
#include "starkernel/capsule_vm_switch_signal.h" /* sk_vm_switch_signal_switch_count() */
|
||||
#include <string.h> /* strlen */
|
||||
#endif
|
||||
|
||||
@@ -296,6 +299,59 @@ static void log_word_append_raw(VM *vm)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ── DOE-PERSIST-TRIAL ( run cfg rep nw mode fail isa-addr isa-u -- ) ── */
|
||||
/*
|
||||
* Kernel-only. Writes one trial-summary record to Artemis's on-disk DoE
|
||||
* ring (doe_region.c), FABRIC-3.md §XXXV.3/.4. Called from
|
||||
* MU-EMIT-TRIAL-MARKER (multiuser-doe.4th / per-isa-doe.4th) right
|
||||
* alongside the existing console print, so a campaign's data survives
|
||||
* past the QEMU serial log without needing a live host tail.
|
||||
*
|
||||
* fleet_k_q48/fleet_conserved/switch_count_cumulative are NOT caller-
|
||||
* supplied -- read directly here via vm_physics_fleet_heat_sum(),
|
||||
* vm_physics_conserved(), sk_vm_switch_signal_switch_count(), same
|
||||
* discipline (LOG-APPEND-RAW) uses for its own source attribution: a
|
||||
* trial-end snapshot no FORTH caller could spoof or get stale by passing
|
||||
* the wrong value.
|
||||
*/
|
||||
#ifdef __STARKERNEL__
|
||||
static void log_word_doe_persist_trial(VM *vm)
|
||||
{
|
||||
if (vm->dsp < 7) {
|
||||
console_println("DOE-PERSIST-TRIAL: stack underflow");
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
cell_t isa_u = vm_pop(vm);
|
||||
cell_t isa_addr = vm_pop(vm);
|
||||
cell_t fail = vm_pop(vm);
|
||||
cell_t mode = vm_pop(vm);
|
||||
cell_t nw = vm_pop(vm);
|
||||
cell_t rep = vm_pop(vm);
|
||||
cell_t cfg = vm_pop(vm);
|
||||
cell_t run = vm_pop(vm);
|
||||
|
||||
if (isa_u < 0 || isa_addr < 0 || (isa_addr + isa_u) > (cell_t)VM_MEMORY_SIZE) {
|
||||
console_println("DOE-PERSIST-TRIAL: isa string address out of range");
|
||||
vm->error = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const char *isa = (const char *)&vm->memory[isa_addr];
|
||||
|
||||
uint64_t fleet_k_q48 = vm_physics_fleet_heat_sum();
|
||||
unsigned fleet_conserved = vm_physics_conserved() ? 1u : 0u;
|
||||
uint64_t switch_count = sk_vm_switch_signal_switch_count();
|
||||
|
||||
if (doe_region_append((uint32_t)run, (uint32_t)cfg, (uint32_t)rep, (uint32_t)nw,
|
||||
(uint32_t)mode, (uint32_t)fail,
|
||||
fleet_k_q48, fleet_conserved, switch_count,
|
||||
isa, (uint32_t)isa_u) != 0) {
|
||||
vm->error = 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ── Registration ────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
@@ -308,6 +364,7 @@ static void log_word_append_raw(VM *vm)
|
||||
* String literals: LOG-ERROR" LOG-WARN" LOG-INFO" LOG-TEST" LOG-DEBUG"
|
||||
* Stack string: LOG-ERROR-STR LOG-WARN-STR LOG-INFO-STR
|
||||
* LOG-TEST-STR LOG-DEBUG-STR
|
||||
* Kernel-only: (LOG-APPEND-RAW) DOE-PERSIST-TRIAL
|
||||
*/
|
||||
void register_log_words(VM *vm)
|
||||
{
|
||||
@@ -344,5 +401,6 @@ void register_log_words(VM *vm)
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
register_word(vm, "(LOG-APPEND-RAW)", log_word_append_raw);
|
||||
register_word(vm, "DOE-PERSIST-TRIAL", log_word_doe_persist_trial);
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user