- Captain Bob, 2026-09-25: "pin observr and dynmically utilize worker VM's"; MC-R1..R5 as proposed; MC-R6 changed to start now; O-D relaunches with row path A (serial-only DoE rows). The amd64 O-D launch was stopped after 81 min / 131,537 rows for this. - FABRIC-3.10.8: the traced inventory of shared state (kernel subsystems and the shared VM code -- notably one global dictionary lookup index for all VMs), and stages MC-0a..MC-4 with evidence named in advance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
267 lines
18 KiB
Markdown
267 lines
18 KiB
Markdown
# FABRIC-3.10.0.md — Multi-core: observers pinned, workers claimed by the physics
|
||
|
||
**Status: RULED 2026-09-25 (§7); execution in `FABRIC-3.10.8.md`.** Multi-core starts now, ahead
|
||
of O-D (MC-R6 as ruled). Part II (§8) records the OS's other open gaps as of this writing.
|
||
|
||
**Provenance.** Opened 2026-09-25, while O-D (the DoE relaunch) was running on amd64. Captain Bob,
|
||
2026-09-25:
|
||
- *"I'm interested im the idea of pinning hera, hesta and artimes to cpu cores whre possibl."*
|
||
- On placing workers on cores at birth: *"Not sure, static allocation of worker vms seems anti
|
||
starshipos given there isn't a fied number of vm's. investigate how to pin observers and allow
|
||
multithreading across cores forload ballancing."*
|
||
|
||
**Static placement of workers is therefore ruled out.** A worker is never assigned a core. It
|
||
runs wherever the physics next chooses it.
|
||
|
||
---
|
||
|
||
## 1. The idea
|
||
|
||
- **Observers are pinned.** The Tripod (Hera, Hestia, Artemis) are a fixed, known set, already
|
||
outside the compudynamic comparison (`FABRIC-3.9.0.md` §2). They get affinity to a **system
|
||
core**, core 0, where there is more than one core, and share the one core exactly as today where
|
||
there isn't.
|
||
- **Workers are placed nowhere.** Every other core is a worker core. At each clean boundary, a
|
||
worker core asks the existing compudynamic decision for the coolest participant with work that
|
||
no other core is running, and claims it atomically. A worker runs wherever it is next chosen. It
|
||
migrates for free, always at a clean boundary, and the load balances through the heat itself: a
|
||
hot worker is not the coolest, so free cores take the cooler ones.
|
||
- **No scheduler, no fixed count.** Each core runs the same physical question the single core
|
||
runs today. Nothing counts VMs or cores in advance, so the design holds for any number of VMs on
|
||
any number of cores.
|
||
|
||
A worker VM stays single-threaded: one interpreter, one data stack, one native stack, one core
|
||
at a time. Parallelism comes from many workers running at once. Splitting one VM across cores
|
||
would need a multi-threaded interpreter sharing one dictionary and one set of stacks, which is a
|
||
different machine. A job that wants more parallelism is split into more workers, as the DoE
|
||
already does.
|
||
|
||
---
|
||
|
||
## 2. What exists today (traced 2026-09-25)
|
||
|
||
| Area | Today |
|
||
|---|---|
|
||
| QEMU | No `-smp` on any ISA (`Makefile.starkernel`): every run is one CPU. |
|
||
| Secondary core start | None on any ISA: no INIT-SIPI (amd64), no PSCI `CPU_ON` (aarch64), no SBI HSM `hart_start` (riscv64). `arch/riscv64/interrupts.c` says "the current single-hart kernel". |
|
||
| Groundwork to reuse | amd64 already parses the ACPI MADT (`arch/amd64/ioapic.c`, for the I/O APIC); its LAPIC entries (type 0) list the cores. aarch64 already discovers the PSCI conduit from the DTB (`aarch64_psci_conduit_init()`, for reset); `CPU_ON` goes through the same conduit. All three have halt primitives (`hlt`/`wfi`, `arch_halt()`). |
|
||
| Locks | `sf_mutex_*` are deliberate no-ops in the kernel (`vm/host/shim.c`: "single-threaded kernel"). The shared VM code already takes them at the right places (`dict_lock`, `tuning_lock`), so they can become real spinlocks without touching callers. |
|
||
| Atomics | Two helpers in `vm/vm_runtime.c` (`__atomic_load_n`/`__atomic_store_n`). |
|
||
| Global mutable state | Written as "single-writer mainline" throughout: kernel-Hermes (arena, queues, channels, ledger), Stadium (reservoirs, cells, quotas), `kmalloc`'s free list, the VM registry, the switch-signal table, console output, the log region, the block subsystem. |
|
||
| Per-core state | None. `g_switch_current_vm` (`vm/switch.c`) is one global "who is running". The heartbeat is one global beat, driven by the boot CPU's timer ISR. |
|
||
| Stacks | Each VM has its own native stack; a parked VM holds only its trampoline context (Stage D `sk_vm_parked_idle()`). Hera runs on the boot stack. |
|
||
|
||
---
|
||
|
||
## 3. Design
|
||
|
||
### 3.1 Observers on the system core
|
||
|
||
- **Affinity is a role fact, set at birth**, from the same `is_fleet_foundation` fact that sets
|
||
the observer flag (O-A). Nothing a VM executes can change it.
|
||
- **Core 0 runs the Tripod** under exactly today's rules: the O-A tiers, the O-B idle-point drain,
|
||
the console, USB polling (`sk_repl_idle()`), and Artemis's storage.
|
||
- **Worker cores never claim an observer.** Hera never runs on a worker core.
|
||
- **One core means today's behaviour:** core 0 is also the only worker core, and every
|
||
participant runs there as now.
|
||
|
||
### 3.2 Workers: claimed through the shared decision
|
||
|
||
- The switch-signal slot gains **`running_on`** (core index, or none).
|
||
- At every clean boundary (the trampoline's between-messages point, the in-word checkpoint, a
|
||
worker core's idle loop), a worker core runs `sk_vm_switch_decide()` over participants with
|
||
`running_on` = none or = this core. It then claims the winner with **one atomic compare-and-swap
|
||
on `running_on`**. If it loses the race, it decides again.
|
||
- The claim is **released** when the worker parks (IDLE frame) or switches out.
|
||
- **Tie-break:** among equally cool candidates, prefer the one that last ran on this core (cache
|
||
affinity), then the existing incumbent rule, then the oldest wait stamp.
|
||
- **Migration is implicit.** A parked worker resumes on whichever core claims it next. Its stack
|
||
holds nothing tied to a core; per-core values live behind the per-core pointer (§3.5), never on
|
||
a VM's stack.
|
||
- **"Nobody has work" changes on worker cores.** Today a trampoline with no work switches to
|
||
Hera. On a worker core it switches to that **core's own idle context**, a small per-core stack
|
||
that halts (§3.3). Hera remains the idle root on core 0 only.
|
||
|
||
### 3.3 Idle and wake
|
||
|
||
- **An idle worker core halts** (`hlt`/`wfi`). Nothing polls.
|
||
- **Wake by inter-processor interrupt.** When `sk_hermes_send_one()` admits mail, or an event
|
||
(M-B), for a participant that no core is running, it wakes one halted worker core. That core's
|
||
decision picks the work up.
|
||
- **Core 0 wakes on its own inputs as now:** its timer, console input, and USB polling at its
|
||
idle point.
|
||
|
||
### 3.4 Physics across cores
|
||
|
||
- **Each core has its own timer** (the LAPIC timer on amd64, the generic timer on aarch64, the
|
||
SBI timer on riscv64) and charges beats to the VM it is running. Beats stay execution-paced per
|
||
VM, as today.
|
||
- **Conservation stays exact.** Stadium reservoirs, `consumed` and kernel-Hermes's
|
||
held/pulled/returned/consumed become atomic, or per-core accumulators merged at audit time. The
|
||
integer audits (`stadium_conserved()`, `sk_hermes_audit()`) must hold exactly, as today.
|
||
- **Observers stay uncharged** (O-2).
|
||
|
||
### 3.5 Per-core state
|
||
|
||
- **One per-core block per core**, reached through each ISA's per-CPU register: GS base on amd64,
|
||
`TPIDR_EL1` on aarch64, `tp` on riscv64. The kernel owns `tp`; there is no user mode.
|
||
- **It holds:** the current VM (replacing the global `g_switch_current_vm`), the core's idle
|
||
context, the running frame stack, local beat counters, and the "decision due" flag.
|
||
|
||
### 3.6 What has to be made safe
|
||
|
||
These are the shared structures and what each needs (`FABRIC-3.10.8.md` will inventory them line
|
||
by line):
|
||
|
||
| Structure | Needs |
|
||
|---|---|
|
||
| `sf_mutex_*` | Real spinlocks (with interrupts masked while held on the owning core). |
|
||
| `kmalloc` | A heap lock. |
|
||
| kernel-Hermes (arena, queues, channels, ledger, events) | A lock (one to start; per-queue later if contention shows). |
|
||
| Stadium (reservoirs, cells, quotas, word rows) | **Not one lock — the per-dispatch path must not contend** (see below). |
|
||
| VM registry, birth, kill | A registry lock. `KILL` of a VM whose `running_on` is another core is refused, the multi-core form of today's "it is the VM running" refusal. |
|
||
| Switch-signal table | Compare-and-swap for claim and release; a lock for register and unregister. |
|
||
| Console output | A line lock, so lines from different cores never interleave mid-line. |
|
||
| Log region, block subsystem | A lock each. Artemis's storage stays on core 0, but workers can use `BLOCK` words. |
|
||
|
||
**The Stadium hot path decides whether multi-core speeds anything up** (added 2026-09-25, after
|
||
Captain Bob asked whether this should give a significant performance boost).
|
||
- `stadium_word_dispatch()` runs on **every word dispatch** of every VM (`vm_core.c`), for word
|
||
heat: resolve the word's cell, cool it, pull admission heat from the VM's reservoir, and admit
|
||
or evict a cell.
|
||
- Behind a single Stadium lock, every worker core would contend on every word, and the speedup
|
||
would collapse towards 1×. `kmalloc` and kernel-Hermes are touched far less often (on
|
||
definitions and messages), so one lock each is fine there.
|
||
- **Requirement: Stadium's per-dispatch state is partitioned by the VM's quota slot,** which is
|
||
already how the word rows are keyed (`word_slots[slot][word_id]`, S-1b).
|
||
- A VM runs on one core at a time, so its own row and cells need no lock while it runs.
|
||
- Its reservoir takes atomic arithmetic.
|
||
- Only operations that cross slots (admission evicting another VM's cell, quota grants,
|
||
conservation audits) take the Stadium lock.
|
||
- This is part of MC-0, and MC-0's evidence includes it: a per-dispatch path that takes no
|
||
shared lock.
|
||
|
||
### 3.7 Bringing up the cores, per ISA
|
||
|
||
- **amd64:** enumerate cores from the MADT's LAPIC entries. Start each with INIT-SIPI-SIPI through
|
||
the LAPIC ICR, into a trampoline copied below 1 MiB (real mode → long mode). The trampoline must
|
||
be position-independent: the kernel is relocated by a whole number of pages plus 0x440,
|
||
differently on every boot (found at O-C).
|
||
- **aarch64:** enumerate cores from the DTB's `/cpus`. Start each with PSCI `CPU_ON` through the
|
||
already-discovered conduit (HVC on QEMU virt). Each core initialises its GIC CPU interface or
|
||
redistributor and its generic timer.
|
||
- **riscv64:** enumerate harts from the DTB. Start each with SBI HSM `hart_start`. Each hart
|
||
arms its own SBI timer. IPIs go through the SBI IPI extension, or ACLINT where present.
|
||
|
||
---
|
||
|
||
## 4. Consequences
|
||
|
||
- **Execution order stops being deterministic** once there is more than one worker core.
|
||
- Byte-identical evidence such as the Stage G interleave string gives way to count evidence
|
||
(A = 40, B = 40).
|
||
- The `dict_hash` set and the integer conservation audits stay exact.
|
||
- **Cores become a DoE factor.** A core count is fixed per boot (`-smp N`), so it is a factor
|
||
across boots. Measuring what compudynamics does with 1 core versus N is probably the most
|
||
interesting result this makes possible.
|
||
- **QEMU:** each virtual CPU is a host thread. Host-side contention shows up in the timing the
|
||
DoE measures, so the one-QEMU-at-a-time rule matters more, not less.
|
||
- **Hardware:** SER5 (8 cores) and the Raspberry Pi 5 (4 cores) are the real targets this opens.
|
||
|
||
---
|
||
|
||
## 5. Staging (proposed)
|
||
|
||
Each step keeps the system shippable and gets its own 3-ISA acceptance.
|
||
|
||
| Stage | Change | Evidence (to be named in full in 3.10.8) |
|
||
|---|---|---|
|
||
| **MC-0** | Per-core infrastructure on one core: the per-CPU pointer and block; real spinlocks behind `sf_mutex`; the §3.6 locking applied, including Stadium's per-dispatch path partitioned by quota slot. | Standard sequence unchanged on one core. A probe shows every lock taken and released, never held across a switch, and **no shared lock taken on the word-dispatch path**. |
|
||
| **MC-1** | Bring up the extra cores and park them halted; QEMU `-smp 2`. | The boot reports N cores online; the standard sequence is unchanged, with the extra cores idle. |
|
||
| **MC-2** | Observers get system-core affinity (core 0). | With one worker core, behaviour identical to today; observers are never claimed off core 0. |
|
||
| **MC-3** | Worker cores claim through the shared decision, with per-core idle contexts and wake by interrupt. | Two workers genuinely overlapping in time on two cores. A probe proves the claim never lets two cores run one worker. Conservation holds throughout. G by counts. Idle cores halted, not spinning (0 idle log growth). |
|
||
| **MC-4** | DoE with cores as a factor. | A per-ISA campaign at each ruled core count. |
|
||
|
||
---
|
||
|
||
## 6. Known limits and open questions
|
||
|
||
- **Worker cores and devices:** only core 0 services USB. A worker core never touches xHCI.
|
||
- **Locking granularity:** one lock per subsystem to start (MC-R2) may contend under many
|
||
workers. It is measured before it is refined. Stadium's per-dispatch path is the exception,
|
||
partitioned from the start (§3.6).
|
||
- **Expected gain.** Close to linear throughput for many independent compute-bound workers on
|
||
real hardware, once the dispatch path is partitioned. None for a single worker (a VM is
|
||
single-threaded). Little for anything bound by serial output, the current DoE's limit: every
|
||
core's heartbeat rows funnel through one console. MC-3/MC-4 measure it: throughput for a fixed
|
||
batch of workers at 1, 2 and 4 cores.
|
||
- **Interrupt routing:** device IRQs stay on core 0 (the I/O APIC, GIC SPIs and PLIC contexts
|
||
route there). Only inter-processor wakes reach worker cores.
|
||
|
||
---
|
||
|
||
## 7. Rulings needed from Captain Bob
|
||
|
||
1. **MC-R1: the system core.** All three observers together on core 0 (proposed), or a core
|
||
each where cores allow.
|
||
2. **MC-R2: lock granularity to start.** One lock per subsystem (proposed), or a single kernel
|
||
lock first, or fine-grained from the start.
|
||
3. **MC-R3: tie-break affinity.** Prefer the worker's last core among equally cool candidates
|
||
(proposed), or no affinity.
|
||
4. **MC-R4: evidence under concurrency.** Count-based checks replace byte-identical interleave
|
||
strings once there is more than one worker core (proposed).
|
||
5. **MC-R5: DoE core counts.** Which counts per ISA (proposed: 1, 2, 4).
|
||
6. **MC-R6: timing.** Start after O-D and the merge (proposed).
|
||
|
||
**RULED 2026-09-25, Captain Bob.** First, redirecting from the DoE row-path question: *"pin
|
||
observr and dynmically utilize worker VM's."* Then, confirming:
|
||
- MC-R1 to MC-R5 go **as proposed**: all three observers on core 0; one lock per subsystem, with
|
||
Stadium's dispatch path partitioned; the last-core tie-break; count-based evidence; DoE at 1, 2
|
||
and 4 cores.
|
||
- **MC-R6 is changed:** multi-core starts **now, ahead of O-D**. O-D waits until it lands.
|
||
- For O-D's relaunch, the row path is **A**: DoE rows go to serial only, skipping the
|
||
framebuffer, whose full-screen scroll on every row was the rows-on bottleneck. The 2026-09-25
|
||
amd64 launch (`logs/20260925-112529/amd64/`) was stopped for this after 81 minutes and 131,537
|
||
rows, with no trial yet complete.
|
||
|
||
---
|
||
|
||
## 8. Part II — the OS's other open gaps (2026-09-25)
|
||
|
||
**Ruled out by Captain Bob, 2026-09-25:**
|
||
- **A filesystem: never.** *"Filesystem? NEVER! anti POSIX!"* Storage is FORTH blocks, Artemis's
|
||
disk, content-addressed capsules and identity thumbdrives. That is the model, not a gap.
|
||
- **Networking: deferred.** *"Not iterested in networking yet."* There is no virtio-net and no
|
||
stack today; it is not scheduled.
|
||
|
||
**Open, in proposed order:**
|
||
|
||
| # | Gap | State |
|
||
|---|---|---|
|
||
| 1 | **O-D and the merge.** The DoE relaunch on all three ISAs, then this branch to `master`. | amd64 launched 2026-09-25 11:29:29 (`logs/20260925-112529/amd64/`), about 2.5–3 days, bound by serial row output. |
|
||
| 2 | **Multi-core** (this document). | Designed; rulings needed (§7). |
|
||
| 3 | **Interrupt-driven devices** (O-4 option (b)). USB is polled at Hera's idle point. | Deferred at O-4; the system core (MC-2) eases it. |
|
||
| 4 | **The rest of the fixed-size audit** (`FABRIC-3.9.0.md` §8). S-1 and M-0 are done. Still fixed: WIREBIND identities attached at once (13), block relocations (500), executable regions (8), the run log (1024), the kernel RAM disk, control-flow nesting (64), and the RAM-sized VM/queue/channel tables (class D′). | S-3 order ruled; **S-2** (Stadium's cell pool) still needs a ruling. The WIREBIND cap matters most for multi-user. |
|
||
| 5 | **Security policy.** The ACL mechanism works, but `ACL-RECHECK` allows every STRICT word; `INJECT`'s gate is in place with no policy behind it. ACL Phase 8 (PKI challenge-response) is marked open, though MINT/WIREBIND covers part of it. | Policy to write in `ACL.4th`. |
|
||
| 6 | **Real hardware.** SER5 and Raspberry Pi 5 tracks exist; nothing is verified on hardware. `LITHOS_VERSION` stays 2.0.0 until it is. | Unstarted this line. |
|
||
| 7 | **Milestones M8 (REPL keyboard input) and M9 (block storage).** Listed as planned; completion not independently verified. | To verify. |
|
||
| 8 | **Failure counters readable from FORTH.** §8.1 asks for this. S-1a/S-1b/M-0 expose C accessors only; `EV-DROPPED` is the one FORTH counter so far (its name unruled). | Ruling needed: adding words changes the `dict_hash` set. |
|
||
|
||
**Known defects, reported and not fixed** (from `FABRIC-3.9.8.md` §2 unless noted):
|
||
|
||
1. `COLD` rewinds `HERE` to 1024 without rewinding `latest`: live words get compiled over (S-1c).
|
||
2. `PAD` returns `VM_MEMORY_SIZE - 512`, inside the top block I/O window slot (S-1c).
|
||
3. riscv64 intermittent `virtio_blk: vblk_io timed out` on disk writes under emulation (M-A).
|
||
4. Two boot self-tests silently `SKIPPED`: drain, and channel-open policy. Both still wait for the
|
||
removed Hermes VM (M-0).
|
||
5. `dictionary_heat_optimization.c`: the heat snapshot is `heats[1024]`, silently truncated (S-1b).
|
||
6. `vm_create_word()`: a failed transition-metrics allocation still defines the word (S-1b).
|
||
7. `vm_cleanup()` frees neither `transition_heat` nor `context_window` inside each word's metrics
|
||
(S-1b).
|
||
8. `transition_metrics_update_cache()` scans the whole id space on every recorded transition
|
||
(S-1b).
|
||
9. The kernel's FC-bucket grow never frees the old block after `sf_realloc()` (S-1b).
|
||
10. **New, found at the xHCI fix:** `xhci_cmd_configure_endpoint()` allocates a new Input Context
|
||
on every configure, and never frees the old one. It is a per-attach leak, but harmless to the
|
||
controller, which reads the Input Context only during the command.
|