FABRIC-3.10.8 MC-3a: the claim, the physics lock, per-core decision state
VM.running_on claimed by compare-and-swap on switch-in and released once the context is saved (release_after, sk_vm_switch_finish); a lost claim returns 1 and the caller re-decides; double claims counted. Per-core entry_vm and decision_due. g_lock_physics over capsule_vm_physics.c (rank between switch and registry). KILL refuses a VM running anywhere. Probe: 15737 claims, 0 lost, 0 double; a core-1 claim of Hera refused. 3-ISA acceptance with a 28-VM churn clean, 0 order violations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2e3b7ebe66
commit
4440faf352
@@ -1057,3 +1057,27 @@ The boot hart HSM chose (0) is the one OpenSBI names as `Boot HART ID`.
|
||||
**Evidence (named in advance):**
|
||||
- **Standard sequence on all three ISAs:** K-10 hashes, G byte-identical (one worker core), all ok `3`, 0 B idle growth, **0 lock-order violations** (so the new rank fits), 0 observer refusals.
|
||||
- **Probe** (temporary word, amd64): the claim counters after O-A are claims > 0, **lost claims 0, double claims 0**. Posing as core 1, a claim of Hera (held by core 0) **fails** and is counted as lost; with `core_id` back at 0, nothing else changed.
|
||||
|
||||
### 7.6 MC-3a — executed (2026-09-26)
|
||||
|
||||
**Change.**
|
||||
- **`VM.running_on`** (`vm.h`; zeroed by `vm_init`).
|
||||
- `sk_vm_try_claim()` claims by compare-and-swap. `sk_vm_switch_set_current()` claims Hera for core 0 at boot.
|
||||
- `sk_vm_context_switch()` claims `to` first and returns 1 if another core holds it. `note_switch_performed` moved inside it, after a successful claim, so a lost claim is not counted as a switch. The trampoline's call site re-decides on 1 (it used to panic on any non-zero return); the checkpoint and the idle point simply go on.
|
||||
- The switch sets `release_after = from`. `sk_vm_switch_finish()` clears `from`'s `running_on` once its context is saved, and checks the incoming VM is this core's (the double-claim count). It runs on the resume path of `sk_vm_context_switch()` and at the top of a first-entry trampoline.
|
||||
- **Per core:** `g_switch_entry_vm` became `sk_percpu_t.entry_vm`. Two cores priming first entries together would otherwise swap VMs. "Decision due" moved into `sk_percpu_t.decision_due`: `note_beat` marks this core, `note_mail` marks every core in the core table, and `take_decision_due` is an atomic exchange on this core.
|
||||
- The stamps and switch counters were already under `g_lock_switch` since MC-0b, so the plan's "atomic stamps" needed no change.
|
||||
- **`g_lock_physics`** (rank `physics`, between switch and registry). Every public function of `capsule_vm_physics.c` is now a wrapper over a static `*_locked` body. Internal calls (remove→retire, heartbeat_tick→tick, conserved/status→heat sum) go to the `_locked` forms. The file's "no locking" note is kept as history, headed by the new rule.
|
||||
- **`KILL`** (`capsule_vm_kill_refusal()`) also refuses a VM with `running_on` set: "it is running on another core".
|
||||
|
||||
**Probe** (temporary `MC3A-PROBE`, amd64, after O-A; `logs/20260926-143403`): `claims=15737 lost=0 double=0; as core 1, claim of Hera refused; after: claims=15737 lost=1 double=0; Hera running_on=1`. There were 0 order violations in that run.
|
||||
|
||||
**Standard sequence, with a 28-VM churn this time** (clean build, probe reverted; the tree is byte-identical to the saved stage diff). The churn exercises the physics lock through 28 retire-and-removes, and the new KILL rule through 28 kills.
|
||||
|
||||
| ISA | log | result |
|
||||
|---|---|---|
|
||||
| amd64 | `logs/20260926-144228` | K-10 hashes, G A=40 B=40 byte-identical, HHH, births 32 / kills 28, all ok `3`, ledger 319488/316703 (the K-11 churn baseline), 0 B idle, 0 / 0; **0 order violations**, 0 observer refusals, 0 park wakes, 0 `LOCK:` |
|
||||
| aarch64 | `logs/20260926-145937` | same |
|
||||
| riscv64 | `logs/20260926-151755` | same; 0 `timed out` |
|
||||
|
||||
**MC-3a is complete.** Next: MC-3b, each worker core's interrupt controller and the IPI.
|
||||
|
||||
@@ -67,6 +67,12 @@ typedef struct sk_percpu {
|
||||
uint64_t reg_readback; /* its per-CPU register, read on arrival */
|
||||
volatile uint32_t online; /* 1 once parked (release store) */
|
||||
volatile uint64_t park_wakes; /* times woken while parked */
|
||||
/* MC-3a (switch.c): the VM this core just switched away from, released
|
||||
* (running_on cleared) by whatever next runs here -- only once its
|
||||
* context is saved. And this core's "re-decide at the next checkpoint". */
|
||||
struct VM *release_after;
|
||||
volatile uint32_t decision_due;
|
||||
struct VM *entry_vm; /* the VM a first-entry trampoline starts as */
|
||||
} sk_percpu_t;
|
||||
|
||||
/* Installs the boot core's block in this core's per-CPU register. Call
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
* 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_physics fleet physics: node list, heat, touch window, slope
|
||||
* (capsule_vm_physics.c, MC-3a); retirement walks
|
||||
* the registry under it
|
||||
* 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)
|
||||
@@ -91,6 +94,7 @@ typedef enum sk_lock_rank {
|
||||
SK_LOCK_RANK_NONE = 0, /* unranked: neither checked nor counted */
|
||||
SK_LOCK_RANK_HERMES,
|
||||
SK_LOCK_RANK_SWITCH,
|
||||
SK_LOCK_RANK_PHYSICS, /* fleet physics (MC-3a): walks the registry */
|
||||
SK_LOCK_RANK_REGISTRY,
|
||||
SK_LOCK_RANK_VM_DICT, /* a VM's dict_lock (sf_mutex_t) */
|
||||
SK_LOCK_RANK_VM_TUNING, /* a VM's tuning_lock (sf_mutex_t) */
|
||||
@@ -141,6 +145,7 @@ uint64_t sk_lock_order_violations(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_physics;
|
||||
extern sk_spinlock_t g_lock_hermes;
|
||||
extern sk_spinlock_t g_lock_doe;
|
||||
extern sk_spinlock_t g_lock_log;
|
||||
|
||||
@@ -52,6 +52,15 @@ int sk_vm_context_switch(struct VM *from, struct VM *to);
|
||||
* have entered an observer on a core other than core 0 (a guard; 0). */
|
||||
uint64_t sk_vm_switch_observer_refusals(void);
|
||||
|
||||
/* FABRIC-3.10.8 MC-3a: the claim. sk_vm_try_claim() takes `vm` for this
|
||||
* core (running_on 0 -> core_id + 1) by compare-and-swap: 1 if taken, 0 if
|
||||
* another core holds it (counted as lost). sk_vm_context_switch() returns
|
||||
* 1 when that happens to `to` -- nothing switched; the caller re-decides.
|
||||
* The stats: claims taken, claims lost, and double claims (a switch-in that
|
||||
* found its VM held by another core -- must stay 0). */
|
||||
int sk_vm_try_claim(struct VM *vm);
|
||||
void sk_vm_switch_claim_stats(uint64_t *claims, uint64_t *lost, uint64_t *doubles);
|
||||
|
||||
/* FABRIC-3.md §XXVIII Stage 3 follow-on (2026-09-14): which VM is
|
||||
* currently physically running, tracked by the switch mechanism itself --
|
||||
* see switch.c's own doc comment on g_switch_current_vm for why this
|
||||
|
||||
@@ -684,6 +684,10 @@ typedef struct VM
|
||||
* entered (no context parked yet); non-zero only
|
||||
* while VMRegistryEntry.state ==
|
||||
* VM_STATE_SWITCHED_OUT for this VM. */
|
||||
volatile uint32_t running_on; /**< FABRIC-3.10.8 MC-3a: the core running this VM,
|
||||
* as core_id + 1 (a lock owner's encoding); 0 =
|
||||
* nobody. Claimed by compare-and-swap on switch-in,
|
||||
* released once its context is saved. */
|
||||
VMFrameStack frames; /**< FABRIC-3.8.md §3.12 (Stage FR, 2026-09-23):
|
||||
* this VM's execution frame stack -- the nested
|
||||
* levels running on its own native stack, each
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -544,6 +544,12 @@ const char *capsule_vm_kill_refusal(const VMRegistryEntry *entry) {
|
||||
if (entry->vm_ptr && entry->vm_ptr == (void *)sk_vm_switch_current_vm()) {
|
||||
return " refused -- it is the VM running";
|
||||
}
|
||||
/* FABRIC-3.10.8 MC-3a: nor can any other core's running VM be freed
|
||||
* under it (FABRIC-3.10.0 §3.6). running_on is 0 once it is parked. */
|
||||
if (entry->vm_ptr &&
|
||||
__atomic_load_n(&((VM *)entry->vm_ptr)->running_on, __ATOMIC_ACQUIRE) != 0) {
|
||||
return " refused -- it is running on another core";
|
||||
}
|
||||
return (const char *)0;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,11 @@
|
||||
* docs/working/architecture/VM-PHYSICS-DYNAMIC-FLEET-DESIGN-20260705.md
|
||||
* for the full design rationale.
|
||||
*
|
||||
* Concurrency: no locking. The only interrupt-driven kernel path
|
||||
* Concurrency (FABRIC-3.10.8 MC-3a): every public function now runs under
|
||||
* g_lock_physics -- see the wrappers at the end of this file. The paragraph
|
||||
* below is the one-core reasoning that held until then.
|
||||
*
|
||||
* Concurrency (before MC-3a): no locking. The only interrupt-driven kernel path
|
||||
* (heartbeat_tick, from the timer ISR) touches only its own isolated
|
||||
* TimeTrustState and never calls into mama_word_*, vm_interpret, or any
|
||||
* capsule/dictionary state. HEARTBEAT_THREAD_ENABLED=0 means no separate
|
||||
@@ -43,6 +47,7 @@
|
||||
#include "starkernel/vm/bootstrap/sk_vm_bootstrap.h" /* sk_get_mama_vm */
|
||||
#include "vm.h" /* VM, vm->ssm_l8_state */
|
||||
#include "ssm_jacquard.h" /* ssm_l8_state_t, CDConfigTable */
|
||||
#include "starkernel/spinlock.h" /* FABRIC-3.10.8 MC-3a -- g_lock_physics */
|
||||
|
||||
/* Word-level ROLLING_WINDOW_SIZE defaults to 4096 because word executions
|
||||
* number in the millions. Fleet touches are real VM-EXEC/VM-CALL/VM-STEP
|
||||
@@ -200,7 +205,7 @@ static void vm_physics_transfer(VMPhysics *from, VMPhysics *to, uint64_t amount_
|
||||
to->execution_heat_q48 += moved;
|
||||
}
|
||||
|
||||
void vm_physics_init(VMUuid vm_id)
|
||||
static void physics_init_locked(VMUuid vm_id)
|
||||
{
|
||||
vm_physics_node_t *node = vm_physics_find(vm_id);
|
||||
/* Hera (vm_uuid_hera()) is the fleet's single structural root -- the one
|
||||
@@ -267,7 +272,7 @@ static VMUuid vm_physics_find_root_id(VMUuid vm_id)
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void vm_physics_retire(VMUuid vm_id)
|
||||
static void physics_retire_locked(VMUuid vm_id)
|
||||
{
|
||||
vm_physics_node_t *dying = vm_physics_find(vm_id);
|
||||
vm_physics_node_t *root;
|
||||
@@ -300,14 +305,14 @@ void vm_physics_retire(VMUuid vm_id)
|
||||
dying->physics.is_live = 0;
|
||||
}
|
||||
|
||||
void vm_physics_remove(VMUuid vm_id)
|
||||
static void physics_remove_locked(VMUuid vm_id)
|
||||
{
|
||||
vm_physics_node_t **link = &vm_physics_head;
|
||||
vm_physics_node_t *n;
|
||||
|
||||
if (vm_uuid_is_hera(vm_id)) return; /* the root is never removed */
|
||||
|
||||
vm_physics_retire(vm_id); /* its heat to the root first; no-op if already retired */
|
||||
physics_retire_locked(vm_id); /* its heat to the root first; no-op if already retired */
|
||||
|
||||
while ((n = *link) != (void *)0) {
|
||||
if (vm_uuid_equal(n->vm_id, vm_id)) {
|
||||
@@ -319,7 +324,7 @@ void vm_physics_remove(VMUuid vm_id)
|
||||
}
|
||||
}
|
||||
|
||||
void vm_physics_touch(VMUuid vm_id)
|
||||
static void physics_touch_locked(VMUuid vm_id)
|
||||
{
|
||||
vm_physics_node_t *target = vm_physics_find(vm_id);
|
||||
VMFleetTouchSample sample = { 0, 0, 0 };
|
||||
@@ -400,7 +405,7 @@ void vm_physics_touch(VMUuid vm_id)
|
||||
* work rather than per dispatch, and with the sub-unit remainder carried
|
||||
* instead of truncated away -- touch's elapsed_ticks * slope >> 16 is 0 for
|
||||
* any single beat, which is why touch can't simply be called per beat. */
|
||||
void vm_physics_charge(VMUuid vm_id)
|
||||
static void physics_charge_locked(VMUuid vm_id)
|
||||
{
|
||||
vm_physics_node_t *target = vm_physics_find(vm_id);
|
||||
vm_physics_node_t *n;
|
||||
@@ -450,14 +455,14 @@ void vm_physics_charge(VMUuid vm_id)
|
||||
target->physics.charge_carry += (moved_total - moved) << 16;
|
||||
}
|
||||
|
||||
uint64_t vm_physics_work_heat_of(VMUuid vm_id)
|
||||
static uint64_t physics_work_heat_of_locked(VMUuid vm_id)
|
||||
{
|
||||
vm_physics_node_t *n = vm_physics_find(vm_id);
|
||||
if (!n || !n->physics.is_live) return 0;
|
||||
return n->physics.work_heat_q48;
|
||||
}
|
||||
|
||||
void vm_physics_work_heat_floor(VMUuid vm_id, uint64_t floor_q48)
|
||||
static void physics_work_heat_floor_locked(VMUuid vm_id, uint64_t floor_q48)
|
||||
{
|
||||
vm_physics_node_t *n = vm_physics_find(vm_id);
|
||||
if (!n || !n->physics.is_live) return;
|
||||
@@ -504,7 +509,7 @@ static uint32_t l8_regime_modulation_q16(void)
|
||||
return (popcount + 1u) * 32768u;
|
||||
}
|
||||
|
||||
void vm_physics_tick(uint64_t now_ns)
|
||||
static void physics_tick_locked(uint64_t now_ns)
|
||||
{
|
||||
uint64_t rates[VM_FLEET_WINDOW_DEPTH];
|
||||
uint32_t rate_count = 0;
|
||||
@@ -574,16 +579,16 @@ void vm_physics_tick(uint64_t now_ns)
|
||||
* from a VM's own heat-inference loop. Still one clock, one counter --
|
||||
* this is a bigger flat threshold on the same counter, not a second tick
|
||||
* source or anything that reads live VM count to adjust itself. */
|
||||
void vm_physics_heartbeat_tick(uint64_t now_ns)
|
||||
static void physics_heartbeat_tick_locked(uint64_t now_ns)
|
||||
{
|
||||
fleet_heartbeat_tick_count++;
|
||||
if ((fleet_heartbeat_tick_count - fleet_last_capacity_tick) >= STADIUM_CAPACITY_TICK) {
|
||||
vm_physics_tick(now_ns);
|
||||
physics_tick_locked(now_ns);
|
||||
fleet_last_capacity_tick = fleet_heartbeat_tick_count;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t vm_physics_fleet_heat_sum(void)
|
||||
static uint64_t physics_fleet_heat_sum_locked(void)
|
||||
{
|
||||
uint64_t total = 0;
|
||||
vm_physics_node_t *n;
|
||||
@@ -596,14 +601,14 @@ uint64_t vm_physics_fleet_heat_sum(void)
|
||||
return total;
|
||||
}
|
||||
|
||||
int vm_physics_conserved(void)
|
||||
static int physics_conserved_locked(void)
|
||||
{
|
||||
uint64_t sum = vm_physics_fleet_heat_sum();
|
||||
uint64_t sum = physics_fleet_heat_sum_locked();
|
||||
uint64_t diff = (sum > Q48_ONE) ? (sum - Q48_ONE) : (Q48_ONE - sum);
|
||||
return diff < VM_PHYSICS_EPSILON_Q48;
|
||||
}
|
||||
|
||||
uint64_t vm_physics_heat_of(VMUuid vm_id)
|
||||
static uint64_t physics_heat_of_locked(VMUuid vm_id)
|
||||
{
|
||||
vm_physics_node_t *n = vm_physics_find(vm_id);
|
||||
if (!n || !n->physics.is_live) return 0;
|
||||
@@ -629,16 +634,16 @@ static void console_put_u64(uint64_t val)
|
||||
console_puts(&buf[i + 1]);
|
||||
}
|
||||
|
||||
void vm_physics_status(void)
|
||||
static void physics_status_locked(void)
|
||||
{
|
||||
uint64_t sum = vm_physics_fleet_heat_sum();
|
||||
uint64_t sum = physics_fleet_heat_sum_locked();
|
||||
|
||||
console_puts("VM-PHYSICS: fleet_heat_sum=");
|
||||
console_put_u64(sum);
|
||||
console_println("");
|
||||
|
||||
console_puts("VM-PHYSICS: conserved=");
|
||||
console_println(vm_physics_conserved() ? "CONSERVED" : "DRIFTED");
|
||||
console_println(physics_conserved_locked() ? "CONSERVED" : "DRIFTED");
|
||||
|
||||
console_puts("VM-PHYSICS: fleet_transfer_slope_q48=");
|
||||
console_put_u64(fleet_transfer_slope_q48);
|
||||
@@ -678,3 +683,108 @@ void vm_physics_status(void)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- the public API: each call under g_lock_physics (FABRIC-3.10.8 MC-3a)
|
||||
* The "no locking" note at the top of this file described one core: with
|
||||
* worker cores, charges and touches from two cores would otherwise move
|
||||
* the same heat twice. Ranked after switch and before registry (spinlock.h):
|
||||
* retirement walks the registry under it; the registry never calls out. */
|
||||
|
||||
void vm_physics_init(VMUuid vm_id)
|
||||
{
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
physics_init_locked(vm_id);
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
}
|
||||
|
||||
void vm_physics_retire(VMUuid vm_id)
|
||||
{
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
physics_retire_locked(vm_id);
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
}
|
||||
|
||||
void vm_physics_remove(VMUuid vm_id)
|
||||
{
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
physics_remove_locked(vm_id);
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
}
|
||||
|
||||
void vm_physics_touch(VMUuid vm_id)
|
||||
{
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
physics_touch_locked(vm_id);
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
}
|
||||
|
||||
void vm_physics_charge(VMUuid vm_id)
|
||||
{
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
physics_charge_locked(vm_id);
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
}
|
||||
|
||||
uint64_t vm_physics_work_heat_of(VMUuid vm_id)
|
||||
{
|
||||
uint64_t v;
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
v = physics_work_heat_of_locked(vm_id);
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
return v;
|
||||
}
|
||||
|
||||
void vm_physics_work_heat_floor(VMUuid vm_id, uint64_t floor_q48)
|
||||
{
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
physics_work_heat_floor_locked(vm_id, floor_q48);
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
}
|
||||
|
||||
void vm_physics_tick(uint64_t now_ns)
|
||||
{
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
physics_tick_locked(now_ns);
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
}
|
||||
|
||||
void vm_physics_heartbeat_tick(uint64_t now_ns)
|
||||
{
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
physics_heartbeat_tick_locked(now_ns);
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
}
|
||||
|
||||
uint64_t vm_physics_fleet_heat_sum(void)
|
||||
{
|
||||
uint64_t v;
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
v = physics_fleet_heat_sum_locked();
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
return v;
|
||||
}
|
||||
|
||||
int vm_physics_conserved(void)
|
||||
{
|
||||
int v;
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
v = physics_conserved_locked();
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
return v;
|
||||
}
|
||||
|
||||
uint64_t vm_physics_heat_of(VMUuid vm_id)
|
||||
{
|
||||
uint64_t v;
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
v = physics_heat_of_locked(vm_id);
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
return v;
|
||||
}
|
||||
|
||||
void vm_physics_status(void)
|
||||
{
|
||||
sk_spin_lock(&g_lock_physics);
|
||||
physics_status_locked();
|
||||
sk_spin_unlock(&g_lock_physics);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "starkernel/repl.h" /* sk_console_key_available() -- Hera's mail (C-5) */
|
||||
#include "starkernel/spinlock.h" /* FABRIC-3.10.8 MC-0b -- g_lock_switch */
|
||||
#include "starkernel/percpu.h" /* FABRIC-3.10.8 MC-2 -- the deciding core */
|
||||
#include "starkernel/smp.h" /* FABRIC-3.10.8 MC-3a -- every core's decision flag */
|
||||
#include <stddef.h>
|
||||
|
||||
/* FABRIC-3.6.md task 3.1 (2026-09-21, ruled B2 / FABRIC-3.5.md §XLV.2):
|
||||
@@ -90,8 +91,9 @@ static uint64_t g_stamp_next = 1;
|
||||
/* Set when something physical changed that could change the decision --
|
||||
* the running VM produced a beat (and was just charged for it), or mail
|
||||
* was admitted -- and consumed at the next owned checkpoint. Between such
|
||||
* events every decision would repeat the last one (§3.13.3). */
|
||||
static int g_decision_due;
|
||||
* events every decision would repeat the last one (§3.13.3).
|
||||
* FABRIC-3.10.8 MC-3a: per core now (sk_percpu_t.decision_due) -- a beat
|
||||
* is its own core's news; admitted mail is every core's. */
|
||||
|
||||
/* DoE CSV exposure (FABRIC-3.md §XXVIII Stage 3 follow-on, 2026-09-13). */
|
||||
static uint64_t g_switch_count;
|
||||
@@ -249,23 +251,20 @@ void sk_vm_switch_signal_note_parked(VMUuid vm_id) {
|
||||
void sk_vm_switch_signal_note_beat(void) {
|
||||
sk_spin_lock(&g_lock_switch);
|
||||
g_beats_since_switch++;
|
||||
g_decision_due = 1;
|
||||
sk_spin_unlock(&g_lock_switch);
|
||||
__atomic_store_n(&sk_percpu()->decision_due, 1u, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
void sk_vm_switch_signal_note_mail(void) {
|
||||
sk_spin_lock(&g_lock_switch);
|
||||
g_decision_due = 1;
|
||||
sk_spin_unlock(&g_lock_switch);
|
||||
uint32_t i, n = sk_smp_core_count();
|
||||
for (i = 0; i < n; i++) {
|
||||
sk_percpu_t *pc = sk_smp_core(i);
|
||||
if (pc) __atomic_store_n(&pc->decision_due, 1u, __ATOMIC_RELEASE);
|
||||
}
|
||||
}
|
||||
|
||||
int sk_vm_switch_signal_take_decision_due(void) {
|
||||
int due;
|
||||
sk_spin_lock(&g_lock_switch);
|
||||
due = g_decision_due;
|
||||
g_decision_due = 0;
|
||||
sk_spin_unlock(&g_lock_switch);
|
||||
return due;
|
||||
return (int)__atomic_exchange_n(&sk_percpu()->decision_due, 0u, __ATOMIC_ACQ_REL);
|
||||
}
|
||||
|
||||
/* One candidate's physical standing: its heat, and how long it has been
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
sk_spinlock_t g_lock_registry = SK_SPINLOCK_INIT(SK_LOCK_RANK_REGISTRY);
|
||||
sk_spinlock_t g_lock_stadium = SK_SPINLOCK_INIT(SK_LOCK_RANK_STADIUM);
|
||||
sk_spinlock_t g_lock_switch = SK_SPINLOCK_INIT(SK_LOCK_RANK_SWITCH);
|
||||
sk_spinlock_t g_lock_physics = SK_SPINLOCK_INIT(SK_LOCK_RANK_PHYSICS);
|
||||
sk_spinlock_t g_lock_hermes = SK_SPINLOCK_INIT(SK_LOCK_RANK_HERMES);
|
||||
sk_spinlock_t g_lock_doe = SK_SPINLOCK_INIT(SK_LOCK_RANK_DOE);
|
||||
sk_spinlock_t g_lock_log = SK_SPINLOCK_INIT(SK_LOCK_RANK_LOG);
|
||||
@@ -221,6 +222,7 @@ static const char *lock_name(const volatile uint32_t *w)
|
||||
if (w == &g_lock_registry.owner) return "registry";
|
||||
if (w == &g_lock_stadium.owner) return "stadium";
|
||||
if (w == &g_lock_switch.owner) return "switch";
|
||||
if (w == &g_lock_physics.owner) return "physics";
|
||||
if (w == &g_lock_hermes.owner) return "hermes";
|
||||
if (w == &g_lock_doe.owner) return "doe";
|
||||
if (w == &g_lock_log.owner) return "log";
|
||||
@@ -235,7 +237,7 @@ static const char *lock_name(const volatile uint32_t *w)
|
||||
static const char *rank_name(uint32_t rank)
|
||||
{
|
||||
static const char *const names[SK_LOCK_RANK_COUNT] = {
|
||||
"unranked", "hermes", "switch", "registry", "vm dict_lock",
|
||||
"unranked", "hermes", "switch", "physics", "registry", "vm dict_lock",
|
||||
"vm tuning_lock", "stadium", "Stadium slot", "doe", "log", "block",
|
||||
"console", "heap", "vmm", "pmm"
|
||||
};
|
||||
|
||||
+61
-13
@@ -58,7 +58,9 @@ extern uint64_t sk_vm_switch_prime(uint64_t stack_top, void *entry);
|
||||
* yielding to "whoever switched in"): a VM's identity
|
||||
* never changes across the lifetime of its own trampoline instance, only
|
||||
* who most recently switched into it does. */
|
||||
static VM *g_switch_entry_vm;
|
||||
/* FABRIC-3.10.8 MC-3a: the global g_switch_entry_vm described above is now
|
||||
* per core (sk_percpu_t.entry_vm): two cores priming first entries at the
|
||||
* same moment would otherwise hand each other's VM to the trampoline. */
|
||||
|
||||
/* FABRIC-3.10.8 MC-2: switches refused because they would have entered an
|
||||
* observer on a core other than core 0. A guard -- expected to stay 0. */
|
||||
@@ -85,7 +87,46 @@ static uint64_t g_observer_refusals, g_observer_refusals_reported;
|
||||
|
||||
VM *sk_vm_switch_current_vm(void) { return sk_percpu()->current_vm; }
|
||||
|
||||
void sk_vm_switch_set_current(VM *vm) { sk_percpu()->current_vm = vm; }
|
||||
/* FABRIC-3.10.8 MC-3a: the claim. A VM runs on at most one core:
|
||||
* running_on goes from 0 to this core's id + 1 by compare-and-swap on
|
||||
* switch-in, and back to 0 only once the VM's context is saved. */
|
||||
static uint64_t g_claims, g_claims_lost, g_double_claims;
|
||||
|
||||
int sk_vm_try_claim(VM *vm) {
|
||||
uint32_t expected = 0;
|
||||
uint32_t me = sk_percpu()->core_id + 1u;
|
||||
if (__atomic_compare_exchange_n(&vm->running_on, &expected, me, 0,
|
||||
__ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
|
||||
__atomic_add_fetch(&g_claims, 1, __ATOMIC_RELAXED);
|
||||
return 1;
|
||||
}
|
||||
__atomic_add_fetch(&g_claims_lost, 1, __ATOMIC_RELAXED);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sk_vm_switch_claim_stats(uint64_t *claims, uint64_t *lost, uint64_t *doubles) {
|
||||
if (claims) *claims = g_claims;
|
||||
if (lost) *lost = g_claims_lost;
|
||||
if (doubles) *doubles = g_double_claims;
|
||||
}
|
||||
|
||||
/* Whatever runs next on this core after a switch: release the VM it left
|
||||
* (its context is saved by now), and check the one now running is ours. */
|
||||
static void sk_vm_switch_finish(VM *now_running) {
|
||||
sk_percpu_t *pc = sk_percpu();
|
||||
VM *r = pc->release_after;
|
||||
pc->release_after = (VM *)0;
|
||||
if (r) __atomic_store_n(&r->running_on, 0u, __ATOMIC_RELEASE);
|
||||
if (now_running &&
|
||||
__atomic_load_n(&now_running->running_on, __ATOMIC_ACQUIRE) != pc->core_id + 1u) {
|
||||
__atomic_add_fetch(&g_double_claims, 1, __ATOMIC_RELAXED);
|
||||
}
|
||||
}
|
||||
|
||||
void sk_vm_switch_set_current(VM *vm) {
|
||||
sk_percpu()->current_vm = vm;
|
||||
if (vm) __atomic_store_n(&vm->running_on, sk_percpu()->core_id + 1u, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
/**
|
||||
* sk_vm_switch_entry - trampoline for a VM's first-ever entry.
|
||||
@@ -155,7 +196,8 @@ void sk_vm_switch_set_current(VM *vm) { sk_percpu()->current_vm = vm; }
|
||||
* Hera is never waiting on a trampoline, so the chain always ends at her.
|
||||
*/
|
||||
void sk_vm_switch_entry(void) {
|
||||
VM *self = g_switch_entry_vm;
|
||||
VM *self = sk_percpu()->entry_vm; /* MC-3a: per core, set just before this entry */
|
||||
sk_vm_switch_finish(self); /* MC-3a: first entry -- release whoever switched here */
|
||||
for (;;) {
|
||||
VM *next;
|
||||
int drained, has_mail;
|
||||
@@ -200,11 +242,13 @@ void sk_vm_switch_entry(void) {
|
||||
if (idle_id == 0) {
|
||||
sk_hal_panic("switch trampoline: idle frame push failed");
|
||||
}
|
||||
sk_vm_switch_signal_note_switch_performed(next->stadium_vm_id);
|
||||
if (sk_vm_context_switch(self, next) != 0) {
|
||||
sk_hal_panic("switch trampoline: switch failed");
|
||||
{
|
||||
int rc = sk_vm_context_switch(self, next);
|
||||
if (rc < 0) sk_hal_panic("switch trampoline: switch failed");
|
||||
/* rc 1: another core claimed `next` first -- decide again.
|
||||
* rc 0: resumed, someone switched back in. */
|
||||
}
|
||||
vm_frame_pop(&self->frames, idle_id); /* resumed: someone switched back in */
|
||||
vm_frame_pop(&self->frames, idle_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,9 +262,9 @@ void sk_vm_switch_checkpoint(VM *vm) {
|
||||
sk_vm_switch_reap_pending();
|
||||
next = sk_vm_switch_decide(vm, 1); /* vm is mid-task: it still has work */
|
||||
if (!next) return;
|
||||
sk_vm_switch_signal_note_switch_performed(next->stadium_vm_id);
|
||||
(void)sk_vm_context_switch(vm, next);
|
||||
/* Resumes here once the decision, somewhere, switches back to vm. */
|
||||
/* Resumes here once the decision, somewhere, switches back to vm --
|
||||
* or at once, if another core claimed `next` first (MC-3a). */
|
||||
}
|
||||
|
||||
/* See switch.h. FABRIC-3.8.md §3.13.5 (ruling C-5): Hera between lines,
|
||||
@@ -264,8 +308,7 @@ void sk_vm_idle_point(int may_drain_self) {
|
||||
{
|
||||
VMFrameID idle_id = vm_frame_push(&self->frames, self->stadium_vm_id, VM_FRAME_IDLE);
|
||||
if (idle_id == 0) return; /* fail closed: stay put rather than park unmarked */
|
||||
sk_vm_switch_signal_note_switch_performed(next->stadium_vm_id);
|
||||
(void)sk_vm_context_switch(self, next);
|
||||
(void)sk_vm_context_switch(self, next); /* 1 = claimed elsewhere: the poll loop calls again */
|
||||
vm_frame_pop(&self->frames, idle_id);
|
||||
}
|
||||
}
|
||||
@@ -290,10 +333,13 @@ int sk_vm_context_switch(VM *from, VM *to) {
|
||||
__atomic_add_fetch(&g_observer_refusals, 1, __ATOMIC_RELAXED);
|
||||
return -1;
|
||||
}
|
||||
/* MC-3a: take `to` for this core, or learn another core has it. */
|
||||
if (!sk_vm_try_claim(to)) return 1;
|
||||
sk_vm_switch_signal_note_switch_performed(to->stadium_vm_id);
|
||||
|
||||
if (to->native_stack_saved_sp == 0) {
|
||||
/* First-ever entry: synthesize the initial frame. */
|
||||
g_switch_entry_vm = to;
|
||||
sk_percpu()->entry_vm = to;
|
||||
new_sp = sk_vm_switch_prime(to->native_stack_top, (void *)&sk_vm_switch_entry);
|
||||
} else {
|
||||
new_sp = to->native_stack_saved_sp;
|
||||
@@ -303,7 +349,8 @@ int sk_vm_context_switch(VM *from, VM *to) {
|
||||
capsule_vm_set_state(to->stadium_vm_id, VM_STATE_LIVE);
|
||||
capsule_vm_set_state(from->stadium_vm_id, VM_STATE_SWITCHED_OUT);
|
||||
sk_vm_switch_signal_note_parked(from->stadium_vm_id); /* §3.13.3 wait stamp */
|
||||
sk_percpu()->current_vm = to;
|
||||
sk_percpu()->current_vm = to;
|
||||
sk_percpu()->release_after = from; /* released once its context is saved */
|
||||
|
||||
/* FABRIC-3.10.8 MC-0b: no lock crosses a switch (spinlock.h). */
|
||||
sk_lock_check_none_held("context switch, leaving");
|
||||
@@ -314,6 +361,7 @@ int sk_vm_context_switch(VM *from, VM *to) {
|
||||
* from `from`'s own point of view, this call simply took a while.
|
||||
* sk_percpu() is read fresh here, not cached from before the switch:
|
||||
* once workers migrate (MC-3), `from` may resume on a different core. */
|
||||
sk_vm_switch_finish(from); /* MC-3a: release whoever switched back to us */
|
||||
capsule_vm_set_state(from->stadium_vm_id, VM_STATE_LIVE);
|
||||
sk_percpu()->current_vm = from;
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user