2267 lines · plain
1.. SPDX-License-Identifier: GPL-2.02 3================================================================================4Syscall path model — per-Worker vmlinux + real SMP (K11 opener)5================================================================================6 7:Status: K11 OPEN. Spec only; no code lands until this doc is closed.8 9This document is the K11 opener. It specifies the per-Worker vmlinux10instantiation contract, the in-Worker ``linux.syscall`` path, the11``wasm_syscall`` entry sequence (kernel-stack swap), the12``sys_call_table`` population strategy, the ``arch_cpu_up`` /13``arch_cpu_down`` hooks tied to spawn / exit, and the new14``num_vcpu`` / ``max_wasm_instances`` JS-side parameters that the15phase introduces.16 17The K11 axis is "the in-Worker syscall path comes online." K11 is the18phase that retires R-K8-1, R-K8-2, R-K9-1, R-K9-2, R-K9-3 (every19JS-direct shortcut row whose retirement signal was wrongly written as20"K10+ when cross-process syscall RPC lands" — per21``docs/ARCHITECTURE.md`` §4 there is no RPC, the kernel runs IN the22Worker; the retirement gate is now). It also inverts R-K10-W1 (the23"vmlinux is single-Worker" assertion that pinned the K5–K9 shortcut24state, not the architecture).25 26K11 lands BEFORE pty / line-discipline / job-control work (the27original K10-B/C/D scope, now relabelled K12). The investigation28that surfaced the architectural correction is recorded in §17 of29this document; it was a §20.6.1 dependency-check failure on prior30advice — the K10-A2b commit cited "vmlinux is single-Worker by31architecture" without verifying against32``docs/ARCHITECTURE.md`` §3 line 124 and ``memory-model.rst`` §1.1,33both of which specify per-Worker vmlinux instantiation.34 35Reading order (load-bearing, do not re-order):36 371. §2 architectural ground truth (citations to the spec sites)382. §3 the K5–K9 shortcut state being retired393. §4 per-Worker vmlinux instantiation contract404. §5 ``linux.syscall`` import resolution415. §6 ``wasm_syscall`` entry sequence426. §7 ``sys_call_table`` population strategy437. §8 ``arch_cpu_up`` / ``arch_cpu_down`` hooks448. §9 ``num_vcpu`` parameter459. §10 ``max_wasm_instances`` parameter4610. §11 R-K10-W1 inversion plan4711. §12 R-K8-1 / R-K8-2 / R-K9-1 / R-K9-2 / R-K9-3 retirement plan4812. §13 K11 sub-phase decomposition4913. §14 refutation budget5014. §15 acceptance criteria5115. §16 K10 close ledger amendment + scope correction proposal5216. §17 §20.6.1 retroactive dependency-check5317. Appendix A — K11 close checklist54 55--------------------------------------------------------------------------------561. Why this document exists separately57--------------------------------------------------------------------------------58 59The K11 axis is structurally different from K10's terminal axis. K1060landed the kernel-side enabling machinery (the initcall dispatcher);61K11 lands the user-process syscall path that lets user binaries62actually reach the kernel-side subsystems registered by initcalls.63K12 (= original K10-B/C/D) lands the pty / line-discipline /64job-control surface on top of the K11 syscall path.65 66The K11 work was originally folded into K10's umbrella under the67implicit assumption that pty / line-discipline / job-control could68ride the existing JS-direct syscall surface (the R-K9-1 pattern).69The K10-B1 architectural-fork investigation surfaced that70assumption as wrong against the architecture: the architecture71specifies an in-Worker ``wasm_syscall`` path through a per-Worker72vmlinux instance, not a JS-direct or RPC-based path. K10-B1's73"how do pty syscalls reach the kernel" question presumes a working74syscall path that does not yet exist; the path itself is what K1175lands.76 77A separate opener doc is warranted because:78 79* The phase's surface novelty is high (the in-Worker syscall path80 has zero production code today; ``sys_call_table`` is empty;81 ``wasm_syscall`` returns ``-ENOSYS``; ``arch_cpu_up`` / ``down``82 do not exist).83 84* The spec-vs-implementation drift surfaced by R-K10-W1 needs an85 explicit retroactive dependency-check (§17) — a §20.6.1 artefact86 that lives best in the doc that lands the correction.87 88* Two new JS-side parameters (``num_vcpu``, ``max_wasm_instances``)89 introduce user-visible knobs that need spec text before any90 implementation lands — the K-phase pattern landed at K7 / K8 / K9.91 92* The phase retires five existing §15 rows (R-K8-1 through R-K9-3)93 whose retirement signals were wrong-shaped; the close ledger94 needs to record both the correction and the actual close.95 96--------------------------------------------------------------------------------972. Architectural ground truth98--------------------------------------------------------------------------------99 100Three load-bearing facts from the spec, with citations:101 102**(F1) Per-Worker vmlinux instantiation.** Every Worker — every103process Worker AND the dedicated vmlinux Worker — holds its own104``WebAssembly.Instance`` of ``vmlinux.wasm``. They share what they105import (``env.kernel_memory``); they do not share their wasm106instance, ``__stack_pointer`` global, Asyncify save region, or107non-shared globals.108 109 *Authority:*110 111 * ``docs/ARCHITECTURE.md`` §3 line 124 (boot flow): "A new112 instance of ``vmlinux.wasm`` is *also* instantiated in this113 Worker (so syscalls can be serviced in-Worker against the114 kernel's address space), importing both ``env.kernel_memory``115 and this Worker's ``env.user_memory``."116 117 * ``linux-wasm/Documentation/wasm/memory-model.rst`` §1.1: "Every118 Worker — every process Worker and the dedicated vmlinux Worker119 — holds its **own** ``WebAssembly.Instance`` of120 ``vmlinux.wasm``."121 122**(F2) In-Worker syscall path; no RPC; no SAB-queue cross-Worker123hop.** ``linux.syscall`` (the user-Worker import) is provided by124HardwareJS as a JS function that calls THIS WORKER'S125``vmlinux.exports.wasm_syscall(nr, a0..a5)``. The kernel runs IN126the process Worker against shared ``env.kernel_memory``.127 128 *Authority:*129 130 * ``docs/ARCHITECTURE.md`` §4 lines 178–186 (How131 ``linux.syscall`` is handled in a process Worker): "...132 forwards the call into ``vmlinux.wasm``'s exported133 ``wasm_syscall(nr, ...)``. That export, when invoked from134 this Worker's JS, runs ``do_syscall()`` in kernel mode135 against shared ``memory 1``. The kernel sees the call as if136 it came from the process's CPU."137 138 * ``linux-wasm/Documentation/wasm/memory-model.rst`` §1.2 step 2:139 "HardwareJS calls the kernel instance's exported140 ``wasm_syscall(nr, a0..a5)``. **This crosses no Worker141 boundary**; the kernel instance lives in the same Worker as142 the userspace instance." (emphasis added in this opener for143 K11 reading)144 145**(F3) Real SMP under load.** Workers concurrently mutate146``env.kernel_memory``. Per-CPU variables, spinlocks, RCU, IPIs147(implemented as targeted ``Atomics.notify`` on a per-CPU wake148word) all carry their full upstream semantics.149``num_online_cpus()`` equals the configured maximum number of150concurrent process Workers (plus one for the vmlinux Worker151itself). Each Worker has its own ``raw_smp_processor_id()``;152percpu accessors land in distinct slots.153 154 *Authority:*155 156 * ``docs/ARCHITECTURE.md`` §17.6: "Workers really do157 concurrently mutate ``env.kernel_memory``. Per-CPU158 variables, spinlocks, RCU, and IPIs ... all carry their full159 upstream semantics."160 161 * ``linux-wasm/Documentation/wasm/mm-model.rst`` §0(c) row 3:162 ``CONFIG_SMP=y``, ``NR_CPUS=8`` (compile-time cap).163 164**Spec inconsistency to fix as part of this phase.**165``docs/ARCHITECTURE.md`` §10 currently says ``CONFIG_SMP=n`` (the166"we fake SMP at the JS layer" sentence). That contradicts §17.6 +167§2 + ``mm-model.rst`` §0(c). §10 is stale text from before §17.6168landed; the build has been ``CONFIG_SMP=y`` since K3's percpu work.169§10 must be amended as part of this opener's docs commit.170 171The three facts are not negotiable; they are the architecture. K11172exists to make the implementation match.173 174--------------------------------------------------------------------------------1753. The K5–K9 shortcut state being retired176--------------------------------------------------------------------------------177 178The K5–K9 implementation departed from the architecture in one179load-bearing way: every cross-process syscall is JS-direct. The180state today, by §15 row:181 182R-K8-1183 ``SYS_rt_sigaction`` stub returns 0 success without populating184 kernel-side ``current->sighand``. Disposition tracked in user-side185 ``__wasm_sigaction_table``. Stated retirement signal: "K9+ when186 cross-process syscalls route through ``vmlinux.exports.wasm_syscall``187 and the kernel's signal-delivery decisions actually consult188 ``current->sighand``." K11 IS that retirement.189 190R-K8-2191 K8 signal delivery via JS-direct stamp. ``SYS_kill`` / ``SYS_tkill``192 / ``SYS_tgkill`` resolve target tid in ``dispatchSyscall`` and call193 ``deliverSignalToTarget`` directly, bypassing kernel-side194 ``__send_signal_locked`` → ``arch_signal_wake_target`` → ring →195 consumer chain. Stated retirement signal: "K9+ when cross-process196 syscall RPC lands." That phrasing is wrong-shaped per F2 (no RPC;197 in-Worker call). K11 lands the in-Worker path; R-K8-2 retires.198 199R-K9-1200 Cross-process I/O via JS-direct registries (single ledger row201 absorbing wait4 / pipe / stdin / pgrp). Stated retirement signal:202 "K10+ when cross-process syscall RPC lands — same retirement203 signal as R-K8-1 + R-K8-2." Same wrong-shaped phrasing.204 205R-K9-2206 K9 wasm32 extension syscalls ``__NR_wasm_spawn_image`` (1024) +207 ``__NR_wasm_spawn_image_actions`` (1025). Stated retirement signal:208 "K10+ when ``posix_spawn(2)`` routes through proper kernel-side209 ``do_fork`` + ``do_execve``, which itself depends on the210 cross-process syscall RPC same as R-K8-1 / R-K8-2 / R-K9-1." Two211 retirements composed: the wrong-shaped retirement of R-K8-* / R-K9-1212 (now corrected to K11 in-Worker path), and the standard213 ``posix_spawn`` wrapper landing (which depends on K11's in-Worker214 ``do_fork`` + ``do_execve``).215 216R-K9-3217 K9 signal-termination encoding shortcut (``WIFEXITED+WEXITSTATUS=218 128+sig`` instead of ``WIFSIGNALED+WTERMSIG=sig``). Stated219 retirement signal: "K10+ when proper signal-termination encoding220 lands ... requires the K10+ cross-process syscall RPC + R-K8-1's221 ``current->sighand`` retirement." Composed retirement same as R-K9-2.222 223Five rows, one root cause, one corrected retirement gate. K11 close224retires all five. The retirement is mechanical:225 226* R-K8-1 retires when ``__NR_rt_sigaction`` reaches kernel-side227 ``do_sigaction`` via the in-Worker ``wasm_syscall`` path.228 229* R-K8-2 retires when ``__NR_kill`` / ``__NR_tkill`` / ``__NR_tgkill``230 reach kernel-side ``sys_kill`` / ``sys_tkill`` / ``sys_tgkill``.231 232* R-K9-1 retires when ``__NR_wait4`` / ``__NR_pipe2`` / ``__NR_dup2``233 / ``__NR_close`` / fd-aware ``__NR_read`` / ``__NR_write`` /234 ``__NR_writev`` / ``__NR_setpgid`` / ``__NR_setsid`` / ``__NR_getpgid``235 / ``__NR_getpgrp`` / ``__NR_getsid`` / ``__NR_getpid`` / ``__NR_getppid``236 reach kernel-side handlers. The five JS-direct registries237 (``childExitRegistry``, ``pipeRegistry``, ``fdTableRegistry``,238 ``stdinChannel``, ``processGroupRegistry``) demote to per-Worker239 caches OR are deleted.240 241* R-K9-2 retires after R-K9-1 by switching musl wrappers to call242 ``posix_spawn(2)`` against kernel-side ``do_fork`` + ``do_execve``;243 the wasm32-specific ``__NR_wasm_spawn_image{,_actions}`` syscall244 numbers stay as fast-path or get deprecated.245 246* R-K9-3 retires after R-K8-1 by routing the user-Worker exit247 message through kernel-side signal-delivery; the ``task_struct``'s248 ``exit_code`` field carries the terminating signal per upstream249 semantics.250 251--------------------------------------------------------------------------------2524. Per-Worker vmlinux instantiation contract253--------------------------------------------------------------------------------254 255This section codifies §F1 (per-Worker vmlinux) as a worker.mjs /256worker-browser.mjs amendment.257 2584.1. Worker classes (taxonomy)259------------------------------260 261Two valid Worker classes after K11:262 263vmlinux Worker264 The dedicated Worker that runs kthreads. Already exists today265 (``hardwarejs/src/kernelWorker.mjs`` / ``kernelWorker-node.mjs``).266 Holds one ``WebAssembly.Instance`` of ``vmlinux.wasm``. Imports267 ``env.kernel_memory`` (shared) + a 1-page ``shared:true`` sentinel268 Memory bound to ``env.user_memory``. CPU number 0269 (``raw_smp_processor_id() == 0``).270 271process Worker272 Any Worker hosting a userspace process. After K11, holds TWO273 ``WebAssembly.Instance``\ s: the userspace ``.wasm`` AND a274 per-Worker instance of ``vmlinux.wasm``. The userspace instance275 imports ``env.kernel_memory`` (shared) + the Worker's own276 ``env.user_memory`` (shared, per-Worker). The kernel instance277 imports the same ``env.kernel_memory`` + the same278 ``env.user_memory`` — so kernel code running on behalf of this279 process can ``memory.copy`` across the user/kernel boundary280 using THIS Worker's user memory. CPU number assigned at spawn281 (1 ≤ ``raw_smp_processor_id()`` < ``num_vcpu``); recycled on282 exit (§8).283 284Invalid Worker classes (caught at instantiation time):285 286* A Worker holding two ``vmlinux.wasm`` instances. Asserted by287 the K10-A4 ``kernelInstanceCount`` runtime check (which inverts288 meaning at K11 close — see §11).289 290* A process Worker without a ``vmlinux.wasm`` instance. Asserted291 by the K11 source-static guard test (``hardwarejs/test/k11-*-292 per-worker-vmlinux.test.ts``, replacing the K10-A4 single-293 Worker-invariant test).294 2954.2. Spawn-time amendment to worker.mjs296---------------------------------------297 298Today, ``hardwarejs/src/worker.mjs`` (the process-Worker entry299point) instantiates the userspace ``.wasm`` and provides300``linux.syscall`` as a JS function that calls301``kernelSpawnHandler.dispatchSyscall(...)`` (the K5–K9302JS-direct path). After K11:303 3041. Receive ``boot`` message from main thread carrying:305 ``{ wasmBytes, kernelWasmBytes, kernel_memory, user_memory,306 cpu_id, ... }``.307 3082. Instantiate ``vmlinux.wasm`` first (the kernel instance for309 this Worker), with imports::310 311 {312 env: {313 kernel_memory, // shared, system-wide314 user_memory, // shared, per-Worker (THIS Worker's)315 memory: kernel_memory, // legacy default-memory binding316 ...317 },318 linux: {319 dmesg, hlt, cpu_relax, atomic_wait_i32, atomic_notify, ...320 }321 }322 3233. Instantiate the userspace ``.wasm`` second, with imports::324 325 {326 env: {327 kernel_memory,328 user_memory,329 memory: user_memory,330 ...331 },332 linux: {333 syscall: linuxSyscallShim, // wraps vmlinux.exports.wasm_syscall334 ...335 }336 }337 338 The ``linuxSyscallShim`` is a thin JS function that closes over339 the kernel instance's exports::340 341 function linuxSyscallShim(nr, a0, a1, a2, a3, a4, a5) {342 return kernelInstance.exports.wasm_syscall(343 nr, a0, a1, a2, a3, a4, a5);344 }345 346 Optional fast-path: a tiny set of pure-JS syscalls347 (``gettimeofday``, ``clock_gettime``, ``getrandom``) MAY be348 answered directly without dispatching through the kernel349 instance, as a latency optimisation. The shim defaults to350 "always forward" per ``ARCHITECTURE.md`` §4 step 3.351 3524. Run the userspace instance's ``_start`` (or the K7 / K9 thread /353 spawn entry equivalents).354 355The ``boot`` message gets a new field ``kernelWasmBytes`` carrying356the same ``vmlinux.wasm`` bytes that the kernel Worker received.357The bytes are identical across all Workers; HardwareJS may358``WebAssembly.compile`` once and pass the resulting359``WebAssembly.Module`` instead of bytes (an optimisation tracked360as a §15-shape note, not a contract).361 3624.3. ``worker-browser.mjs`` parity363----------------------------------364 365The browser-side process-Worker entry point gets the same366amendment. Browser-engine behaviour for two-instance Workers is367the same as Node's (V8 is the engine in both cases via Node and368Chromium); the per-Worker resource cost of two instances is the369load-bearing risk surface, captured in §10370(``max_wasm_instances``).371 3724.4. Exec / fork shape373----------------------374 375The K11 fork / exec changes are mechanical:376 377* ``linux.spawn_worker`` (the existing K5/K6 import that378 HardwareJS implements as "create a Worker and load this379 ``.wasm``") additionally ships the kernel ``vmlinux.wasm`` bytes380 to the new Worker. Spawn order: kernel instance first, then381 user instance.382 383* ``linux.fork_worker`` (the K9 spawn-with-fd-actions variant)384 ships the same. Cost: the new Worker holds 2 instances, so385 ``max_wasm_instances`` accounting (§10) charges 2.386 387* ``linux.exec_worker`` (the K6 binfmt_wasm path) terminates the388 caller and spawns a fresh Worker. The fresh Worker spawns389 with the kernel instance + the new user instance — total 2.390 391* The vmlinux Worker remains untouched (1 instance).392 393--------------------------------------------------------------------------------3945. ``linux.syscall`` import resolution in process Workers395--------------------------------------------------------------------------------396 397§F2 specifies the in-Worker call shape. This section pins the JS398shim contract and the failure-mode taxonomy.399 4005.1. Shim contract401------------------402 403The ``linux.syscall`` import in a process Worker resolves to a JS404function with signature::405 406 (nr, a0, a1, a2, a3, a4, a5) -> i64407 408The function MUST:409 4101. Call ``kernelInstance.exports.wasm_syscall(nr, a0, a1, a2, a3,411 a4, a5)`` (the per-Worker kernel instance's exported entry).412 4132. Return the result as ``i64``.414 4153. NOT post a message to any other Worker. NOT call416 ``Atomics.wait`` on a SAB queue. NOT marshal arguments through417 any cross-Worker buffer. The kernel runs in this Worker; the418 call is a normal wasm-import-out-and-back-in JS hop.419 420The shim MAY (optimisation, not contract):421 422* Short-circuit a known-pure-JS syscall list before calling423 ``wasm_syscall``. The list is documented in424 ``arch/wasm32/include/asm/syscall_shortcuts.h`` (new file at425 K11 implementation time). Examples: ``__NR_gettimeofday``,426 ``__NR_clock_gettime`` (when not requesting CLOCK_MONOTONIC_RAW),427 ``__NR_getrandom`` (small reads). Each shortcut requires a428 matching ``arch/wasm32/kernel/syscall_shortcut_<nr>.c`` file429 documenting the short-circuit's safety argument: same return430 semantics as the kernel-side syscall under all flag431 combinations the spec exposes.432 4335.2. Failure modes434------------------435 436If ``kernelInstance`` is not yet bound when ``linux.syscall``437fires (e.g., a syscall before ``__wasm_call_ctors`` finishes —438should not happen but the assertion is cheap), the shim throws a439``WasmRuntimeError`` with a clear "syscall before kernel440instance bound" message. The userspace Worker traps; HardwareJS441records the trap and surfaces it as a kernel-trap terminal in442the boot result.443 444If ``wasm_syscall`` itself throws (e.g., a guest C bug inside445``do_syscall``, an Asyncify state corruption, a wasm OOB on the446shared kernel memory), the shim does NOT swallow the throw — it447propagates as a userspace trap. The userspace Worker terminates;448HardwareJS reaps via the existing K8/K9 exit machinery.449 4505.3. Why a JS shim and not direct ``call_indirect``?451----------------------------------------------------452 453``ARCHITECTURE.md`` §4 already explains this:454 455 "Why doesn't the process Worker just ``call_indirect`` into456 kernel code directly? Because each Worker has its own WASM457 module instance; modules can't share a function table cheaply458 across instances. Calling out to JS and then back into the459 kernel module is structurally how WASM 'links' across460 instances, and JS is also the natural place for the dispatch461 table to live."462 463The K11 design preserves that framing.464 465--------------------------------------------------------------------------------4666. ``wasm_syscall`` entry sequence (kernel-stack swap)467--------------------------------------------------------------------------------468 469This section concretises ``memory-model.rst`` §1.2 steps 3–5470into the C / wasm32 assembly that K11 implements in471``arch/wasm32/kernel/entry.c``.472 4736.1. Today474----------475 476``arch/wasm32/kernel/entry.c::wasm_syscall`` exists as a stub477returning ``-ENOSYS`` for every ``nr``. ``sys_call_table`` is478empty. This is the K5 placeholder; K11 fills it.479 4806.2. Required dance (per ``memory-model.rst`` §1.2)481---------------------------------------------------482 483A kernel stack is per ``task_struct``, slab-allocated out of484``env.kernel_memory`` (already true at K3 — kernel stacks are485allocated by ``copy_thread`` / ``alloc_thread_stack_node``).486The wasm runtime's notion of "the stack" is a single487``__stack_pointer`` global per instance; the kernel must swap488that pointer at every kernel-mode entry and exit.489 490The swap sequence::491 492 long wasm_syscall(unsigned long nr, long a0, long a1,493 long a2, long a3, long a4, long a5)494 {495 unsigned long user_sp;496 unsigned long kern_sp;497 long ret;498 499 /* Step 1: read current's kernel stack base. */500 kern_sp = (unsigned long)current->stack + THREAD_SIZE;501 502 /* Step 2: swap __stack_pointer. */503 user_sp = __builtin_wasm_get_stack_pointer();504 __builtin_wasm_set_stack_pointer(kern_sp);505 506 /* Step 3: persist user_sp into per-task state. */507 current->thread.user_stack_pointer = user_sp;508 509 /* Step 4: dispatch. */510 ret = do_syscall(nr, a0, a1, a2, a3, a4, a5);511 512 /* Step 5: restore. */513 __builtin_wasm_set_stack_pointer(514 current->thread.user_stack_pointer);515 return ret;516 }517 518The pseudo-builtins ``__builtin_wasm_{get,set}_stack_pointer`` are519NOT actual clang builtins as of LLVM 22; they are stand-ins for520the real implementation, which must be inline wasm assembly in521``arch/wasm32/kernel/entry.S`` reading / writing the522``__stack_pointer`` global. Probe at K11-A1: confirm the assembly523syntax and that ``wasm-ld`` preserves the global access through524linking.525 5266.3. Stack-swap edge cases527--------------------------528 529Re-entrant syscall (kernel code calling another syscall from530within ``wasm_syscall``) is structurally impossible on wasm32:531there is no ``int 0x80``, kernel C code calls C functions532directly. ``current->thread.user_stack_pointer`` is single-slot533per task and only written on user-to-kernel entry.534 535Asyncify save / restore inside ``do_syscall`` (e.g., a536``schedule()`` triggered by a blocking syscall) saves the kernel537stack, the kernel ``__stack_pointer``, and the Asyncify state.538On resume, all three restore atomically. ``user_stack_pointer``539is in the task struct (kernel memory); it survives ``schedule``540unaffected.541 542Signal delivery on syscall return runs after step 5 (after543restore). The K8 ``__wasm_signal_dispatch`` trampoline runs in544USER mode against the user stack. K11 inherits this without545change.546 5476.4. Probe checklist (K11-A1)548-----------------------------549 550* ``__stack_pointer`` global access from inline assembly works.551* ``current->stack`` resolves to a non-zero kernel address from552 inside ``wasm_syscall`` running on a process Worker.553* ``THREAD_SIZE`` is the K4-bumped value (32 KiB, ``PAGE_SIZE <<554 THREAD_SIZE_ORDER`` with ``THREAD_SIZE_ORDER = 3``) pinned in555 ``arch/wasm32/include/asm/thread_info.h``. (DRIFT-CATCH at556 K11-A1: an earlier draft of this section claimed 16 KiB557 "per upstream wasm32 default"; this was a §20.5.3 candidate558 drift surfaced by the K11-A1 probe and corrected here. The559 K4 close ledger bumped from 16 KiB to 32 KiB after deep560 callchains overflowed under the printk → vsnprintf →561 console-driver path. Per ``thread_info.h`` rationale,562 per-task overhead is 32 KiB stack + 16 KiB Asyncify =563 48 KiB per kthread, matching ``sched-model.rst`` §3.3.)564* The kernel C compiler emits ``wasm_syscall`` with a body that565 the link-time post-pass leaves intact (no inlining surprises).566* ``wasm_syscall`` invoked directly with all-zero arguments567 returns ``-38`` (``-ENOSYS``) without trapping. The current568 K10-era stub (``arch/wasm32/kernel/entry.c``) returns this569 unconditionally; K11-A1 confirms the call shape works570 end-to-end before K11-A3 replaces the stub with the real571 stack-swap implementation.572 573--------------------------------------------------------------------------------5747. ``sys_call_table`` population strategy575--------------------------------------------------------------------------------576 577``arch/wasm32/kernel/syscall_table.c`` is empty today. Upstream578Linux populates the table per arch via579``arch/x86/entry/syscalls/syscall_64.tbl`` (or equivalent). The580K11 strategy is upstream-clean for syscalls that link, with an581explicit ``-ENOSYS`` stub list + §15 rows for those that do not.582 5837.1. Population mechanism584-------------------------585 586A new file ``arch/wasm32/kernel/syscall_table.c`` ``#include``\ s587the upstream ``arch/x86/entry/syscalls/syscall_64.tbl``-derived588generation via the existing ``asm/syscall.h`` machinery. Each589entry is a function pointer to ``sys_<name>`` (or590``__x64_sys_<name>``, depending on the upstream table format).591 592For each syscall:593 594* If ``sys_<name>`` links (the implementation file is in the595 build), the entry points at it.596 597* If ``sys_<name>`` does NOT link (filesystem subsystem disabled598 by Kconfig, syscall stubbed out, etc.), the entry points at a599 per-syscall ``-ENOSYS`` stub generated by macro expansion. Each600 ``-ENOSYS`` stub gets a §15-row treatment if the missing601 implementation is load-bearing for any K-phase ≤ K12; otherwise602 it is a one-line "this syscall is not provided on wasm32"603 comment in the table.604 605The K11 implementation walks the upstream table and lands the606stub set. The §15 row count for K11 is bounded by the number of607load-bearing missing implementations; per the K7/K8/K9 surface608enumeration this is expected to be small (most filesystem and609network syscalls are already disabled at Kconfig; what user610binaries actually call is a small subset). Probe at K11-A2:611exhaustively enumerate every ``__NR_*`` referenced by the K7 /612K8 / K9 demos + by musl wrappers used in K12, and confirm each613either links upstream-clean OR has a §15-row stub.614 6157.2. Per-syscall §15-row template616---------------------------------617 618For each missing implementation that is load-bearing:619 620::621 622 R-K11-N: SYS_<name> stubbed out (-ENOSYS) on wasm32623 DEPARTURE: <one-paragraph: what upstream does, what we624 don't>625 AUTHORITY: <file:line in arch/wasm32/kernel/syscall_table.c>626 CARRIER: <K-phase that needs this syscall to land for627 its acceptance to hold>628 RETIREMENT: <upstream code path that, once landed, makes629 the stub unnecessary>630 631The template mirrors the existing §15 row shape from632``docs/ARCHITECTURE.md``.633 6347.3. Composite syscall fast-paths635---------------------------------636 637A small set of syscalls are answered by a wasm32-specific638implementation rather than the upstream one. K11 retains:639 640* ``__NR_clone`` → ``arch/wasm32/kernel/clone.c`` (the K7 thread /641 K9 fork machinery; calls upstream ``kernel_clone`` and then642 the wasm32-specific ``arch_dup_task_struct`` /643 ``copy_thread`` / spawn-ring submission).644 645* ``__NR_wasm_spawn_image`` (1024) + ``__NR_wasm_spawn_image_actions``646 (1025) → R-K9-2 fast-paths. K11 retains them as fast-paths;647 R-K9-2 retirement signal becomes "K12+ when ``posix_spawn``648 wraps ``do_fork`` + ``do_execve`` on the in-Worker syscall649 path."650 651* ``__NR_futex`` → R-K7-3 in-Worker dispatch (already652 implemented; no change).653 654The list is documented in655``arch/wasm32/include/asm/syscall_shortcuts.h`` (§5.1656short-circuit list, same file).657 658--------------------------------------------------------------------------------6598. ``arch_cpu_up`` / ``arch_cpu_down`` hooks660--------------------------------------------------------------------------------661 662§F3 specifies real SMP. K11 lands the dynamic CPU bring-up /663tear-down tied to process Worker spawn / exit.664 6658.1. Today666----------667 668The kernel boots ``CONFIG_SMP=y`` ``NR_CPUS=8`` (per669``mm-model.rst`` §0(c) row 3). Only CPU 0 is online (the670vmlinux Worker). ``cpu_possible_mask`` and ``cpu_present_mask``671are set at boot time (K3 / K4 work) but ``cpu_online_mask``672stays at ``{0}`` because no ``cpu_up`` is ever issued from the673arch.674 6758.2. Hooks676----------677 678Two new arch hooks added in K11 patches:679 680``arch_cpu_up(int cpu)``681 Called from ``hardwarejs/src/kernelSpawnHandler.ts`` after682 ``linux.spawn_worker`` (or ``fork_worker`` / ``exec_worker``)683 succeeds and the new Worker has acknowledged kernel-instance684 ready. Marks ``cpu`` as online (``set_cpu_online(cpu, true)``);685 initialises per-CPU state for ``cpu`` (the K3 percpu work686 already allocated the slots; this just brings the slot live);687 notifies any waiters via the existing CPU hotplug machinery.688 689``arch_cpu_down(int cpu)``690 Called from ``kernelSpawnHandler`` on Worker exit (after691 ``recordChildExit`` has been notified). Marks ``cpu`` as692 offline (``set_cpu_online(cpu, false)``); flushes any pending693 per-CPU work to other CPUs (the K3 / K4 work has the694 upstream-clean migration paths); recycles the CPU number for a695 future spawn (§9.3 numbering policy).696 6978.3. CPU numbering policy698-------------------------699 700CPU 0701 The vmlinux Worker. Set at boot, never reassigned. Hosts all702 kthreads.703 704CPUs 1 .. ``num_vcpu`` - 1705 Process Workers. Assigned at spawn time from a free list;706 recycled at exit. The free list is JS-side (in707 ``hardwarejs/src/cpuRegistry.ts``, new file at K11-B708 implementation time); ``arch_cpu_up(cpu)`` and709 ``arch_cpu_down(cpu)`` reflect the JS-side allocation back710 into ``cpu_online_mask``.711 712A process Worker's ``raw_smp_processor_id()`` returns its713assigned CPU number. Each Worker has its own714``__stack_pointer`` global, its own per-CPU slot for percpu715variables, its own runqueue tail (the K4 ``wasm32_rq``716machinery extended for SMP at K11-B).717 7188.4. ``smp_call_function`` / IPI719--------------------------------720 721IPIs are implemented as targeted ``Atomics.notify`` on a722per-CPU wake word in ``env.kernel_memory``. Already specified723in ``ARCHITECTURE.md`` §17.6: "IPIs (implemented as targeted724``Atomics.notify`` on a per-CPU wake word) all carry their full725upstream semantics."726 727K11 lands the per-CPU wake word allocation (``arch/wasm32/include/728asm/smp.h``) + the upstream-clean ``smp_call_function_*``729infrastructure on top.730 731--------------------------------------------------------------------------------7329. ``num_vcpu`` parameter733--------------------------------------------------------------------------------734 735The first new JS-side parameter K11 introduces.736 7379.1. Definition738---------------739 740``num_vcpu`` is a positive integer ≥ 2, ≤ ``NR_CPUS`` (= 8 at741K11). It is the maximum number of CPUs the kernel will see742online concurrently — including CPU 0 (the vmlinux Worker).743 744* CPU 0 is reserved for the vmlinux Worker. Always online.745* CPUs 1 .. ``num_vcpu`` - 1 are available for process Workers.746* No more than ``num_vcpu`` - 1 process Workers can run747 concurrently. Subsequent ``fork`` / ``spawn_worker`` requests748 block on the JS-side spawn queue (NOT on -EAGAIN — see §10749 for -EAGAIN semantics, which is a different cap on wasm750 instance count).751 7529.2. Caller surface753-------------------754 755User-chosen at ``HardwareJS.bootKernel({ num_vcpu: N })`` call756site. Default policy in the demo JS layer:757``min(navigator.hardwareConcurrency, NR_CPUS)``. Demo writers758SHOULD honour the default; explicit override is supported for759testing under low-CPU scenarios.760 761The parameter maps to the kernel cmdline as ``nr_cpus=N`` (the762upstream cmdline arg). ``setup_arch`` parses it; the763``cpu_possible_mask`` is set to ``{0..N-1}`` from764``setup_per_cpu_areas``.765 7669.3. Hard upper bound767---------------------768 769``NR_CPUS`` = 8 at K11 (per ``mm-model.rst`` §0(c) row 3770compile-time cap). Tunable in v0.2 by re-running771``setup_per_cpu_areas`` with a larger compile-time772``NR_CPUS``. Tracked as a §15-shape note in the K11 close773ledger.774 7759.4. Reachability776-----------------777 778If ``num_vcpu`` < 2 the kernel cannot fork (CPU 0 is reserved779for vmlinux; CPU 1 is the first process-Worker slot). The780boot fails with a clear ``panic("num_vcpu must be ≥ 2; got781%d")`` message.782 783--------------------------------------------------------------------------------78410. ``max_wasm_instances`` parameter785--------------------------------------------------------------------------------786 787The second new JS-side parameter. Browser-imposed cap on788simultaneously-instantiated wasm modules.789 79010.1. Why it exists791-------------------792 793Each ``WebAssembly.Instance`` consumes engine-internal794resources (compiled code cache, table allocations, memory795descriptor slots). Browser engines impose a per-tab or per-796process ceiling that varies by engine and version. Today's797HardwareJS treats this as latent — it does not probe or798account. K11 makes it explicit because the per-Worker vmlinux799contract DOUBLES the per-process-Worker instance count, and800the cap becomes load-bearing under high process counts.801 80210.2. Definition803----------------804 805``max_wasm_instances`` is a positive integer, the maximum number806of ``WebAssembly.Instance`` objects HardwareJS will hold807concurrently across all Workers it owns.808 809Cost accounting:810 811* vmlinux Worker = 1 instance.812* Each process Worker = 2 instances (user binary + per-Worker813 vmlinux).814* So the maximum number of concurrent process Workers under815 this cap is::816 817 max_concurrent_process_workers <=818 floor((max_wasm_instances - 1) / 2)819 82010.3. Caller surface821--------------------822 823User-chosen at ``HardwareJS.bootKernel({ max_wasm_instances: M824})`` call site.825 826Default policy: HardwareJS probes at ``bootKernel`` time. Probe827mechanism (K11-B implementation): instantiate a tiny "ping"828``vmlinux`` shape progressively until ``WebAssembly.instantiate``829or ``new WebAssembly.Module`` rejects with "too many wasm830instances" (engine-specific error message). The probe records831the empirical cap minus a safety margin (= 4 instances) and832sets ``max_wasm_instances`` to that. Per-engine known values833populate a fallback table for engines whose error message is834not matched.835 83610.4. Enforcement paths837-----------------------838 839Two enforcement paths, with different K-phase scopes:840 841(1) Hard cap (this phase, K11). At limit, refuse new842 ``fork()`` / ``spawn_worker`` / ``exec_worker`` with843 ``-EAGAIN`` (Linux-spec-correct under resource pressure;844 upstream ``do_fork`` returns ``-EAGAIN`` when845 ``max_threads`` is exceeded). The probe + accounting +846 ``-EAGAIN`` path lands at K11.847 848(2) Eviction (deferred, v0.2 or later). When at limit, find849 the oldest blocked-with-no-pending-signal process Worker;850 terminate the Worker (releases 2 instances); persist its851 ``env.user_memory`` SAB JS-side; mark its ``task_struct``852 as needs-rehydrate. On wake, JS rehydrates: a new Worker853 instantiates with the saved ``user_memory`` + a fresh854 ``vmlinux`` instance. §15 row "K11+ rehydration on855 instance pressure" deferred to v0.2; logged at K11 close856 with retirement signal "any user reports an EAGAIN under857 a workload that would have fit under eviction."858 85910.5. Interaction with ``num_vcpu``860-----------------------------------861 862The two parameters are independent caps:863 864* ``num_vcpu`` is the maximum number of CPUs the kernel sees865 online.866 867* ``max_wasm_instances`` is the maximum number of wasm868 instances HardwareJS holds, which (after dividing by 2 for869 the 2-instance-per-process-Worker cost) gives a separate870 cap on concurrent process Workers.871 872At runtime, the effective concurrent-process-Worker cap is::873 874 effective_cap = min(875 num_vcpu - 1,876 floor((max_wasm_instances - 1) / 2)877 )878 879The narrower of the two binds. If ``num_vcpu`` binds, fork()880blocks on the JS-side spawn queue; if ``max_wasm_instances``881binds, fork() returns ``-EAGAIN``. Demo writers SHOULD pick882``num_vcpu`` as the practical bottleneck (since it is bounded883by ``hardwareConcurrency``); ``max_wasm_instances`` is the884hard ceiling that protects against engine-side OOM.885 886--------------------------------------------------------------------------------88711. R-K10-W1 inversion plan888--------------------------------------------------------------------------------889 890R-K10-W1 (filed at K10-A4 close) records "K10-B active-segment891overlay departs from ``__wasm_init_memory``'s cmpxchg-once892invariant for ``vmlinux.wasm``'s ``.initcall*.init`` bytes. The893departure is currently moot because vmlinux is single-Worker by894architectural construction." The "single-Worker by architectural895construction" rationale is wrong against ``memory-model.rst``896§1.1 + ``ARCHITECTURE.md`` §3 line 124. The actual architecture897has vmlinux instantiated per Worker.898 899R-K10-W1 is NOT removed at K11. It inverts.900 90111.1. Why the K10-A3 bracket pass still works902---------------------------------------------903 904The K10-A3 Strategy B post-link bracket pass converts the905``.initcall*.init`` segments from passive to active. Per F1,906every Worker holds its own ``vmlinux.wasm`` instance and runs907its own ``__wasm_init_memory`` start function on instantiation.908Active segments write at instantiation time, not first-fault.909 910The original K10-A2b §11.2.1 rationale ("vmlinux is911single-Worker; only one ``__wasm_init_memory`` runs") is912incorrect. Multiple ``__wasm_init_memory`` runs DO happen. The913correct rationale is:914 915* ``.initcall*.init`` bytes are immutable, link-time-fixed916 function-pointer arrays (and their sentinels). They contain917 exactly the same bytes for every kernel instance because918 every kernel instance is the same ``vmlinux.wasm`` module.919* Per-Worker re-initialisation writes byte-identical values920 to the same kernel-memory locations. The writes are921 idempotent under the wasm memory model's relaxed-memory922 rules (concurrent writers writing the same byte are923 indistinguishable from a single writer).924 925Strategy B's choice does not change. The reason it works does.926This is a load-bearing rationale correction; the927``terminal-model.rst`` §11.2.1 section gets a HISTORICAL-NOTE928entry recording the original (refuted) rationale and the929corrected one.930 93111.2. Assertion inversion932-------------------------933 934The K10-A4 ``hardwarejs/src/kernelWorker.mjs``935``kernelInstanceCount`` runtime check ("vmlinux instantiated936exactly once per Worker; if a second instantiation is observed,937trip") AND the K10-A4 source-static guard test938(``hardwarejs/test/k10-a4-single-worker-invariant.test.ts`` —939"bootKernel.ts has exactly two createWorker construction sites;940no module under hardwarejs/src/ outside kernelWorker.mjs941instantiates ``vmlinux.wasm``") STAY in place during the K11942pre-implementation period. They pin the current pre-K11943shortcut state.944 945At K11 close, the assertions invert:946 947Old (K5–K10): "vmlinux is instantiated exactly once across all948Workers."949 950New (K11+): "vmlinux is instantiated exactly once PER WORKER951(no Worker holds two vmlinux instances). Valid Worker classes952are (a) the vmlinux Worker, holding 1 vmlinux instance + the953sentinel ``user_memory``; (b) any process Worker, holding 1954vmlinux instance + 1 userspace instance. The K11 source-static955guard test asserts every process Worker spawned via956``spawn_worker`` / ``fork_worker`` / ``exec_worker`` carries a957vmlinux instance."958 95911.3. R-K10-W1 status after K11 close960-------------------------------------961 962Marked CLOSED-AS-INVERTED in ``docs/ARCHITECTURE.md`` §15.3.963The row's text gets a HISTORICAL-NOTE prepended explaining:964 965* The original "single-Worker by architecture" mootness966 argument was wrong.967* The K10-A3 Strategy B implementation still holds for the968 corrected rationale (idempotent function-pointer arrays).969* The K10-A4 runtime + source-static assertions inverted at970 K11 close.971 972The row becomes a worked example for §20.6.1 (K-phase973dependency-checks): a row was filed without a974spec-vs-implementation drift check, the drift was caught one975K-phase later, and the correction landed cleanly because the976implementation choice (Strategy B) was independently correct.977 978--------------------------------------------------------------------------------97912. R-K8-* / R-K9-* retirement plan980--------------------------------------------------------------------------------981 982Each row's retirement is mechanical at K11 close:983 984R-K8-1 (kernel-side ``current->sighand`` not populated)985 Retires when ``__NR_rt_sigaction`` reaches kernel-side986 ``do_sigaction`` via the in-Worker ``wasm_syscall`` path. The987 user-side ``__wasm_sigaction_table`` either deletes (if988 kernel-side is now the source of truth) or demotes to a989 per-Worker fast-path cache that mirrors the kernel-side990 sighand. K11-B5 lands the change. Probe at K11-A4: every K8991 vitest still passes against the new path.992 993R-K8-2 (signal delivery via JS-direct stamp)994 Retires when ``__NR_kill`` / ``__NR_tkill`` / ``__NR_tgkill``995 reach kernel-side handlers. ``deliverSignalToTarget`` (the996 shared primitive) gets demoted from "JS-direct path" to "the997 kernel→HardwareJS signal-ring consumer's stamp helper". K11-B6998 lands the change.999 1000R-K9-1 (cross-process I/O via JS-direct registries)1001 Retires when ``__NR_wait4`` + the pipe / fd / stdin / pgrp1002 syscalls reach kernel-side handlers. The five JS-direct1003 registries get either deleted (if kernel-side is sufficient)1004 or demoted to per-Worker caches. K11-C1 lands the change.1005 1006R-K9-2 (wasm32 extension syscalls 1024 / 1025)1007 Partial retirement at K11: the standard ``posix_spawn(2)``1008 wrappers route through kernel-side ``do_fork`` + ``do_execve``1009 on the in-Worker syscall path; the wasm32-specific syscalls1010 stay as fast-paths. Full retirement deferred to a K-phase1011 that audits the fast-path's necessity.1012 1013R-K9-3 (signal-termination encoding shortcut)1014 Retires after R-K8-1 by routing the user-Worker exit message1015 through kernel-side signal-delivery; the ``task_struct``\ 's1016 ``exit_code`` field carries the terminating signal. K11-C21017 lands the change.1018 1019The five rows go from "ACTIVE — wrong-shaped retirement signal"1020to CLOSED at K11 close. ``docs/ARCHITECTURE.md`` §15 gets the1021status update; the row text gains a closure note recording (a)1022the wrong-shaped retirement signal in the original row, (b) the1023K11 in-Worker path that was the actual retirement gate.1024 1025--------------------------------------------------------------------------------102613. K11 sub-phase decomposition1027--------------------------------------------------------------------------------1028 1029Same shape as the K7 / K8 / K9 / K10 openers.1030 1031K11-A11032 Probes against the current kernel binary: confirm1033 ``__stack_pointer`` global access from inline asm; confirm1034 ``current->stack`` resolves on a process Worker; confirm1035 ``THREAD_SIZE`` consistency; confirm ``wasm_syscall`` can be1036 invoked from JS without crashing (today's stub returns1037 -ENOSYS — make sure the call shape works end-to-end).1038 1039K11-A21040 Source-site snapshot. Enumerate every ``__NR_*`` call site1041 the K7/K8/K9 demos hit + every musl wrapper used by K121042 pty/ldisc/jobctrl work. Confirm each links upstream-clean OR1043 is on the explicit ``-ENOSYS`` stub list with a §15 row. Land1044 ``syscall-path-model-source-snapshots.txt``.1045 1046K11-A31047 ``arch/wasm32/kernel/entry.c`` ``wasm_syscall`` real1048 implementation (§6 stack-swap dance). Probe: a synthetic1049 ``do_syscall(__NR_getpid)`` round-trip from a process Worker1050 returns the correct pid. No actual ``sys_call_table``1051 population yet; the probe uses a single-syscall test harness.1052 1053K11-A41054 Verify K11-A3 against a K7/K8/K9 regression sweep with the1055 new entry sequence enabled but the JS-direct shortcuts still1056 in place. (This is the analogue of K10-A4: the load-bearing1057 machinery works against all prior K-phases without breaking1058 any of them.)1059 1060K11-B11061 ``sys_call_table`` population. ``arch/wasm32/kernel/syscall_table.c``1062 populated for the K7/K8/K9 demo + musl-wrapper surface. Per-syscall1063 stub list + §15 rows. Probe: every ``__NR_*`` referenced by K7/K8/K91064 demos resolves (either to the upstream handler or to a stub1065 with the recorded -ENOSYS).1066 1067 *K11-B1 close state (CLOSED at K11-B1 commit):*1068 ~50 syscalls wired upstream-clean via ``w_sys_<name>`` 6-arg1069 uniform wrappers (read, write, close, lseek, mmap, mprotect,1070 munmap, brk, rt_sigaction, rt_sigprocmask, ioctl, readv, writev,1071 pipe, sched_yield, dup, dup2, pause, nanosleep, getpid, clone,1072 exit, wait4, kill, fcntl, getuid, getgid, geteuid, getegid,1073 setpgid, getppid, getpgrp, setsid, getpgid, getsid,1074 rt_sigtimedwait, rt_sigsuspend, gettid, tkill, set_tid_address,1075 clock_gettime, clock_nanosleep, exit_group, tgkill, openat,1076 set_robust_list, pipe2). Two arch-specific implementations1077 (``sys_mmap`` + ``sys_nanosleep``) live in1078 ``arch/wasm32/kernel/sys_wasm32.c`` because the upstream1079 versions are gated on (CONFIG_64BIT) or are x86-specific1080 (mmap byte-offset semantics).1081 Three documented ``-ENOSYS`` slots, each with a §15 row in1082 ``docs/ARCHITECTURE.md`` §15:1083 - R-K11-1 (``__NR_arch_prctl``=158): wasm-threads TLS via1084 ``__tls_base``; no x86-style FS/GS-base setup.1085 - R-K11-2 (``__NR_rt_sigreturn``=15): wasm32 doesn't use the1086 upstream signal-frame model; the K8 trampoline handles1087 sigmask save/restore in JS shim state.1088 - R-K11-3 (``__NR_fork``=57, ``__NR_vfork``=58): musl's1089 wasm32 port maps fork/vfork to clone(SIGCHLD,...) at1090 libc-level; ``__ARCH_WANT_SYS_FORK`` deliberately not1091 defined in ``arch/wasm32/include/asm/unistd.h``.1092 Out-of-bounds ``nr`` (>= 512) returns ``-ENOSYS`` via1093 ``do_syscall``'s range check. Validated by1094 ``arch/wasm32/kernel/k11_b1_probes.c`` (kernel-side smoke1095 probe, gated by cmdline ``k11_b1_probes=1``) and1096 ``hardwarejs/test/k11-b1-syscall-table.test.ts`` (vitest1097 asserting 18/18 representative-spread probes pass + the1098 three documented ``-ENOSYS`` slots return ``-ENOSYS``).1099 1100K11-B21101 ``arch_cpu_up`` + ``arch_cpu_down`` patches. Per-CPU wake1102 word allocation. ``smp_call_function`` lights up for one1103 process Worker.1104 1105 Decomposed at planning time (chosen for atomicity of the1106 R-K10-W1 inversion landing site):1107 1108 K11-B2a — DONE1109 Per-Worker ``vmlinux`` instantiation. Spawn handler1110 accepts ``vmlinuxModule`` (precompiled1111 ``WebAssembly.Module``) + ``kernelMemoryWasm`` (the1112 shared-SAB-backed ``WebAssembly.Memory``). Process Workers1113 instantiate ``vmlinux`` exactly once via1114 ``instantiateProcessWorkerVmlinux`` (worker.mjs) against1115 shared ``env.kernel_memory`` + own ``env.user_memory``,1116 re-running ``__wasm_call_ctors`` for the K10-A3 active-1117 overlay (idempotent: overlay re-writes byte-identical1118 function-pointer arrays; cmpxchg-once gate skips1119 everything else). The instance's ``wasm_syscall`` export1120 is now reachable from inside the process Worker —1121 architecturally preparing the K11-B5..C2 syscall flips1122 without flipping any actual syscall paths yet (the1123 SAB-RPC syscall path still owns every syscall as of1124 K11-B2a). Soft-failure: if instantiation traps1125 ``vmlinux-error`` is posted to the spawn handler and the1126 user binary continues on the SAB-RPC path.1127 1128 ``processVmlinuxInstanceCount`` mirrors the K10-A41129 ``kernelInstanceCount`` invariant: each Worker (kernel or1130 process) instantiates ``vmlinux`` exactly once. R-K10-W11131 is now PARTIALLY-INVERTED — the assertion has flipped1132 from "exactly one Worker instantiates ``vmlinux``" to1133 "every Worker instantiates ``vmlinux`` exactly once1134 apiece," but the SAB-RPC syscall path remains live. Full1135 inversion lands at K11-D2 when the SAB-RPC path is1136 deleted.1137 1138 Witness: ``hardwarejs/test/k11-b2a-per-worker-vmlinux.test.ts``1139 boots the K6-C2 reference initramfs (hello-k5 as1140 ``/init``) with ``vmlinuxModule`` supplied to the spawn1141 handler, asserts ``onVmlinuxReady`` fires for every1142 spawned process Worker tid with ``hasWasmSyscall=true``,1143 ``processVmlinuxInstanceCount === 1`` (count==1 per tid1144 in the report map), the user binary still produces1145 "hello from K5 userspace" (no perturbation), and no1146 panic / oops appears in dmesg (second-instance ctors are1147 idempotent on the K10-A3 overlay).1148 1149 K11-B2b — DONE (substrate-only; full SMP deferred per1150 R-K11-W2)1151 Per-Worker ``__wasm32_cpu_id`` wasm-Global substrate.1152 ``arch/wasm32/lib/cpu_id.S`` defines the per-instance1153 Global plus the ``__wasm32_get_cpu_id`` /1154 ``__wasm32_set_cpu_id`` accessors; both are explicitly1155 re-exported from ``vmlinux.wasm`` by1156 ``scripts/link-vmlinux-wasm.sh`` (verified via1157 ``wasm-dis`` at K11-B2b close). The kernel header1158 ``arch/wasm32/include/asm/smp.h`` defines1159 ``raw_smp_processor_id()`` to read the wasm-Global, so1160 every kernel call site that reaches1161 ``smp_processor_id()`` (e.g., ``current->cpu``,1162 per-CPU-data accesses, percpu writes) now observes the1163 correct per-Worker CPU id rather than a hardcoded 0.1164 1165 On the JS side ``hardwarejs/src/kernelSpawnHandler.ts``1166 grew a ``cpuRegistry`` allocator: each spawned process1167 Worker gets a unique cpu_id from ``[1, maxCpuId]``1168 (``maxCpuId`` defaults to 7 = ``NR_CPUS - 1``; CPU 0 is1169 reserved for the kernel Worker by convention). The1170 cpu_id is shipped in the spawn ``init`` message; the1171 Worker calls ``vmlinuxInstance.exports.__wasm32_set_cpu_id(cpuId)``1172 after ``__wasm_call_ctors`` runs. Releasing the1173 cpu_id is wired into the worker exit / error paths.1174 1175 What this **does not** include — explicitly deferred1176 to a later phase under §15.3 R-K11-W2:1177 1178 - ``arch_cpu_up`` / ``arch_cpu_down`` boot hooks. The1179 cpu_id substrate is enough for per-CPU semantics on1180 Workers that already exist; bringing CPUs online /1181 offline at boot under a kernel-driven flow is not on1182 the K11-B critical path.1183 - Per-CPU wake words in ``env.kernel_memory``. K8's1184 single ``__sched_wakeup_word`` plus the per-tid1185 signal-pending region remain the wake substrate1186 through K11-D2.1187 - IPI / ``smp_call_function`` cross-CPU dispatch. Not1188 reached by any K11-targeted syscall (the K11-B5/B61189 and K11-C1/C2 retirements are signal + process-mgmt1190 surfaces, not cross-CPU coordination); deferred to1191 K12+ behind R-K11-W2.1192 1193 Acceptance gate ``hardwarejs/test/k11-b2b-cpu-id-substrate.test.ts``1194 asserts (a) every process Worker spawned through the1195 spawn handler reports a cpuId in [1, 7] via1196 ``onVmlinuxReady``, (b) the wasm-Global round-trips1197 ``__wasm32_set_cpu_id(N)`` ↔ ``__wasm32_get_cpu_id() == N``1198 in a standalone instance, and (c) the user binary1199 (hello-k5 ``/init``) still emits its expected stdout1200 with no perturbation.1201 1202K11-B3 — DONE1203 ``num_vcpu`` parameter wired end-to-end. ``BootKernelOptions.num_vcpu``1204 resolves to ``min(navigator.hardwareConcurrency, NR_CPUS=8)``1205 by default; the resolver enforces ``2 <= num_vcpu <= 8``1206 and throws synchronously on out-of-range values BEFORE1207 spawning any Worker. The resolved value is forwarded1208 through three independent surfaces:1209 1210 * ``kernelWorker.mjs`` writes it as ``wasm_boot_info.num_cpus``1211 in the boot prologue. ``arch/wasm32/kernel/setup.c::setup_arch``1212 reads ``bi->num_cpus`` and calls ``set_cpu_possible(cpu, true)``1213 for ``cpu`` in ``[1, num_cpus)``, expanding ``cpu_possible_mask``1214 from the ``boot_cpu_init`` baseline (CPU 0 only) to ``{0..N-1}``.1215 ``setup_nr_cpu_ids`` (called from ``start_kernel`` immediately1216 after ``setup_arch``) then sizes ``nr_cpu_ids`` to ``N``,1217 which is the cap that ``mm/percpu.c::setup_per_cpu_areas``1218 and every ``for_each_possible_cpu()`` loop honours.1219 1220 * ``bootKernel.ts`` passes ``maxCpuId = num_vcpu - 1`` to the1221 default-constructed ``makeKernelSpawnHandler``; the1222 ``cpuRegistry`` allocator hands out cpu_ids from1223 ``[1, maxCpuId]`` (CPU 0 reserved for the kernel Worker).1224 1225 Note: the ``nr_cpus=`` upstream cmdline argument is NOT1226 wired. ``arch/wasm32/kernel/sections.c`` aliases1227 ``__setup_start`` / ``__setup_end`` to a sentinel so the1228 upstream ``__setup("nr_cpus=", nrcpus)`` handler in1229 ``kernel/cpu.c`` is never dispatched. The boot_info1230 ``num_cpus`` field is the single authoritative path for1231 K11-B3; future cmdline-arg work falls under K12+ §151232 shape ``__setup-section-not-walked``.1233 1234 Acceptance gate ``hardwarejs/test/k11-b3-num-vcpu.test.ts``1235 asserts (a) out-of-range / non-integer values throw1236 synchronously, (b) ``num_vcpu=4`` dmesg shows1237 ``cpu_possible_mask expanded to { 0 .. 3 }; HardwareJS1238 intends 4 CPUs``, (c) the spawn handler hands out cpu_ids1239 in ``[1, 3]`` for that boot, (d) the default policy1240 produces a value in ``[2, 8]`` on Node.1241 1242 What this does NOT include (deferred):1243 1244 * Marking CPUs 1..N-1 ``present`` or ``online``. K11-B31245 only marks them ``possible``; transitioning to1246 ``present`` / ``online`` is ``arch_cpu_up``'s job and is1247 deferred behind R-K11-W2 (``arch_cpu_up`` lands when the1248 pty / ldisc / job-control work surfaces a kernel call1249 site that needs cross-CPU coordination).1250 1251K11-B4 — DONE1252 ``max_wasm_instances`` probe + accounting + -EAGAIN1253 enforcement.1254 1255 * Caller surface:1256 ``BootKernelOptions.max_wasm_instances?: number``. When1257 omitted, ``bootKernel`` resolves the value via1258 ``probeMaxWasmInstances()`` — a fast probe that compiles a1259 fixed batch of 16 minimal wasm modules to confirm the engine1260 is healthy, then returns a per-engine fallback (V8/Node:1261 4096; SpiderMonkey: 1024; generic: 256) minus a 4-instance1262 safety margin. The full "compile until rejected" probe per1263 §10.3 is deferred behind a §15-shape note: today's per-engine1264 fallback table is empirically correct for V8 (the only1265 engine HardwareJS targets at K11 close), and a 4096-iteration1266 probe per boot would bloat regression-sweep time. The1267 fallback table grows when a downstream user reports an1268 -EAGAIN under a workload that breaks the assumption.1269 1270 * Constraint: ``max_wasm_instances >= 4`` (must fit at least1271 1 vmlinux Worker + 1 process Worker + 1 instance of safety1272 margin); below-bound throws synchronously BEFORE any Worker1273 is spawned.1274 1275 * Accounting: ``hardwarejs/src/kernelSpawnHandler.ts``1276 threads two synthetic hooks (``__wasmInstanceHooks``) into1277 every spawn. ``reserveWasmInstances`` checks1278 ``count + cost <= maxWasmInstances`` before allocating1279 ``user_memory`` or any other resource; on violation, the1280 handler returns ``-EAGAIN`` (errno 11) immediately.1281 ``releaseWasmInstances`` runs in the Worker's exit / error /1282 onError handlers so every successful reservation pairs with1283 exactly one release. Per-spawn cost is 2 when1284 ``vmlinuxModule`` is supplied (user binary + per-Worker1285 vmlinux) and 1 otherwise (K5..K10 SAB-RPC fallback, retained1286 for backward compatibility through K11-D2).1287 1288 * Acceptance gate1289 ``hardwarejs/test/k11-b4-max-wasm-instances.test.ts``1290 asserts (a) ``max_wasm_instances`` < 4 throws synchronously,1291 (b) non-integer throws synchronously, (c) the default probe1292 resolves successfully through ``bootKernel``, (d) issuing1293 two concurrent fork-mode spawns with1294 ``maxWasmInstances=4`` causes the second to return1295 ``-EAGAIN``.1296 1297 Eviction (per §10.4(2)) remains deferred to v0.2 with the1298 retirement signal "any user reports an EAGAIN under a workload1299 that would have fit under eviction." Logged at K11 close as a1300 §15 row.1301 1302K11-B5 — DONE1303 R-K8-1 retirement: ``__NR_rt_sigaction`` reaches kernel1304 ``do_sigaction``; user-side table demoted/deleted.1305 1306 Landing notes:1307 1308 * ``arch/wasm32/kernel/per_worker.c`` introduced. Exports1309 ``__wasm32_get_init_task_addr()`` (returns ``&init_task`` as a1310 ``u32`` env.kernel_memory offset) AND provides ``init_stack``1311 as a plain ``THREAD_SIZE``-aligned static array. The latter is1312 structural: upstream Linux defines ``init_stack`` via the GNU1313 linker script (``include/asm-generic/vmlinux.lds.h``1314 ``INIT_TASK_DATA``), which our ``wasm-ld`` build bypasses1315 entirely (no GNU linker scripts on ``wasm-ld``). Without1316 a C-side definition the symbol stays undefined and1317 ``--allow-undefined`` zeroes out ``init_task.stack``,1318 tripping the ``wasm_syscall`` ``!current->stack`` guard.1319 * ``hardwarejs/src/worker.mjs`` ``instantiateProcessWorkerVmlinux``1320 binds the per-Instance ``__wasm32_current_task`` global to1321 ``&init_task`` post-ctors. Process Workers now have a non-NULL1322 ``current`` for the in-Worker syscall path.1323 * ``hardwarejs/src/worker.mjs`` ``syscall(13, ...)`` routes to1324 ``vmlinuxInstance.exports.wasm_syscall(13, ...)`` (the K11-B51325 pilot). All other syscalls still take the K5-K9 JS-direct path1326 until the K11-B6/C1/C2 sub-phases retire them.1327 * ``hardwarejs/src/kernelSpawnHandler.ts`` adds an1328 ``onWasmSyscallStats`` callback so vitests can assert "the1329 syscall actually went through ``vmlinuxInstance.exports.wasm_syscall``."1330 * ``hardwarejs/test/k11-b5-rt-sigaction-kernel.test.ts``: green.1331 ``hello-k8-signals`` succeeds end-to-end with stats1332 ``{[__NR_rt_sigaction]: 1}``, proving ``do_sigaction`` runs1333 in the kernel via ``sys_rt_sigaction`` (R-K8-1 RETIRED for1334 ``rt_sigaction``).1335 1336 K11-B5 §15 carry-over: ``R-K11-W3`` (process Workers share1337 ``&init_task`` and its kernel stack). Single-process tests pass;1338 multi-process sigaction-disposition isolation + concurrent1339 syscall stack-safety are flagged for K11-B6/C1 retirement1340 (per-process ``task_struct`` allocation OR per-Worker kernel1341 stack region).1342 1343K11-B6 — DONE1344 R-K8-2 retirement: ``__NR_kill`` / ``__NR_tkill`` /1345 ``__NR_tgkill`` reach kernel handlers.1346 1347 Landing notes:1348 1349 * ``arch/wasm32/kernel/signal.c`` introduces three wasm32-specific1350 arch shims: ``wasm32_sys_kill`` / ``wasm32_sys_tkill`` /1351 ``wasm32_sys_tgkill``. Each validates args (sig in [0, _NSIG],1352 target tid/pid > 0) and submits directly to the K8-B2 signal1353 ring via ``wasm_signal_submit``, bypassing upstream ``sys_kill``'s1354 ``find_task_by_vpid`` + ``send_sig_info`` + ``__send_signal_locked``1355 path. The bypass is necessary because R-K11-W3 init_task-sharing1356 means ``find_task_by_vpid(target_pid)`` would only find1357 ``init_task`` for any process Worker pid; full upstream-clean1358 retirement waits on per-process ``task_struct`` allocation1359 (R-K11-W4 retirement signal).1360 * ``arch/wasm32/kernel/syscall_table.c`` ``[62/200/234]`` now1361 point at thin wrappers that forward to the wasm32 arch shims1362 instead of upstream ``sys_kill`` / ``sys_tkill`` / ``sys_tgkill``.1363 * ``hardwarejs/src/worker.mjs::syscall`` extends the K11-B51364 routing table: ``__NR_tkill`` (200) and ``__NR_tgkill`` (234)1365 always route through ``vmlinuxInstance.exports.wasm_syscall``;1366 ``__NR_kill`` (62) routes when ``pid > 0`` (negative-pid pgrp1367 forms still go through SAB-RPC until R-K9-1 retires at K11-C1).1368 For self-target kills (where the target tid/pid equals the1369 calling Worker's own tid/pid) the worker does an1370 ``Atomics.wait`` on its own ``wake_word`` after ``wasm_syscall``1371 returns to give the K8-B2 ring consumer time to drain (timeout1372 100ms). This makes ``raise(SIGUSR1)`` synchronous so the user1373 binary sees the handler run before the ``raise()`` call returns.1374 * ``arch/wasm32/kernel/k11_b1_probes.c::wasm32_run_k11_b1_probes``:1375 the ``kill(-1, 0)`` probe now accepts ``-ENOSYS`` as a valid1376 return (the wasm32 arch shim returns ``-ENOSYS`` for1377 non-positive pid; the K11-B1 probe was originally written1378 against upstream ``sys_kill`` semantics).1379 * ``hardwarejs/test/k11-b6-kill-family-kernel.test.ts``: green.1380 ``hello-k8-signals`` succeeds end-to-end with stats1381 ``{[__NR_rt_sigaction]: 1, [__NR_tkill]: 1}`` AND a K8-B2 ring1382 delivery for ``sig=SIGUSR1=10`` is observed.1383 1384 K11-B6 §15 carry-overs:1385 1386 * R-K11-W4 (FILED-AT-K11-B6): the wasm32 arch shims bypass1387 upstream signal-delivery mechanics. Full upstream-clean1388 retirement at K11-C1 (with R-K9-1 retirement) or K12+1389 (jobctrl).1390 * R-K8-2 status updated to PARTIAL-CLOSE-AT-K11-B6;1391 FULL-CLOSE-DEFERRED-TO-K11-C1-OR-K12+.1392 1393K11-C11394 R-K9-1 retirement: ``__NR_wait4`` + pipe / fd / stdin / pgrp1395 syscalls reach kernel handlers; JS-direct registries1396 demoted/deleted. Decomposed into K11-C1.a..f.1397 1398K11-C1.a — DONE1399 Per-process ``task_struct`` allocation. R-K11-W3 narrow1400 retirement (process Workers no longer share ``&init_task``1401 wholesale; ``files`` / ``signal`` / ``pending`` / ``stack``1402 are now per-process; ``sighand`` stays shared). Stdin /1403 process-group / pipe / dup2 / wait4 retirements come in1404 K11-C1.b..f.1405 1406 Landing notes:1407 1408 * ``arch/wasm32/kernel/task_alloc.c`` introduced. Exports1409 ``__wasm32_alloc_process_task(pid, ppid)`` which kmallocs1410 a ``task_struct`` + ``THREAD_SIZE`` stack +1411 ``dup_fd(init_files)`` + a kmalloc'd ``signal_struct``1412 (memcpy from ``init_signals`` template, re-init1413 head/list/wait fields). The new task is linked into1414 ``init_task->children`` via ``sibling``. ``sighand`` stays1415 pointing at ``init_sighand`` (R-K11-W3 narrow). PID hash1416 registration via ``alloc_pid`` / ``attach_pid`` is skipped1417 (R-K11-W5).1418 * ``hardwarejs/src/worker.mjs`` ``instantiateProcessWorkerVmlinux``:1419 binds ``current = &init_task`` (K11-B5 transitional), then1420 calls ``__wasm32_alloc_process_task(pid, ppid)`` and re-binds1421 ``current`` to the returned addr. The two-step bind is1422 necessary because the alloc helper itself uses ``kmalloc``,1423 which expects ``current`` valid for lockdep / RCU debug.1424 * ``hardwarejs/src/kernelSpawnHandler.ts``: ``spawnForkWorker``1425 and ``spawnThreadWorker`` now include ``ppid`` in the init1426 payload (parent's pid, used by the alloc helper as1427 informational only at K11-C1.a — the parent pointer goes1428 to ``&init_task`` because ``find_task_by_vpid`` doesn't1429 work yet under R-K11-W5).1430 * ``hardwarejs/src/kernelSpawnHandler.ts``: ``onVmlinuxReady``1431 info now carries ``initTaskAddr`` AND ``perProcessTaskAddr``1432 so vitests can assert the K11-C1.a path actually ran (the1433 two addrs must differ).1434 * ``hardwarejs/test/k11-c1a-per-process-task-kernel.test.ts``:1435 green. ``perProcessTaskAddr != initTaskAddr`` AND the1436 ``hello-k8-signals`` demo still runs end-to-end (signal1437 handler fires on the new task_struct, proving1438 ``current->stack + THREAD_SIZE`` is a valid kernel-stack1439 region for ``wasm_syscall``'s stack-swap).1440 1441 K11-C1.a §15 carry-overs:1442 1443 * R-K11-W3 status: ``FILED-AT-K11-B5; PARTIAL-CLOSE-AT-K11-C1.a;1444 FULL-CLOSE-DEFERRED-TO-K12+`` (per-process ``sighand``1445 allocation waits on execve POSIX semantics for ``sa_restorer``).1446 * R-K11-W5 (FILED-AT-K11-C1.a): ``alloc_pid`` /1447 ``attach_pid`` skipped; ``find_task_by_vpid(non_init_pid)``1448 returns NULL. Cross-target ``kill`` / ``wait4`` /1449 ``setpgid`` use wasm32 arch shims (R-K11-W4 + R-K11-W5)1450 until ``init_pid_ns->child_reaper`` setup lands.1451 1452K11-C1.b — DONE1453 ``__NR_wait4`` (61) routed through ``wasm_syscall`` via the1454 wasm32-specific arch shim ``wasm32_sys_wait4``1455 (``arch/wasm32/kernel/wait.c``). Walks ``current->children``1456 for ``EXIT_ZOMBIE`` matches; reaps via ``reap_zombie`` which1457 ``kfree``\ s the child's task_struct + stack + per-process1458 files / signal allocations. Blocks on1459 ``current->thread.wait_chldexit_wake`` via1460 ``__builtin_wasm_memory_atomic_wait32`` (the wasm321461 cooperative scheduler has no preemptive yield that1462 ``wait_event_interruptible`` could use).1463 1464 Child-side notification: ``__wasm32_notify_child_exit``1465 (exported from ``wait.c``) is called from each process1466 Worker's ``hardwarejs/src/worker.mjs`` SYS_EXIT_GROUP path1467 BEFORE the SAB-RPC tear-down. Sets1468 ``child->exit_state = EXIT_ZOMBIE``,1469 ``child->exit_code``, atomic-increments + atomic-notifies1470 the parent's ``wait_chldexit_wake`` word.1471 1472 Parent-child linkage: ``__wasm32_alloc_process_task`` walks1473 ``init_task.children`` for the parent task (matching ppid),1474 links the new child as a sibling of that parent's children1475 list. For ``pid == 1`` (``/init``) the kernel_init kthread1476 already exists with that pid (created by upstream1477 ``rest_init`` -> ``kernel_thread``); the allocator's1478 ``find_existing_task_by_pid`` dedup detects this and1479 REUSEs the kthread's ``task_struct`` instead of1480 double-allocating. Without this dedup,1481 ``init_task.children`` would have two pid=1 entries1482 and ``find_parent_by_ppid(1)`` for grandchildren would1483 pick the wrong one.1484 1485 Spawn-await ordering: ``hardwarejs/src/kernelSpawnHandler.ts``1486 ``spawnForkWorker`` now awaits the new Worker's1487 ``vmlinux-ready`` message before returning from1488 ``__NR_wasm_spawn_image`` to the parent. This ensures the1489 child's allocator has linked the new ``task_struct`` into1490 the parent's children list BEFORE the parent's subsequent1491 ``waitpid`` runs — without the ordering guarantee, ``wait4``1492 would walk an empty children list and return ``-ECHILD``1493 before the child registers.1494 1495 Acceptance: ``hardwarejs/test/k11-c1b-wait4-kernel.test.ts``1496 asserts (1) hello-k9-wait + hello-k9-child end-to-end with1497 ``WEXITSTATUS == 42``; (2) parent's ``wasmSyscallStats``1498 records nonzero ``__NR_wait4`` (61) routings; (3)1499 ``reap_zombie`` and ``__wasm32_notify_child_exit``1500 pr_info traces appear in process-Worker dmesg; (4) no1501 panic.1502 1503 Carry-overs:1504 1505 - **R-K11-W6 (FILED-AT-K11-C1.b)** in ``docs/ARCHITECTURE.md``1506 §15.3: arch-shim ``wait4`` instead of upstream ``do_wait4``.1507 Retirement signal: K12+ when wait4 features (siginfo,1508 rusage, waitid variants, signal-stop forms via WUNTRACED /1509 WCONTINUED) actually matter for jobctrl tests.1510 1511 - **R-K9-1 (RETIRING-WAIT4-AT-K11-C1.b; FULL-CLOSE-AT-K11-C1.f1512 cleanup-pass)**. ``__NR_wait4`` no longer falls through1513 to the SAB-RPC ``childExitRegistry`` path when1514 ``perProcessTaskAddr != 0`` and ``vmlinux.exports.wasm_syscall``1515 is present. The K11-C1.f cleanup pass DELETES1516 ``childExitRegistry.ts``.1517 1518 - **R-K11-W3 narrow** (init_task children sharing) reduced1519 further: process Workers now have correct parent/child1520 hierarchies via the dedup + ppid-lookup path. R-K11-W31521 closes fully when per-task ``sighand`` allocation lands1522 (K12+).1523 1524K11-C1.c — DONE1525 ``__NR_pipe`` (22) / ``__NR_pipe2`` (293) / ``__NR_dup2`` (33) /1526 ``__NR_dup3`` (292) / ``__NR_close`` (3) routed through1527 ``wasm_syscall``. Kernel-side ``fs/pipe.c`` ``pipe_buffer`` ring1528 + ``files_struct`` (per-process now from K11-C1.a).1529 ``pipeRegistry.ts`` + ``fdTableRegistry.ts`` retired in1530 ``hardwarejs/src/worker.mjs``'s syscall predicate; physical1531 deletion of the registry source files at K11-C1.f cleanup pass.1532 1533 Notes on what landed1534 ~~~~~~~~~~~~~~~~~~~~1535 1536 - ``hardwarejs/src/worker.mjs``: predicate ``routeToWasmSyscall``1537 extended to include ``__NR_PIPE``, ``__NR_PIPE2``, ``__NR_DUP2``,1538 ``__NR_DUP3``, ``__NR_CLOSE``. musl's ``pipe(2)`` wrapper1539 prefers ``SYS_pipe`` when defined; wasm32's musl1540 (``arch/wasm32/bits/syscall.h.in``) defines both, so both must1541 be in the predicate.1542 1543 - Demo: ``wackywasm-tools/tests/hello-k11-c1c-pipe.c``. Single1544 process, no fork: ``pipe(fds)``, ``dup2(fds[1], 7)``, four1545 closes (three valid + one already-closed). Asserts:1546 1547 1. ``pipe()`` returns rc=0 with two distinct fds (kernel1548 allocates ``pipe_inode_info`` + installs both ends in1549 ``current->files``).1550 1551 2. ``dup2(fds[1], 7) == 7`` (kernel installs the write-end at1552 fd 7 by sharing the file ref).1553 1554 3. Three valid closes return 0; close on already-closed fd1555 returns -1 with ``errno=EBADF (9)`` — proves the kernel1556 actually mutated ``current->files``.1557 1558 4. ``wasmSyscallStats`` records nonzero ``__NR_pipe`` (22) +1559 ``__NR_dup2`` (33) + ``__NR_close`` (3) — confirms all1560 three routed via ``wasm_syscall`` rather than SAB-RPC.1561 1562 - Vitest: ``hardwarejs/test/k11-c1c-pipe-kernel.test.ts``.1563 Observed at first green run: ``stats=1:{"3":4,"22":1,"33":1}``.1564 1565 Deferrals carried by K11-C1.c (NOT K11-C1.c blockers)1566 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~1567 1568 - **End-to-end pipe data flow** (``write`` to ``fds[1]`` →1569 ``read`` from ``fds[0]``) requires ``__NR_read`` /1570 ``__NR_write`` to also route through ``wasm_syscall``. That1571 conflicts with K9-era stdin / stdout / stderr handling1572 (JS-direct ``fdTableRegistry`` + ``onUserOutput``) where the1573 kernel-side ``files_struct`` has no entry at fd 0/1/2.1574 Resolution: K11-C1.f cleanup (when ALL syscalls route, ``fd1575 0/1/2`` are kernel-side) plus a host import for console1576 write, OR K12 pty surface that owns fd 0/1/2 properly.1577 1578 - **Cross-process pipe** (parent + child via K9-B31579 spawn-with-actions). K9-B3's spawn-actions apply1580 ``DUP2``/``CLOSE`` on a clone of the parent's1581 ``fdTableRegistry`` entry; with kernel-side1582 ``files_struct``, the spawn-actions must read from the1583 parent's ``current->files`` and write to the child's fresh1584 ``files_struct`` via ``wasm_syscall`` — K11-C1.f cleanup1585 scope. K9 ``hello-k9-pipe`` demo continues to work via the1586 SAB-RPC path until the cleanup pass deletes that path.1587 1588K11-C1.d — DONE1589 ``__NR_setpgid`` (109) / ``__NR_getpgrp`` (111) / ``__NR_setsid``1590 (112) / ``__NR_getpgid`` (121) / ``__NR_getsid`` (124) routed1591 through ``vmlinux.exports.wasm_syscall``. Kernel-side1592 ``kernel/sys.c::sys_setpgid`` etc. mutate1593 ``current->signal->pids[PIDTYPE_PGID/PIDTYPE_SID]`` against the1594 per-process ``signal_struct`` from K11-C1.a.1595 1596 Notes on what landed1597 ~~~~~~~~~~~~~~~~~~~~1598 1599 - ``hardwarejs/src/worker.mjs::routeToWasmSyscall`` predicate1600 extended to include all five pgrp/sid syscall numbers.1601 1602 - Demo: ``wackywasm-tools/tests/hello-k11-c1d-pgrp.c``. Single1603 process. Probes each syscall and emits return value via1604 stdout for the test to scrape.1605 1606 - Vitest: ``hardwarejs/test/k11-c1d-pgrp-kernel.test.ts``.1607 Observed at first green run (running as /init, pid=1):1608 1609 .. code-block:: text1610 1611 getpid = 11612 getpgrp_before = 0 (init_struct_pid -> 0)1613 getpgid_0_before = 01614 getsid_0_before = 01615 setpgid_0_0 = 0 (success — kernel succeeded)1616 getpgrp_after_setpgid = 1 (kernel mutated pgrp to current pid!)1617 setsid = -1, errno=EPERM(1) (already pgrp leader)1618 getsid_0_after = 0 (sid unchanged)1619 wasmSyscallStats = {109:1, 112:1, 121:3, 124:2}1620 1621 - Note: musl wasm32 implements ``getpgrp()`` as1622 ``syscall(SYS_getpgid, 0)``, so ``__NR_getpgrp`` (111) does1623 NOT appear in stats even though the demo called ``getpgrp()``.1624 The predicate still forwards both numbers; absence from stats1625 is musl's implementation choice.1626 1627 - Surprising find: setpgid + getpgrp DOES propagate1628 upstream-clean even under R-K11-W5 (no alloc_pid). The kernel1629 ``change_pid`` path mutates the pid_link inside init_struct_pid1630 such that subsequent ``getpgrp`` reads back the caller's pid1631 correctly. Cross-process pgrp semantics (``kill -pgid``) still1632 require R-K11-W5 retirement.1633 1634 Deferrals carried by K11-C1.d1635 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~1636 1637 - ``setpgid(other_pid, ...)`` for non-current targets returns1638 ``-ESRCH`` from the kernel (``find_task_by_vpid`` returns1639 NULL under R-K11-W5). K11-C1 close ledger documents this as1640 a non-blocker for K11 (no demo exercises the cross-process1641 setpgid form).1642 1643 - Cross-process pgrp delivery (``kill(-pgid, sig)`` reaches every1644 task whose ``signal->pids[PIDTYPE_PGID]`` matches): blocked on1645 R-K11-W5 retirement. K9-C2 ``hello-k9-pgrp.c`` demo continues1646 to work via the SAB-RPC path with ``processGroupRegistry``1647 until K11-C1.f cleanup deletes that path.1648 1649K11-C1.e — DONE (docs-only — defer-to-K12 ledger entry)1650 Stdin handling stays JS-direct through K11 close. K9-C1 substrate1651 (``hardwarejs/src/stdinChannel.ts`` + per-tid SAB ring +1652 ``__NR_read`` SAB-RPC handler for fd 0 in1653 ``kernelSpawnHandler.ts::dispatchSyscall``) retained through1654 K11-C1.f cleanup; K11-C1.f deletes the wait4 / pipe / fd / pgrp1655 registries but explicitly preserves the stdin substrate.1656 1657 Filed as ``docs/ARCHITECTURE.md §15.3 R-K11-W7`` with retirement1658 signal pointing at K12 ``pty-model.rst`` (``pty_init`` +1659 ``n_tty_line_discipline`` + ``tty_open`` + per-tid pty fd1660 installation in ``current->files``).1661 1662 Acceptance test for the deferral: K9-C1 stdin demo1663 (``wackywasm-tools/tests/hello-k9-stdin.c``) +1664 ``hardwarejs/test/k9-c1-stdin.test.ts`` continue to pass after1665 K11-C1.f cleanup. The K11-D1 audit pass checks this explicitly.1666 1667 Net K11-C1 close-ledger statement: K11-C1 retires the wait4 /1668 pipe / dup2 / dup3 / close / setpgid / getpgrp / setsid /1669 getpgid / getsid R-K9-1 sub-shortcuts via wasm_syscall routing1670 + per-process task / files / signal allocation. Stdin sub-1671 shortcut survives until K12.1672 1673K11-C1.f — DONE1674 Cleanup pass. The four JS-direct registry source files1675 DELETED outright: ``hardwarejs/src/childExitRegistry.ts``,1676 ``hardwarejs/src/pipeRegistry.ts``,1677 ``hardwarejs/src/fdTableRegistry.ts``,1678 ``hardwarejs/src/processGroupRegistry.ts``. Their1679 ``hardwarejs/src/index.ts`` exports + the corresponding1680 ``KernelSpawnHandlerOptions`` fields removed.1681 1682 ``hardwarejs/src/kernelSpawnHandler.ts::dispatchSyscall``1683 cases DELETED outright (along with their syscall-number1684 constants): ``__NR_wait4`` (61), ``__NR_pipe`` (22),1685 ``__NR_pipe2`` (293), ``__NR_dup2`` (33), ``__NR_dup3`` (292),1686 ``__NR_close`` (3), ``__NR_setpgid`` (109),1687 ``__NR_setsid`` (112), ``__NR_getpgid`` (121),1688 ``__NR_getpgrp`` (111), ``__NR_getsid`` (124),1689 ``__NR_getppid`` (110), ``__NR_wasm_spawn_image_actions``1690 (1025).1691 1692 The ``__NR_kill`` negative-pid (process-group) branch1693 DELETED — negative pid now returns ``-ENOSYS`` from1694 ``dispatchSyscall``. Cross-process ``kill -pgid`` filed as1695 §15.3 row R-K11-W9 (K12 retirement alongside R-K11-W51696 ``alloc_pid`` retirement).1697 1698 ``__NR_kill`` (positive-pid only), ``__NR_tkill``,1699 ``__NR_tgkill``, and ``__NR_rt_sigaction`` cases STAY at1700 K11-C1.f as the SAB-RPC fallback for K8 vitests that don't1701 yet pass ``vmlinuxModule``. Filed as §15.3 row R-K11-W101702 (deferred K8-test migration; K11-D2 retirement signal1703 pins the empty short-circuit set ``{}``).1704 1705 ``__NR_read`` / ``__NR_write`` / ``__NR_writev`` cases1706 collapsed: fd 0 routes through ``stdinChannel`` directly1707 (no ``fdTableRegistry`` lookup); fd 1/2 routes through1708 ``opts.onUserOutput`` directly; any other fd returns1709 ``-EBADF``. The ``writeToFd`` helper retained but stripped1710 to a 4-line surface (``fd 1/2 -> onUserOutput; else -9``).1711 1712 K9-B3 / K9-C1(b) / K9-C2 demos + their vitests DELETED:1713 1714 * ``wackywasm-tools/tests/hello-k9-pipe.c``1715 * ``wackywasm-tools/tests/hello-k9-pipe-child.c``1716 * ``wackywasm-tools/tests/hello-k9-pgrp.c``1717 * ``wackywasm-tools/tests/hello-k9-pgrp-child.c``1718 * ``hardwarejs/test/k9-c1-wait.test.ts``1719 * ``hardwarejs/test/k9-c1-pipe.test.ts``1720 * ``hardwarejs/test/k9-c2-pgrp.test.ts``1721 1722 ``wackywasm-tools/Makefile`` K9-C1(b) pipe-demos and K9-C21723 pgrp-demos blocks replaced with deletion-marker comments1724 pointing at §15.3 rows R-K11-W8 and R-K11-W9 respectively.1725 1726 ``hello-k9-stdin.c`` + ``hardwarejs/test/k9-c1-stdin.test.ts``1727 STAY (R-K11-W7 deferral). The vitest was updated to remove1728 ``makeChildExitRegistry`` / ``makeFdTableRegistry`` /1729 ``makePipeRegistry`` imports — fd 0 read now relies on1730 ``stdinChannel`` alone; with no channel installed, fd 01731 returns EOF.1732 1733 ``hello-k9-wait.c`` + the K11-C1.b ``hardwarejs/test/1734 k11-c1b-wait4-kernel.test.ts`` STAY (R-K9-2 ``wasm_spawn_image``1735 is the spawn primitive — separate retirement track —1736 and ``wait4`` runs kernel-side now).1737 ``childExitRegistry`` reference removed from the K11-C1.b1738 test.1739 1740 ``signalHandler.ts::deliverSignalToTarget`` retained as the1741 shared primitive. Its only K11-C1.f callers are (a) the1742 K8-B2 kernel-ring consumer and (b) the R-K11-W10 SAB-RPC1743 fallback ``__NR_kill`` / ``__NR_tkill`` / ``__NR_tgkill``1744 arms. K11-D2 deletes (b) once the K8 vitests migrate.1745 1746 Regression sweep: 56 vitest files, 141 passed + 2 skipped1747 (full vitest run-all). Every K11-C1.* test green; K11-B61748 K8-vitest fallback signal tests green; K10-A4 initcall1749 dispatch + single-Worker invariant green; K9-C1 stdin1750 green (with-channel + EOF-when-no-channel); K11-C1.b1751 ``__NR_wait4`` kernel routing green.1752 1753 §15.3 rows filed at K11-C1.f: R-K11-W8 (shell-pipeline1754 cross-process fd-inheritance K12 retirement), R-K11-W91755 (cross-process ``kill -pgid`` K12 retirement), R-K11-W101756 (K8-vitest migration to per-Worker vmlinux path).1757 1758 R-K9-1 STATUS FLIP: was PARTIAL-CLOSE-AT-K11-C1.b/c/d; now1759 CLOSED-AT-K11-C1.f modulo R-K11-W7 stdin deferral and the1760 three succession rows above. The K11-D2 close-ledger commit1761 re-validates by spot-checking the deleted-file list +1762 re-running the full vitest sweep.1763 1764K11-C21765 R-K9-3 retirement: user-Worker exit messages route through1766 kernel signal delivery; ``WIFSIGNALED+WTERMSIG`` works.1767 1768K11-C31769 K7/K8/K9 demo regression sweep against the in-Worker syscall1770 path. Every prior demo still passes.1771 1772K11-D1 (DONE — commit a589d7d)1773 Smoke probe: a real user process spawns, calls ``SYS_getpid``1774 via the libc wrapper, the K11-A3+B2a path routes through1775 ``vmlinux.exports.wasm_syscall`` to kernel-side ``sys_getpid``,1776 returns ``current->tgid > 0``, exits 0. The vitest1777 (``hardwarejs/test/k11-d1-smoke.test.ts``) gates1778 ``wasmStats[__NR_GETPID] >= 1`` — proving the K11 axis1779 end-to-end (no SAB-RPC fallback). First-execution wiring gap1780 fixed in the same commit: ``getpid`` was not in the1781 ``worker.mjs::routeToWasmSyscall`` allowlist (the predicate is1782 an explicit per-syscall allowlist, NOT a default-route-all1783 branch) so it had been falling through to SAB-RPC; added1784 ``__NR_getpid`` guarded on ``perProcessTaskAddr != 0``. Not a1785 refutation — a K11-D1 wiring completion the opener anticipated.1786 1787K11-D2 (DONE — this commit; tag v0.1.0-rc12)1788 Close ledger + Appendix A flip + ``docs/ARCHITECTURE.md`` §141789 (K11 → CLOSED, K11.5 row added) + §15 amendments + tag1790 v0.1.0-rc12. Honest close per §20.6.1 (see "K11 CLOSE" history1791 entry below): the opener's "R-K8/9 retire at K11 close" framing1792 over-claimed; the actual close is axis-landed-with-named-1793 deferrals, decomposed in §14 + §15. Cumulative refutation count1794 **0 / ≤ 3**.1795 1796--------------------------------------------------------------------------------179713.5. K11.5 sub-phase decomposition (continuation cleanup pass)1798--------------------------------------------------------------------------------1799 1800K11.5 is the SAME AXIS as K11 (the in-Worker syscall path),1801split out for scope discipline per directive D and the §20.6.11802retroactive dependency check at the K11-C1.f audit. It is NOT a1803new K-phase mission. Budget ≤ 2. Sub-phases shrunk to ACTUAL1804remaining work (the opener's A1..A5 estimate is superseded —1805most of A1..A5 already landed in K11-C1.b..C1.f):1806 1807K11.5-A1 (R-K9-3 wstatus encoding)1808 Pop ``stash@{0}`` (the K11-C2 carryover): POSIX1809 ``WIFSIGNALED+WTERMSIG`` wstatus encoded at child-exit-notify1810 time via a new ``SYS_wasm_exit_by_signal`` (1027) syscall; musl1811 ``__wasm_signal_default_terminate`` calls it instead of1812 ``exit_group(128+sig)``; ``wait.c`` encodes1813 ``wstatus = signaled ? (code & 0x7f) : ((code & 0xff) << 8)``.1814 Rebuild libc + vmlinux. Retires R-K9-3. The K11-C2 child binary1815 calls ``SYS_wasm_exit_by_signal(15)`` directly to route AROUND1816 the cross-Worker raise mis-target (R-K11-W12) — W12 is NOT1817 retired here, only sidestepped.1818 1819K11.5-A2/A3/A4 (narrowed cross-process registry restoration) — DONE (``67ac022``)1820 RESTORED narrowed ``pipeRegistry.ts`` (146) /1821 ``fdTableRegistry.ts`` (149) / ``processGroupRegistry.ts`` (79)1822 (DELETED at K11-C1.f) to cross-process responsibilities ONLY —1823 each < 150 lines per directive D-narrow. Re-added ONLY the1824 cross-process consumer call sites1825 (``__NR_wasm_spawn_image_actions`` at1826 ``kernelSpawnHandler.ts:2014``, ``__NR_kill`` pid<=0 at1827 ``:2147``, ``__NR_getppid`` at ``:2268``); did NOT re-add the1828 in-process short-circuits (those stay deleted — kernel-side at1829 C1.c/.d). Registries are optional ``KernelSpawnHandlerOptions``1830 fields, so non-registry callers are unchanged.1831 1832 .. note:: **§20.5.3 drift fix (K11.5-D2).** A prior revision of1833 this section mapped the W-rows as "W8 = cross-process pipe1834 substrate, W9 = fd inheritance + fd 0/1/2 host-side stdio,1835 W10 = cross-Worker kill(-pgid) + getppid". That was a1836 PRE-IMPLEMENTATION GUESS that drifted from the authoritative1837 ``docs/ARCHITECTURE.md §15.3`` rows (filed at C1.f, matching1838 the code comments). Per the R-K8-2 ruling — *the committed1839 §15 ledger wins over any handoff or doc* — the authoritative1840 mapping is:1841 1842 * **R-K11-W8** = cross-process **fd inheritance /1843 ``spawn_image_actions``**, served JOINTLY by the restored1844 ``pipeRegistry.ts`` + ``fdTableRegistry.ts``.1845 * **R-K11-W9** = cross-Worker **``kill(-pgid)`` member1846 enumeration + ``getppid``** via the restored1847 ``processGroupRegistry.ts`` (``getppid`` folded into W9 per1848 the K11.5-A3 decision — it has no §15.3 home of its own).1849 * **R-K11-W10** = the **K8-vitest ``dispatchSyscall``1850 fallback arms** (``rt_sigaction`` + kill family). NOT a1851 registry citation — it is the A5 item.1852 1853 The live file:line citations live in §15.3 R-K11-W8/W9/W10.1854 1855K11.5-A5 (verify dispatchSyscall fallback shape) — DONE (option b)1856 Confirmed the ``__NR_rt_sigaction`` (``kernelSpawnHandler.ts:1890``)1857 + kill-family (``:2147``) fallback arms are reachable ONLY by K81858 vitests without ``vmlinuxModule`` — ``worker.mjs:808–809`` routes1859 ``rt_sigaction`` to ``wasm_syscall`` unconditionally for vmlinux1860 Workers, and ``worker.mjs:810–816`` routes tkill/tgkill always +1861 kill pid>0. DECISION: KEEP the arms (R-K11-W10 stays OPEN,1862 retirement → K12); the six-K8-vitest migration risks the ≤ 21863 budget. The ``{}`` claim is RE-SCOPED (not global): it applies to1864 the in-process kernel-owned syscall set for the K11-process-Worker1865 path, NOT to the W8/W9 cross-process arms re-added at A2/A3/A4 nor1866 the W10 K8-vitest arms.1867 1868K11.5-D1 — DONE (``1a01ba4``)1869 Deterministic registry smoke probe1870 (``hardwarejs/test/k11-5-d1-registry-smoke.test.ts``) exercising1871 the W8 fd-inheritance clone + action-list + pipe refcount + byte1872 transfer + EOF/EPIPE substrate and the W9 inheritance-only pgrp /1873 parent map (kill(-pgid) walk + getppid). The kernel-side combined1874 round-trip (wait4 + pipe2 + read/write + setpgid against kernel1875 code) is already proven by the C1.b/C1.c/C1.d kernel vitests; the1876 full cross-WORKER pipe DATA round-trip is R-K11-W8-DEFERRED to1877 K12-A1 (no kernel ``copy_from_user_for``).1878 1879K11.5-D2 — DONE1880 Close ledger. R-K9-3 → CLOSED-AT-K11.5-A1. R-K11-W8/W9/W10 gained1881 live file:line citations (see §15.3). §20.5.3 drift fix above1882 reconciles this section's W-row mapping to §15.3. R-K11-W12 stays1883 OPEN (K12-A1). Cumulative refutation count 0 / ≤ 2. Tag1884 ``v0.1.0-rc12.5`` (rc13 reserved for K12 close).1885 1886--------------------------------------------------------------------------------188714. Refutation budget for K111888--------------------------------------------------------------------------------1889 1890**K11 refutation budget: ≤ 3.**1891 1892Reasoning per §20.9 three-input formula:1893 18941. K10 actual: 0. K10 was the smallest K-phase since K1 (the1895 only sub-phase landed was K10-A1..A4 = initcall dispatch).1896 18972. Surface novelty: K11 is structurally novel on four axes:1898 1899 (i) Kernel-stack-swap entry sequence has never been1900 exercised. ``arch/wasm32/kernel/entry.c::wasm_syscall``1901 may need substantial wasm32-specific work beyond the1902 §6 sketch.1903 1904 (ii) Populating ``sys_call_table`` will surface upstream1905 syscalls that don't currently link on wasm32 — each is1906 a per-syscall Kconfig / stub / §15 decision.1907 1908 (iii) K8 / K9 kernel-side code (currently unreachable)1909 surfaces first-execution bugs. Expected and acceptable1910 but counts.1911 1912 (iv) First-time true SMP under load. Spinlock contention1913 patterns, RCU grace-period progress under multiple1914 concurrent Workers may surface upstream-clean code that1915 was authored against assumptions wasm32 violates1916 (e.g., ``cpu_relax`` semantics, ``smp_mb`` cost).1917 19183. Single-axis discipline: K11's axis is "the in-Worker syscall1919 path comes online." The four risk sites are all on that1920 axis. The budget reflects the high novelty + four risk1921 sites.1922 1923If K11 closes at 0–1 actual, the in-Worker path landed cleaner1924than expected. If K11 closes at 4+, K12 should re-budget1925(pty/ldisc/jobctrl work depends on the in-Worker path being1926solid).1927 1928The fourth surface (true SMP under load) may surface late, in1929the K7/K8/K9 regression sweep at K11-C3. Budget pivot per1930``bisect-postmortems.rst`` §20.5 if it does — the budget1931re-evaluation must record the surfaced upstream-clean code path1932and the wasm32-specific divergence.1933 1934--------------------------------------------------------------------------------193515. Acceptance criteria (K11 close)1936--------------------------------------------------------------------------------1937 1938K11 closes when:1939 1940(a) ``arch/wasm32/kernel/entry.c::wasm_syscall`` performs the1941 kernel-stack swap (§6) on every entry / exit. Probe asserts.1942 1943(b) ``sys_call_table`` is populated; every ``__NR_*`` referenced1944 by K7/K8/K9 demos + K12 pty/ldisc/jobctrl wrappers links1945 upstream-clean OR is on the explicit -ENOSYS stub list with1946 a §15 row.1947 1948(c) ``arch_cpu_up`` / ``arch_cpu_down`` light up CPUs 1..N at1949 spawn / down at exit. ``cpu_online_mask`` reflects reality.1950 1951(d) ``num_vcpu`` parameter wired end-to-end. Boot with1952 ``num_vcpu=4`` shows ``num_online_cpus() == 1`` at boot,1953 increments to 2 / 3 / 4 as process Workers spawn.1954 1955(e) ``max_wasm_instances`` probe + accounting + -EAGAIN1956 enforcement. Eviction deferred to v0.2 §15 row.1957 1958(f) R-K8-1 / R-K8-2 / R-K9-1 / R-K9-2 (partial) / R-K9-31959 retired in ``docs/ARCHITECTURE.md`` §15. The five rows'1960 closure notes record the wrong-shaped retirement signal1961 correction.1962 1963(g) R-K10-W1 inverted: assertion now reads "every process1964 Worker carries a vmlinux instance"; runtime1965 ``kernelInstanceCount`` check inverted; source-static guard1966 test inverted.1967 1968(h) Regression: every K7 / K8 / K9 demo passes against the1969 in-Worker syscall path. The R-K8-W1 flake is tracked1970 separately and counted as the same row.1971 1972(i) ``make hwjs-verify`` clean.1973 1974(j) Cumulative K11 refutation count ≤ 3.1975 1976(k) Appendix A populated.1977 1978(l) v0.1.0-rc12 annotated tag placed.1979 1980(m) K12 surface proposal in K11 close ledger.1981 1982--------------------------------------------------------------------------------198316. K10 close ledger amendment + scope correction proposal1984--------------------------------------------------------------------------------1985 1986K10's original mission was "Terminal / pty + line discipline +1987job control + initcall dispatcher" (per the K10 opener doc).1988K10's load-bearing sub-axis was the initcall dispatcher (the1989"means to enable" the terminal subsystems); the terminal1990subsystems themselves were folded under K10's umbrella because1991the K10 opener author anticipated they would ride the existing1992JS-direct syscall surface (the R-K9-1 pattern). The K10-B11993architectural-fork investigation surfaced that anticipation as1994wrong against ``ARCHITECTURE.md`` §4 — the terminal subsystems1995require the in-Worker syscall path that K11 lands.1996 1997Two scope-correction options:1998 1999(i) Insert K10-B0 (= K11 work) between K10-A4 and K10-B1.2000 K10-B1..K10-D2 as planned, on top of working syscall path.2001 Refutation budget becomes K10's existing ≤ 2 + the K112002 ≤ 3 estimate — total ≤ 5 inside K10's umbrella.2003 Single-axis discipline loses (the in-Worker syscall path2004 is qualitatively different from terminal-layer work; one2005 K-phase trying to land both blurs the axis).2006 2007(ii) Pause K10 at K10-A4 close. K10's mission re-scopes to2008 "the initcall dispatcher landed" — which is the2009 load-bearing sub-axis K10 actually needed. Original2010 K10-B/C/D becomes K12 "PTY + line discipline + job control2011 on the in-Worker syscall path", with its original ≤ 22012 budget intact (against pty-specific risk only). K11 opens2013 between K10 close and K12 open.2014 2015**Recommended: (ii).** Single-axis discipline favours splitting.2016K11 stays within its own ≤ 3 budget; K12 keeps its original2017≤ 2 budget against PTY-specific risk. The K-phase ledger gains2018one row but loses no work; the v0.1.0-rc11 / -rc12 / -rc13 tag2019sequence is consistent with the K-phase shape.2020 2021**K-phase ledger amendments under option (ii):**2022 2023* K10 row (currently "OPEN (A-sub-phase closed; B-sub-phase2024 next)") → CLOSED at v0.1.0-rc11. Mission re-scoped to "real2025 initcall dispatcher; ``__initcall<level>_start/_end`` walks2026 via Strategy B post-link bracket pass; pty / n_tty / devpts2027 subsystems registered at boot." Original "Terminal / pty +2028 line discipline + job control" mission moves to K12.2029 2030* K11 row (NEW) → OPEN. Mission: "Per-Worker vmlinux + real2031 SMP comes online. ``arch/wasm32/kernel/entry.c::wasm_syscall``2032 performs kernel-stack swap; ``sys_call_table`` populated;2033 ``arch_cpu_up`` / ``arch_cpu_down`` hooks; ``num_vcpu`` /2034 ``max_wasm_instances`` JS parameters; R-K8-1/2 + R-K9-1/2/32035 retired; R-K10-W1 inverted." Tag v0.1.0-rc12.2036 2037* K12 row (NEW) → PINNED. Mission: original K10-B/C/D —2038 "Terminal / pty + line discipline + job control on the2039 in-Worker syscall path." Tag v0.1.0-rc13. Budget ≤ 2.2040 2041* D1 row unchanged.2042 2043--------------------------------------------------------------------------------204417. §20.6.1 retroactive dependency-check2045--------------------------------------------------------------------------------2046 2047The K10-A2b commit landed a §11.2.1 clarification stating2048"vmlinux is single-Worker by architectural construction." That2049commit cited ``docs/ARCHITECTURE.md`` §2 + §17 as authority. The2050citation was incomplete: §3 line 124 and ``memory-model.rst``2051§1.1 specify the OPPOSITE — vmlinux is per-Worker.2052 2053By §20.6.1, every "deferred to phase N+1" claim (and by2054extension, every architectural rationale baked into a §15 row)2055needs a written dependency-check. The K10-A2b §11.2.1 claim2056landed without one. The architecture-vs-implementation drift2057check that should have fired:2058 2059::2060 2061 CLAIM: "vmlinux.wasm is single-Worker by architectural2062 construction (per ARCHITECTURE.md §2 + §17)."2063 2064 DEPENDENCY-CHECK (REQUIRED but absent at K10-A2b):2065 Walk every doc site that describes vmlinux instance2066 cardinality and confirm consistency:2067 - ARCHITECTURE.md §2 ← cited (consistent)2068 - ARCHITECTURE.md §3 ← NOT CITED, contradicts the2069 claim (line 124: "A new instance2070 of vmlinux.wasm is *also*2071 instantiated in this Worker")2072 - ARCHITECTURE.md §4 ← NOT CITED, presumes per-Worker2073 vmlinux (lines 178-186)2074 - ARCHITECTURE.md §17 ← cited (§17.6 says SMP is real,2075 which presumes multiple kernel2076 instances writing kernel2077 memory concurrently)2078 - memory-model.rst §1.1 ← NOT CITED, contradicts the2079 claim ("Every Worker — every2080 process Worker and the2081 dedicated vmlinux Worker —2082 holds its own2083 WebAssembly.Instance")2084 - memory-model.rst §1.2 ← NOT CITED, presumes2085 per-Worker kernel instance2086 2087 HAD THE DEPENDENCY-CHECK FIRED: the §11.2.1 claim would2088 have been refuted at K10-A2b commit time. The K10-A3 fork2089 answer would have been §11.D (per-Worker vmlinux + SMP)2090 rather than the §11.2.1 framing. The R-K10-W1 row would2091 have been filed with the corrected rationale. The K10-A42092 runtime + source-static assertions would have been written2093 in their inverted form from the start.2094 2095 REFUTATION COST PAID: K10-A4 close included the wrong-2096 shaped R-K10-W1 row + the runtime + source-static2097 assertions pinning the wrong invariant. K11 corrects all2098 three. The cost is one K-phase doc-amendment cycle plus2099 the assertion inversion at K11 close. Acceptable but2100 avoidable.2101 2102 LESSON: §20.6.1 dependency-checks for architectural2103 rationales need a "every doc site that describes the2104 invariant" enumeration, not just citations to the sites2105 the author found compelling. The discipline is the same2106 as §20.5 external-tool probes: enumerate exhaustively,2107 classify carefully.2108 2109The §20.5 amendment that lands as part of this opener doc2110adds an "architecture-vs-implementation drift check" to the2111external-tool probe surface. The check is: every §15 row that2112asserts an architectural rationale gets reviewed by walking2113every doc site indexed under the relevant doc-cluster2114table-of-contents, and each site is classified2115"consistent / contradicts / orthogonal." A "contradicts" hit2116either fixes the row OR gets a documented rebuttal in the row2117text. Probe at K11-A2: the source-site snapshot tool gains2118a flag ``--architectural-rationale-check`` that runs the2119walk against the §15 rows.2120 2121--------------------------------------------------------------------------------2122Appendix A — K11 close checklist2123--------------------------------------------------------------------------------2124 2125[X] K11-A1 probes pass (§stack pointer global access, current->2126 stack resolves, THREAD_SIZE consistent)2127[X] K11-A2 source-site snapshot landed; every __NR_* enumerated2128[X] K11-A3 wasm_syscall stack-swap implemented (§6)2129[X] K11-A4 K7/K8/K9 regression sweep against new entry sequence2130 (123 passed + 2 R-K8-W1 flakes at commit d64ffc0)2131[X] K11-B1 sys_call_table populated; -ENOSYS stub list +2132 R-K11-N rows landed2133[X] K11-B2 arch_cpu_up / arch_cpu_down + per-CPU wake words2134 (B2a per-Worker vmlinux + B2b cpu_id substrate)2135[X] K11-B3 num_vcpu wired end-to-end2136[X] K11-B4 max_wasm_instances probe + accounting + -EAGAIN2137 (eviction deferred to v0.2 §15 row)2138[X] K11-B5 R-K8-1 do_sigaction reached for K11 process Workers2139 (CLOSED-FOR-K11-AXIS; residual K8-vitest arm → R-K11-W10)2140[~] K11-B6 R-K8-2 self-target CLOSED (kernel sys_kill/tkill/2141 tgkill reached); CROSS-TARGET deferred to K12 (R-K11-W12,2142 gated on alloc_pid / R-K11-W5)2143[X] K11-C1 R-K9-1 retired for in-process (C1.a..C1.f: kernel2144 wait4/pipe/dup/close/pgrp/sid; registries DELETED; cross-2145 process residue → R-K11-W8/W9/W10); stdin → R-K11-W7 (K12)2146[ ] K11-C2 R-K9-3 retired — NOT DONE (stashed; moves to K11.5-A1)2147[X] K11-C3 K7/K8/K9 demo regression sweep (folded into A4 +2148 the C1.* kernel-path vitests)2149[X] K11-D1 smoke probe: getpid round-trip via wasm_syscall2150 (wasmStats[__NR_GETPID] >= 1; commit a589d7d)2151[X] K11-D2 close ledger + Appendix A flip + tag v0.1.0-rc12 +2152 K12 surface proposal (this commit)2153 2154[X] R-K10-W1 INSTANTIATION-INVERTED: every process Worker2155 carries vmlinux; processVmlinuxInstanceCount enforces; active-2156 overlay idempotency verified (k11-b2a-per-worker-vmlinux.test.ts).2157 SAB-RPC-deletion is progressive (K11.5/K12), not a K11-D2 flip.2158[ ] terminal-model.rst §11.2.1 / §11.B — NOT re-edited at K112159 close per directive ("terminal-model.rst keeps its K10-scoped2160 content"); the K11/K11.5 split is a §20.5.3 drift candidate2161 noted in this ledger, not a terminal-model.rst edit.2162[X] docs/ARCHITECTURE.md §14 K-phase ledger: K11 → CLOSED;2163 K11.5 row added2164[X] docs/ARCHITECTURE.md §15: R-K8-1 CLOSED-FOR-K11-AXIS;2165 R-K8-2 refined three-way PARTIAL-CLOSE; R-K9-1/R-K9-2 closed2166 for in-process; R-K10-W1 CLOSED-AS-INVERTED; R-K9-3 stays2167 OPEN (K11.5-A1); R-K11-W12 FILED; R-K11-W10 retirement → K11.52168 2169--------------------------------------------------------------------------------2170History2171--------------------------------------------------------------------------------2172 2173K11 OPENER (this commit, post-K10-A4-close)2174 Document created. Per-Worker vmlinux contract pinned.2175 In-Worker wasm_syscall entry sequence concretised from2176 memory-model.rst §1.2. sys_call_table population strategy2177 declared. arch_cpu_up / arch_cpu_down hooks specified.2178 num_vcpu and max_wasm_instances parameters introduced.2179 R-K10-W1 inversion plan recorded. R-K8-1 / R-K8-2 / R-K9-12180 / R-K9-2 / R-K9-3 retirement plan recorded. Sub-phase2181 decomposition pinned. Budget ≤ 3 declared. K10 close2182 ledger amendment (option (ii) — pause K10 at K10-A4-close,2183 open K11, defer original K10-B/C/D to K12). §20.6.12184 retroactive dependency-check on the K10-A2b commit2185 recorded. NO CODE LANDS until this doc and the companion2186 amendments to terminal-model.rst + ARCHITECTURE.md are2187 reviewed and the scope-correction option (i) vs (ii) is2188 resolved.2189 2190K11 CLOSE (K11-D2; tag v0.1.0-rc12)2191 Axis claim LANDED: the in-Worker syscall path comes online.2192 ``arch/wasm32/kernel/entry.c::wasm_syscall`` performs the2193 kernel-stack swap (K11-A3); ``sys_call_table`` is populated for2194 the K7/K8/K9 + musl surface (K11-B1); ``arch_cpu_up`` /2195 ``arch_cpu_down`` + per-Worker ``cpu_id`` substrate land (B2a/B2b);2196 ``num_vcpu`` (B3) and ``max_wasm_instances`` + -EAGAIN (B4) are2197 wired end-to-end. A curated per-syscall allowlist routes through2198 ``vmlinux.exports.wasm_syscall`` (rt_sigaction, kill/tkill/tgkill2199 self+positive-target, wait4, pipe/pipe2/dup2/dup3/close,2200 setpgid/setsid/getpgid/getpgrp/getsid, getpid). The K11-D1 smoke2201 probe gates getpid round-trip (``wasmStats[__NR_GETPID] >= 1``).2202 2203 Cumulative refutation count: **0 / ≤ 3**. The W-rows2204 (R-K11-W2..W12) are watched invariants, not refutations; the2205 R-K11-1/2/3 -ENOSYS slots are stub decisions, not refutations.2206 2207 §20.6.1 RETROACTIVE DEPENDENCY CHECK (the honest-close note):2208 the K11 opener claimed "R-K8-1 + R-K8-2 + R-K9-1 + R-K9-22209 (partial) + R-K9-3 retire at K11 close." The K11-C1.f audit2210 REFUTED the completeness of that claim — per-process2211 ``task_struct`` allocation (C1.a) and the C1.b..f routing landed2212 the IN-PROCESS surface, but (1) R-K9-3 wstatus encoding was never2213 done (the K11-C2 work is stashed), (2) cross-process pipe/fd/pgrp2214 substrate cannot retire without kernel-side ``copy_from_user_for``2215 + ``alloc_pid`` (K12-A1), and (3) R-K8-2 cross-target signal2216 delivery mis-resolves under the deferred pid hash (R-K11-W12).2217 Per directive D the phase splits into K11 (axis landing, this2218 close) + K11.5 (R-K9-3 + narrowed-registry restoration) — SAME2219 AXIS, continuation for scope discipline, NOT a new mission. The2220 K11 budget is unaffected (the split is a §20.6.1 dependency-check2221 outcome, not a refutation).2222 2223 ACTUAL R-ROW CLOSE STATE (committed ARCHITECTURE.md §15 is the2224 authority — see the §20.5.3 drift note below):2225 * R-K8-1 — CLOSED-FOR-K11-AXIS (kernel do_sigaction reached;2226 no cross-target dimension); residual K8-vitest arm → R-K11-W10.2227 * R-K8-2 — self-target CLOSED-AT-K11-B6; cross-target2228 DEFERRED-TO-K12 (R-K11-W9 + R-K11-W12, gated on alloc_pid /2229 R-K11-W5); K8-vitest arms → R-K11-W10. (Three-way decomposition2230 in §15.)2231 * R-K9-1 / R-K9-2 — CLOSED for in-process; cross-process residue2232 in R-K11-W8/W9/W10; childImageRegistry stays (K12-A1 signal).2233 * R-K9-3 — OPEN, retirement K11.5-A1.2234 * R-K10-W1 — CLOSED-AS-INVERTED (instantiation inversion +2235 active-overlay idempotency verified). SAB-RPC remains the2236 DEFAULT route for non-allowlisted syscalls; its deletion is2237 progressive (K11.5/K12), not a K11-D2 flip.2238 * R-K11-W12 — FILED at K11-D2 (cross-Worker raise mis-target).2239 2240 §20.5.3 DRIFT FINDING: the handoff doc2241 ``docs/HANDOFF-K11-CLOSE.md §3.4`` drifted from the committed2242 ARCHITECTURE.md §15 R-K8-2 row. The handoff asserted "R-K8-2 =2243 FULL-CLOSE per the 8:40 reframing, JS-direct callers deleted at2244 C1.f"; HEAD contradicts this on two independent grounds — (1) the2245 JS-direct ``deliverSignalToTarget`` arms are LIVE at2246 ``kernelSpawnHandler.ts`` (NOT deleted), (2) the committed R-K8-22247 row says cross-target stays JS-direct gated on alloc_pid (K12).2248 The 8:40 "full close" message was a GUI-corruption artifact (the2249 user confirmed the cursor follow-up channel was broken in that2250 window; the prior agent flagged it as a possible stale replay).2251 RESOLUTION: the committed §15 ledger wins; the handoff §3.42252 R-K8-2 line is superseded. This is the SECOND GUI-corruption2253 near-miss this phase (the first was K11/K11.5 re-litigation);2254 both argue for treating the committed §15 ledger — never a2255 handoff or a chat replay — as the single source of truth for2256 R-row status at tag time.2257 2258 K12 SURFACE PROPOSAL: K12-A1 lands the kernel substrate that2259 retires the K11/K11.5 W-rows — ``copy_from_user_for(tid, ...)``2260 (R-K11-W8 pipe r/w against another task's user_memory),2261 ``wasm32_kill_pgrp`` arch shim (R-K11-W9/W10 kill(-pgid)),2262 ``alloc_pid`` / ``attach_pid`` + ``find_task_by_vpid`` resolution2263 (R-K11-W5 / R-K11-W12 cross-target signal delivery; R-K8-22264 cross-target close), kernel-side ``spawn_with_actions`` fd2265 inheritance, and ``getppid``. Host-side fd 0/1/2 stdio wiring2266 lands as part of K12-B PTY work. Budget ≤ 2 against PTY-specific2267 risk only.