243 lines · plain
1.. SPDX-License-Identifier: GPL-2.02 3====================================================================4Conventions for post-link wasm rewrite passes5====================================================================6 7This document is the single home for the project's growing set of8"post-link surgical rewrite" tools that operate on ``vmlinux.wasm``9between ``wasm-ld`` and the runnable artifact handed to HardwareJS.10 11The conventions in this document are referenced from12``docs/ARCHITECTURE.md`` §15 debt rows that depend on a post-link13pass, and any new post-link tool MUST be reviewed against the rules14here.15 16---------------------------------------------------------------------170. Why post-link surgery exists in this port at all18---------------------------------------------------------------------19 20The kernel image is built from a 6.12 LTS source tree that we do21not fork (§16 PR acceptance criteria). Several invariants the22kernel source assumes are normally upheld by:23 24* a GNU linker script (``vmlinux.lds.S``) consumed by ``ld``;25* ``wasm-ld`` and the rest of the LLVM toolchain;26* Binaryen's ``wasm-opt`` pass collection;27* the V8 / SpiderMonkey runtime.28 29Three of those four have gaps that we satisfy without modifying30upstream sources:31 32* ``wasm-ld`` does not consume GNU linker scripts. Boundary33 symbols like ``__per_cpu_start`` / ``__per_cpu_end`` /34 ``__initcall*_start/end`` / ``__setup_start/end`` therefore35 cannot be defined via ``vmlinux.lds.S``.36* ``wasm-opt`` has occasional bugs (e.g. the topmost-runtime37 asyncify-asserts FIXME — see ``sched-model.rst`` §3) whose fix38 upstream is months-to-years out, but whose effect on the kernel39 is immediate.40* The wasm module has no syntax for declaring a second imported41 memory in clang-emitted ``.wat`` form, despite Binaryen +42 V8 accepting the binary form. (See ``mm-model.rst``.)43 44For each, the K-phase boundary is "kernel proceeds further" and45not "upstream toolchain catches up," so the project lands a46surgical post-link rewrite that yields the desired ``vmlinux.wasm``47shape without forking the source.48 49---------------------------------------------------------------------501. The hierarchy of post-link tools51---------------------------------------------------------------------52 53::54 55 vmlinux.a (LLVM IR / wasm objects, no symbols outside arch/wasm32)56 |57 v wasm-ld + scripts/link-vmlinux-wasm.sh58 vmlinux.wasm.linked59 |60 v (optional) wasm-opt --asyncify --pass-arg=asyncify-asserts61 vmlinux.wasm.asyncify-with-asserts62 |63 v (optional) scripts/asyncify-strip-topmost-asserts.py64 vmlinux.wasm.asyncify65 |66 v (planned K5/K6) scripts/wasm-percpu-bracket.py67 vmlinux.wasm.percpu68 |69 v (planned K5/K6) scripts/wasm-initcall-bracket.py70 vmlinux.wasm.initcall71 |72 v scripts/wasm-add-memory.py73 vmlinux.wasm <-- the artifact HardwareJS loads74 75Pass order matters; the order above is fixed by an invariant each76pass would otherwise violate. New passes MUST declare which pass77they depend on (read-after) and which depend on them (write-before)78in their module docstring.79 80---------------------------------------------------------------------812. Substitutive vs. additive — which shape your pass is82---------------------------------------------------------------------83 84Every post-link pass falls into one of two shapes:85 86**Substitutive (the default, lower-risk):**87 88 Modifies bytes in-place, **never changing byte count**. Code/data89 section sizes, function-body sizes, and absolute offsets of every90 byte AFTER the rewrite point are preserved. This means: no LEB12891 re-encoding of section lengths, no function-body length92 recomputation, no relocation cascade across the rest of the93 binary, no DWARF offset shifts.94 95 ``scripts/asyncify-strip-topmost-asserts.py`` is the current96 representative. Its strategy: locate the 9-byte assertion pattern97 ``[0x23 LEB(state) 0x20 LEB(oldState) 0x47 0x04 0x40 0x00 0x0B]``98 and overwrite all 9 bytes with ``0x01`` (nop). Stack effect is99 preserved (``global.get`` and ``local.get`` push 2 i32s; the100 ``i32.ne`` / ``if`` / ``unreachable`` / ``end`` chain consumes101 them; replacing the WHOLE sequence with nops drops both pushes102 too, net stack effect = 0).103 104 Same-byte-count nop substitution preserving stack semantics is105 the **preferred shape** for any rewrite that is logically a106 "delete this fragment" operation. Pads with nops; arithmetic107 unchanged; offsets unchanged.108 109**Additive (acceptable only at section boundaries):**110 111 Inserts new content. Always changes byte count of the section112 it inserts into, which in turn re-encodes the section's LEB128113 length and shifts the section's start offset (and every byte114 after it). The pass MUST then update any offsets that crossed115 the insert point: function body length prefixes, table /116 memory / global / export references that index by ordinal (not117 affected) vs. by file offset (affected).118 119 ``scripts/wasm-add-memory.py`` is the current representative.120 It splices a second imported memory into the IMPORTS section121 (section 2). This is acceptable because:122 123 * The IMPORTS section is at the START of the module; nothing124 in CODE / DATA / EXPORTS references it by file offset.125 * All section indexes (memory 0, memory 1) are determined by126 ordinal within the new IMPORTS section, not by file offset,127 so referenced memidx values in the CODE section stay valid.128 * Names are the cross-module ABI (per129 ``docs/ARCHITECTURE.md`` §0(c)) so identity preservation is130 automatic.131 132 Additive insertion is permitted only at section boundaries133 where no internal offsets reference past the insert point.134 The pass MUST document this property in its module docstring.135 136**A pass that is logically substitutive should never expand137to additive without an explicit justification in its module138docstring** of why the additive form is necessary. Expanding to139additive cascades the cost of every following pass (each must140now recompute offsets from the new section sizes).141 142---------------------------------------------------------------------1433. Mandatory properties of a post-link pass144---------------------------------------------------------------------145 146A new post-link tool must:147 1483.1. **Produce a deterministic output** for a given input. Two149 runs of the pass against the same input bytes must yield150 byte-identical output. CI cross-checks this by re-running151 the pass and comparing.152 1533.2. **Fail loudly on input-shape mismatch.** If the pass relies154 on a specific Binaryen / wasm-ld / clang output shape (an155 export-name convention, a section ordering, a function-body156 prologue pattern), it must detect a mismatch and exit157 non-zero with a message that points the maintainer at the158 toolchain version expected vs. observed. Do not "best-159 effort" past an unexpected shape — the symptom would160 surface five subsystems later in the kernel boot.161 1623.3. **Ship a sibling probe under** ``linux-wasm/tools/``. The163 probe compiles a 1-3-function toy module, runs the same164 post-link pipeline on it, and asserts the pass's contract165 against the toy output. The probe runs in CI on every166 build. This catches Binaryen / clang / wasm-ld bumps that167 change the pass's input shape BEFORE vmlinux.wasm mysteriously168 traps. See ``docs/ARCHITECTURE.md`` §15.1 for the broader169 pattern.170 1713.4. **Document its pass-order dependencies** (read-after,172 write-before) in the module docstring.173 1743.5. **Declare its shape** in the module docstring: substitutive175 (same byte count) or additive (boundary insertion). If176 additive, declare which section it inserts into and why no177 internal-offset references cross the insert point.178 1793.6. **Reference a §15 row** that owns the debt the pass exists180 to satisfy. If the upstream gap closes, the §15 row closes181 and the pass either retires or moves into permanent182 toolchain-glue. (See §15.1 "Probe-driven debt retirement"183 for the retirement protocol.)184 185---------------------------------------------------------------------1864. Naming conventions187---------------------------------------------------------------------188 189* Live in ``linux-wasm/scripts/``.190* Filename starts with ``wasm-`` for additive / structural rewrites191 (``wasm-add-memory.py``, planned ``wasm-percpu-bracket.py``,192 ``wasm-initcall-bracket.py``).193* Filename starts with ``asyncify-`` for passes that operate on194 Asyncify-pass output (``asyncify-strip-topmost-asserts.py``,195 planned ``asyncify-whitelist.py``).196* Filename starts with ``link-`` for build-flow drivers197 (``link-vmlinux-wasm.sh``).198* Sibling probes live in199 ``linux-wasm/tools/k{N}-{pass-name}-probe/`` where N is the200 K-phase that introduced the pass.201 202---------------------------------------------------------------------2035. Listed passes (current + planned)204---------------------------------------------------------------------205 206==================================================== ============ ============ =========== =====================================207Pass Phase intro Shape Sibling probe §15 row208==================================================== ============ ============ =========== =====================================209``scripts/wasm-add-memory.py`` K1 additive — (none; permanent glue)210``scripts/wasm-inspect.py`` K2 read-only — (none; read-only diagnostic)211``scripts/asyncify-strip-topmost-asserts.py`` K4 substitutive ``k4-asyncify-asserts-probe`` "asyncify-asserts enabled with named topmost-strip exceptions"212``scripts/asyncify-whitelist.py`` K4 generator — (generates ``arch/wasm32/asyncify-whitelist.txt``)213``scripts/wasm-percpu-bracket.py`` (planned) K5/K6 substitutive (planned) "Linker-script-anchor symbols stubbed"214``scripts/wasm-initcall-bracket.py`` (planned) K6 substitutive (planned) "vmlinux.lds.S doesn't land __initcall_* ordering"215==================================================== ============ ============ =========== =====================================216 217When adding a new pass, append a row here in the same commit, or218the commit is incomplete (mirrors the §15 invariant).219 220---------------------------------------------------------------------2216. Anti-patterns222---------------------------------------------------------------------223 224* **Do not** rewrite by re-parsing → modifying → re-emitting the225 whole module. That cascades offsets and breaks every following226 pass. Operate on the smallest possible byte window.227 228* **Do not** rely on Binaryen or wasm-ld output details that229 aren't tested by the sibling probe. Toolchain bumps will230 surprise you and the surprise will arrive in vmlinux behavior,231 not in the pass itself.232 233* **Do not** silently no-op when input shape is unexpected. Fail234 loudly per §3.2.235 236* **Do not** combine multiple unrelated rewrites into one pass.237 Each pass owns one logical transform; pass ordering in §1 is238 how they compose.239 240* **Do not** mutate the input file in place. Always write a new241 output file. Build systems retry on partial failure; in-place242 mutation makes that retry corrupt the build.243