Files
LithosAnanake/FABRIC-3.10.8.md
T
Robert Allan JamesandClaude Opus 5.5 8409e376cf Stage MC-0a: per-core block on one core -- current VM moves behind the per-CPU register (FABRIC-3.10.8 §3.1)
- New sk_percpu_t { self, core_id, current_vm }, reached via GS base
  (amd64, %gs:0), TPIDR_EL1 (aarch64), tp (riscv64). Boot core's block is
  one static object installed right after arch_early_init(); secondary
  cores' blocks come with MC-1.
- switch.c's global g_switch_current_vm is gone; current VM is
  sk_percpu()->current_vm, re-read after resume in the context switch.
- Probe (removed): normal path and every heartbeat ISR found core 0's
  block on all three ISAs (ISR ok 889/1877/1625, bad 0).
- Standard sequence on amd64/aarch64/riscv64: hashes unchanged from O-C,
  G A=40 B=40, MINT/WIREBIND/USE ok, conservation true, 0 idle growth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:56:48 -04:00

8.9 KiB

FABRIC-3.10.8.md — Multi-core: execution plan and log

Status: IN PROGRESS. Design and rulings are in FABRIC-3.10.0.md (ruled 2026-09-25, §7). Multi-core runs ahead of O-D (MC-R6 as ruled); O-D relaunches afterwards with row path A.

Each stage:

  • needs Captain Bob's explicit go;
  • gets its own commit;
  • gets its own three-architecture acceptance (clean qemu; amd64, then aarch64, then riscv64; foreground; visible framebuffer; one at a time; coupled images reset before each).

Evidence is named before each stage runs.

The standing acceptance sequence, unchanged from FABRIC-3.9.8.md:

  • a boot to [zuse@Hera] ok> with zero UNKNOWN WORD, faults and frame corruption;
  • the dict_hash set identical across ISAs;
  • the Stage G interleave (A/B), byte-identical across ISAs while there is one worker core; by counts (A = 40, B = 40) once there are more (MC-R4);
  • the O-A checks (observer precedence, console responsive under load);
  • mint → WIREBIND → USE → 1 2 + . → 3;
  • 0 bytes of idle log growth over 20 s.

No DoE execution until everything is closed (Captain Bob, 2026-09-25: "until we close everything, do not do ant DoE eecution."). MC-4's campaigns wait for that too. Stage evidence is the standard sequence plus probes, never a campaign.

Never make clean an ISA while its QEMU runs: it deletes that QEMU's QMP and serial sockets (found 2026-09-25).

1. What MC-0 must cover (traced 2026-09-25)

Everything that would break if two cores ran kernel or VM code at the same moment.

Kernel subsystems (src/starkernel/), one lock each (MC-R2), except where noted:

Subsystem Shared state Treatment
kmalloc, pmm, vmm free lists, bitmaps, page tables heap lock; PMM lock; VMM lock
kernel-Hermes arena directory and segments, queue table, channel table, ledger, event counters one lock
Stadium reservoirs, cells, quotas, word rows per-dispatch path partitioned by quota slot (a VM's own row and cells need no lock while it runs on its one core); reservoir arithmetic atomic; cross-slot operations (admission evicting another VM's cell, grants, audits) take the Stadium lock
VM registry, birth, kill, reap registry list, names registry lock; KILL refuses a VM running on another core
switch-signal table slots, stamps, decision-due flag compare-and-swap for claim/release (MC-3); lock for register/unregister
current VM, frame stack g_switch_current_vm and friends move to the per-core block
console UART, framebuffer VT100 state, line prefix state one line lock
log region, DoE region, block subsystem rings, block chain, caches a lock each
heartbeat tick counters per-core counters (MC-3); one core until then

Shared VM code (src/, vendored; runs inside every VM):

File Global Treatment
dictionary_management.c sf_fc_list/sf_fc_count/sf_fc_cap/sf_cached_latest: one dictionary lookup index for all VMs, rebuilt whenever a different VM's dictionary is searched move into VM (per-VM index). Also stops the rebuild thrash on every switch today.
word_source/starforth_words.c g_prng_state: one PRNG stream for all VMs (accepted at Stage H) an atomic step, so the stream stays one stream (order is nondeterministic across cores anyway, MC-R4)
physics_runtime.c g_runtime inventory in MC-0; per-VM or atomic
word_source/dictionary_manipulation_words.c state_variable (STATE) inventory in MC-0; per-VM
physics_hotwords_cache.c g_hotwords_cache retired under __STARKERNEL__ (item 4.1); confirm unused
word_source/ttf_words.c font and raster cache core 0 only (Hestia's framebuffer)

2. Staged plan

Stage Change Evidence (named in advance)
MC-0a The per-core block, on one core. A per-core structure reached through each ISA's per-CPU register (GS base / TPIDR_EL1 / tp), set up for core 0 at boot. g_switch_current_vm moves into it. Standard sequence unchanged. A probe reads the per-core pointer from a normal kernel path and from an interrupt handler, and gets core 0's block both times on all three ISAs.
MC-0b Real spinlocks. A kernel spinlock primitive (atomic exchange, interrupts masked while held on the owning core). sf_mutex_* stop being no-ops. The §1 kernel locks are added. Standard sequence unchanged. A probe counts every lock's acquisitions and releases through a boot and the standard sequence: counts balance, and no lock is held across a context switch or on return to the idle point.
MC-0c Shared VM code made per-VM or atomic (§1, second table). The dictionary lookup index moves into VM. The dict_hash set unchanged (the index is not hashed). A probe: two VMs searched alternately never rebuild each other's index. Standard sequence.
MC-0d Stadium's dispatch path partitioned (§1). Standard sequence; conservation holds (stadium_conserved() on every VM, the ledger audit). A probe proves the dispatch path takes no shared lock.
MC-1 Bring up the extra cores and park them halted, per ISA: amd64 (MADT, INIT-SIPI-SIPI, a position-independent trampoline below 1 MiB), aarch64 (DTB /cpus, PSCI CPU_ON), riscv64 (DTB harts, SBI HSM hart_start). QEMU gets -smp 2. The boot reports each core online with its own per-core block; the extra cores sit halted (0 idle growth, no spin); the standard sequence unchanged.
MC-2 Observers get core-0 affinity. With one worker core, behaviour identical to today. A probe shows no observer ever claimed off core 0.
MC-3 Worker cores claim through the shared decision: running_on with compare-and-swap, per-core idle contexts, per-core beats, wake by inter-processor interrupt. Two workers genuinely overlapping in time on two cores. A probe counts double claims: 0. Conservation holds. G by counts. Idle worker cores halted (0 idle growth).
MC-4 DoE with cores as a factor (after O-D): per-ISA campaigns at 1, 2 and 4 cores (MC-R5). Per ISA and core count: 18 MU-TRIAL markers, fail=0, MU-CAMPAIGN: complete.

Stop conditions (re-plan rather than push on):

  • dict_hash diverges across ISAs;
  • conservation goes false;
  • a lock held across a switch, or an unbalanced lock count;
  • a double claim;
  • an idle core spinning.

3. Execution log

3.1 MC-0a — the per-core block, on one core (2026-09-25)

Change.

  • include/starkernel/percpu.h (new): sk_percpu_t { self, core_id, current_vm }, with self first.
    • sk_percpu() reads this core's block: movq %gs:0 on amd64, mrs tpidr_el1 on aarch64, mv tp on riscv64.
    • sk_percpu_install() writes the register: IA32_GS_BASE via wrmsr, TPIDR_EL1, or tp.
  • src/starkernel/hal/percpu.c (new): the boot core's block is one static object, not a table sized to a core count. Secondary cores get theirs as they come online (MC-1).
  • kernel_main.c: sk_percpu_boot_init() runs straight after arch_early_init(), because on amd64 that call reloads GS, which would reset the base.
  • switch.c: the global g_switch_current_vm is gone.
    • sk_vm_switch_current_vm(), the setter, the idle point and sk_vm_context_switch() use sk_percpu()->current_vm.
    • After resume, the context switch re-reads the block rather than trusting a pointer cached before the switch.

Probe (temporary; removed before commit).

  • kernel_main.c checked, from the normal path after the Tripod came up, that:
    • sk_percpu()->self == sk_percpu();
    • core_id == 0;
    • current_vm is Hera;
    • sk_vm_switch_current_vm() agrees.
  • heartbeat_tick() ran the same self-check from interrupt context on every beat.
ISA log normal path core_id current_vm is Hera switch.c agrees interrupt path ok / bad
amd64 logs/20260925-130335 ok 0 1 1 889 / 0
aarch64 logs/20260925-130905 ok 0 1 1 1877 / 0
riscv64 logs/20260925-131337 ok 0 1 1 1625 / 0

Standard sequence (clean build, with the probe reverted; the tree was checked byte-for-byte against the saved stage diff).

ISA log O-A (G, PREC, LOAD) M7.1a hash MINT / WIREBIND / USE / 1 2 + . idle growth UNKNOWN / faults
amd64 logs/20260925-131805 A=40 B=40, HHH, ok 0x99b01134c3d3049c all ok, 3 0 B / 20 s 0 / 0
aarch64 logs/20260925-133049 A=40 B=40, HHH, ok 0x99b01134c3d3049c all ok, 3 0 B / 20 s 0 / 0
riscv64 logs/20260925-134357 A=40 B=40, HHH, ok 0x99b01134c3d3049c all ok, 3 0 B / 20 s 0 / 0
  • The dict_hash values (Hera 0x00340a8b2c842eb7, Hestia 0x78bf28439e4a643f, Artemis 0x66886d174d5f3b5a) are identical across ISAs and unchanged from O-C.
  • The ledger line matches across ISAs: held=2048, pulled=258048, returned=255263, consumed=737, conserved=true.
  • No stop condition was hit. MC-0a done.