Files
LithosAnanake/src/starkernel/doe_region.c
T
Robert Allan JamesandClaude Opus 5.5 5bc7b4dee7 Stage MC-0b: real kernel spinlocks; sf_mutex no longer a no-op (FABRIC-3.10.8 §3.2)
- New spinlock primitive (spinlock.h/.c): CAS on an owner word (core+1),
  interrupts masked while any lock is held (state kept in the per-core
  block, so release order doesn't matter). Recursion, release-not-held,
  held-at-switch and destroyed-while-held are counted and logged, never
  silent. Locks are no-ops until the boot core's block is installed
  (riscv64's monolithic early boot prints before sk_percpu_boot_init()).
- Kernel locks in a documented order: hermes, switch, registry, doe, log,
  console, heap, vmm, pmm. sf_mutex (dict_lock/tuning_lock) uses the same
  lock word.
- Hermes: public functions over *_locked internals; never held across the
  drain's vm_interpret_kind(); switch notifications after dropping the
  lock; event payload moved off static storage. Switch table and registry
  never call out while held (slot/entry copied under the lock).
- No lock crosses a switch: checked at context switch (leave, resume) and
  the idle point.
- Probe (removed): every lock balances on all three ISAs; 0 held at
  switch over 7.5M-51M checks; 0 recursions, 0 bad releases.
- Standard sequence on amd64/aarch64/riscv64: hashes and G byte-identical
  to MC-0a, MINT/WIREBIND/USE ok, conservation true, 0 idle growth.
- Block subsystem split out as MC-0b2 (Captain Bob's ruling).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:43:19 -04:00

229 lines
9.7 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.
*/
/**
* 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 */
#include "starkernel/spinlock.h" /* FABRIC-3.10.8 MC-0b -- g_lock_doe */
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);
}
/* §XXXV.12: a devblock holds DOE_SLOTS_PER_DEVBLOCK slots via one
* read-modify-write, so a crash mid-write can corrupt siblings of the
* slot actually being written. This is how a reader (doe_region_
* count_for_isa(), and any future host-side extractor) tells a genuine
* record from that corruption -- same offsetof-span discipline as the
* control header's own CRC above. */
static uint64_t doe_region_slot_compute_crc(const doe_trial_slot_t *slot) {
size_t crc_span = offsetof(doe_trial_slot_t, slot_crc);
return compute_crc64((const uint8_t *)slot, 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;
}
/* Returns 0 and *out populated only if the slot's own CRC checks out --
* a corrupted sibling slot (§XXXV.12's own devblock-sharing risk) reads
* back as a failure here, not as a plausible-looking garbage record. */
static int doe_region_read_slot(uint32_t slot_index, doe_trial_slot_t *out) {
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;
if (blk_meta_zone_read(dft, devblock_buf) != 0) return -1;
memcpy(out, devblock_buf + (size_t)slot_in_devblock * DOE_SLOT_SIZE, sizeof(*out));
if (doe_region_slot_compute_crc(out) != out->slot_crc) return -1;
return 0;
}
/* FABRIC-3.10.8 MC-0b: append and count run under g_lock_doe (the public
* functions below); these are their bodies. */
static int doe_region_append_locked(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);
slot.slot_crc = doe_region_slot_compute_crc(&slot);
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;
}
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) {
int rc;
sk_spin_lock(&g_lock_doe);
rc = doe_region_append_locked(run_id, cfg, rep, nw, mode, fail_count, fleet_k_q48,
fleet_conserved, switch_count_cumulative, isa, isa_len);
sk_spin_unlock(&g_lock_doe);
return rc;
}
static uint32_t doe_region_count_for_isa_locked(const char *isa, uint32_t isa_len) {
doe_region_ctrl_t ctrl;
if (doe_region_ctrl_read(&ctrl) != 0) return 0;
/* Build the full DOE_SLOT_ISA_MAX-byte zero-padded tag exactly as
* doe_region_append() stores it, so this compares the whole field
* (including trailing zero padding) -- not just a prefix, which would
* wrongly match e.g. "amd" against a stored "amd64". */
char want[DOE_SLOT_ISA_MAX];
memset(want, 0, sizeof(want));
uint32_t isa_n = isa_len;
if (isa_n > DOE_SLOT_ISA_MAX) isa_n = DOE_SLOT_ISA_MAX;
if (isa && isa_n) memcpy(want, isa, isa_n);
uint32_t total_slots = ctrl.devblocks * DOE_SLOTS_PER_DEVBLOCK;
uint32_t count = 0;
uint32_t idx = ctrl.head_slot;
for (uint32_t i = 0; i < ctrl.record_count; i++) {
doe_trial_slot_t slot;
if (doe_region_read_slot(idx, &slot) == 0) {
if (memcmp(slot.isa, want, DOE_SLOT_ISA_MAX) == 0) {
count++;
}
}
idx = (idx + 1u) % total_slots;
}
return count;
}
uint32_t doe_region_count_for_isa(const char *isa, uint32_t isa_len) {
uint32_t n;
sk_spin_lock(&g_lock_doe);
n = doe_region_count_for_isa_locked(isa, isa_len);
sk_spin_unlock(&g_lock_doe);
return n;
}