Files
LithosAnanake/include/starkernel/spinlock.h
T
Robert Allan JamesandClaude Opus 5.5 02c4c7e620 Stage MC-0d: Stadium partitioned by quota slot, one lock per slot (FABRIC-3.10.8 §3.5)
- Ruling (Captain Bob): per-slot locks, not "own path needs no lock" --
  a lock only on the cross-slot side can't exclude an unlocked owner
  (Hermes release evicting a sender's cell while the sender admits).
  FABRIC-3.10.0 §3.6 amended.
- StadiumVMQuota.lock covers the slot's lists, reservoir, consumed, word
  row and its cells. Word dispatch holds only its own slot lock for the
  whole dispatch (*_in_slot forms); peek-then-pull is one step.
- g_lock_stadium: grant slot allocation (new slot published with release
  in_use) and the shared block table (ruled: the one shared-lock Stadium
  path). Taken before a slot lock.
- Bitmap bytes set/cleared atomically (8 cells, possibly 8 slots).
- Cross-slot writers moved under the owner's lock: Hermes decay
  (stadium_cell_decay), Hermes release ledger (stadium_evict_credit),
  session pin (stadium_cell_set_pin). stadium_conserved() one hold.
- Word and block cell lookups check the cell is still this slot's.
- Probe (removed), identical on all three ISAs: 10,746,599 of 10,746,616
  word dispatches took exactly one lock (own slot), the 17 others all row
  growths; every live VM conserved; Hermes audit failures 0.
- Findings reported: births/USE run a VM's words while another VM is the
  core's current VM (MC-3 claim must cover it); quota slots never
  released; owner admission can evict a live message cell (latent, 0).
- Standard sequence on amd64/aarch64/riscv64: dict_hash set and G
  byte-identical, MINT/WIREBIND/USE ok, conservation true, 0 idle growth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:40:33 -04:00

124 lines
5.1 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.
*/
/**
* spinlock.h - Kernel spinlocks (FABRIC-3.10.8 Stage MC-0b).
*
* One lock word: 0 = free, core_id + 1 = held by that core. Taken by
* compare-and-swap; interrupts are masked on the owning core while it
* holds any lock (the first lock taken saves the interrupt state in the
* per-core block, the last one released restores it -- percpu.h).
*
* The rule that makes this correct: **no lock is held across a context
* switch or into the idle point.** Every switch and every idle point
* checks it (sk_lock_check_none_held()) and counts a violation.
*
* Failures are counted, never silent (sk_lock_report() logs new ones):
* - recursion: this core takes a lock it already holds. On one core,
* "the owner is this core" can't tell a nested call from a second VM
* on the same core, so it is never treated as legitimate. The call is
* let through (spinning would hang the core) and counted;
* - bad release: releasing a lock this core doesn't hold;
* - held at switch: a switch or idle point reached with a lock held.
*
* sf_mutex_t (the vendored VM's dict_lock/tuning_lock) uses the same lock
* word through sk_lockword_acquire()/sk_lockword_release() (shim.c).
*
* THE KERNEL LOCKS, in acquisition order (a lock may be taken while
* holding only locks above it; MC-3's deadlock freedom rests on this):
*
* g_lock_hermes kernel-Hermes arena, queues, channels, ledger
* g_lock_switch switch-signal slot table and stamps
* (capsule_vm_switch_signal.c)
* g_lock_registry VM registry list and entries (capsule_birth.c)
* g_lock_stadium Stadium's global part: quota-slot allocation
* (grants) and the shared block table (MC-0d)
* (slot lock) one Stadium quota slot: its lists, reservoir,
* consumed, word row and cells (stadium.c's
* StadiumVMQuota.lock; at most one held at a time)
* g_lock_doe DoE region (doe_region.c)
* g_lock_log log region (log_region.c)
* g_lock_block block subsystem: device chain, caches, BAM,
* relocation table, and its device I/O
* (block_subsystem.c, MC-0b2)
* g_lock_console console output and line state (console.c)
* g_lock_heap kmalloc heap
* g_lock_vmm page tables (allocates tables from the PMM)
* g_lock_pmm physical page bitmap
*
* The switch table and the registry never call out while held: their walks
* copy one entry under the lock and judge it outside. Hermes hands the
* switch module its notifications after dropping its own lock (they read
* Hermes queues back). Stadium sits below Hermes (Hermes admits, evicts and
* decays cells) and below a VM's dict_lock (vm_core.c's heat bump dispatches
* the word under it), and above block (MIGRATE calls blk_flush()); nothing
* in the block path calls into Stadium.
*/
#ifndef STARKERNEL_SPINLOCK_H
#define STARKERNEL_SPINLOCK_H
#include <stdint.h>
typedef struct sk_spinlock {
volatile uint32_t owner; /* 0 = free, else core_id + 1 */
uint64_t acquired; /* updated while held, so exact */
uint64_t released;
} sk_spinlock_t;
#define SK_SPINLOCK_INIT { 0, 0, 0 }
void sk_lockword_acquire(volatile uint32_t *word);
void sk_lockword_release(volatile uint32_t *word);
void sk_spin_lock(sk_spinlock_t *l);
void sk_spin_unlock(sk_spinlock_t *l);
/* 1 if this core holds `l`. For paths that must not spin on a lock their
* own core may hold (the fatal exception printers). */
int sk_spin_held_here(const sk_spinlock_t *l);
/* Every switch and idle point: counts a violation if this core holds any
* lock. `where` names the site in the log. */
void sk_lock_check_none_held(const char *where);
/* Logs any failure counted since the last report. Call only with no lock
* held (the idle point does). */
void sk_lock_report(void);
uint64_t sk_lock_recursions(void);
uint64_t sk_lock_bad_releases(void);
uint64_t sk_lock_held_at_switch(void);
extern sk_spinlock_t g_lock_registry;
extern sk_spinlock_t g_lock_stadium;
extern sk_spinlock_t g_lock_switch;
extern sk_spinlock_t g_lock_hermes;
extern sk_spinlock_t g_lock_doe;
extern sk_spinlock_t g_lock_log;
extern sk_spinlock_t g_lock_block;
extern sk_spinlock_t g_lock_console;
extern sk_spinlock_t g_lock_heap;
extern sk_spinlock_t g_lock_pmm;
extern sk_spinlock_t g_lock_vmm;
#endif /* STARKERNEL_SPINLOCK_H */