|
| 1 | += Architecture Decision Record: 0002-multi-producer-carrier-sections |
| 2 | +<!-- SPDX-License-Identifier: MPL-2.0 --> |
| 3 | +<!-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> --> |
| 4 | + |
| 5 | +# 2. Adopt Multi-Producer Carrier Sections for L2–L6 and L15 |
| 6 | + |
| 7 | +Date: 2026-05-30 |
| 8 | + |
| 9 | +## Status |
| 10 | + |
| 11 | +Accepted |
| 12 | + |
| 13 | +Promoted from link:../proposals/0001-multi-producer-carrier-section.adoc[Proposal |
| 14 | +0001] (`[accepted]` 2026-05-30). The proposal file remains canonical for |
| 15 | +wire-format detail, error variants, and producer obligations; this ADR |
| 16 | +records the decision and its consequences. |
| 17 | + |
| 18 | +## Context |
| 19 | + |
| 20 | +Before this decision, the post-codegen verifier |
| 21 | +(`crates/typed-wasm-verify/`) could enforce **L7 (aliasing)**, |
| 22 | +**L10 (linearity)**, and **L13 (module isolation, negative form)** on |
| 23 | +emitted wasm via the single `typedwasm.ownership` carrier section, but |
| 24 | +**L2 (region-binding)**, **L3 (type-compatible access)**, **L4 (null |
| 25 | +safety)**, **L5 (bounds-proof)**, **L6 (result-type)**, and |
| 26 | +**L15 (resource capabilities)** had no wire surface at all. The |
| 27 | +source-level Idris2 spec (`Region.idr`, `Pointer.idr`, |
| 28 | +`MultiModule.idr`, `ResourceCapabilities.idr`) modelled all the |
| 29 | +required structure, but the moment a producer emitted `.wasm` bytes |
| 30 | +the schema and capability data was gone — the source-level checker was |
| 31 | +the only line of defence. |
| 32 | + |
| 33 | +Two producers — AffineScript and Ephapax — share `typedwasm` as their |
| 34 | +emission target. Without a wire-level agreement, cross-language module |
| 35 | +linking (the project's load-bearing differentiator: ADR-0001 RSR |
| 36 | +template plus typed shared memory) had no enforcement story on |
| 37 | +post-link binaries. |
| 38 | + |
| 39 | +This was an **ABI** problem, not an enforcement problem: the |
| 40 | +algorithms existed; the carriers did not. |
| 41 | + |
| 42 | +## Decision |
| 43 | + |
| 44 | +Adopt two new wasm custom sections as the v1 carriers for L2–L6 and |
| 45 | +L15 schema/capability data, plus reserve two additional carriers for |
| 46 | +the access-site and cross-module follow-ups: |
| 47 | + |
| 48 | +[cols="1,2,3"] |
| 49 | +|=== |
| 50 | +| Section name | Carries | Status |
| 51 | + |
| 52 | +| `typedwasm.regions` |
| 53 | +| Region schemas (fields × types × nullability × cardinality) |
| 54 | +| Adopted v1 (this ADR); realised in `crates/typed-wasm-verify` PR #107 |
| 55 | + |
| 56 | +| `typedwasm.capabilities` |
| 57 | +| Capability sets per function signature |
| 58 | +| Adopted v1 (this ADR); realised in `crates/typed-wasm-verify` PR #109 |
| 59 | + |
| 60 | +| `typedwasm.access-sites` |
| 61 | +| Per-instruction `(region_id, field_id)` for typed load/store |
| 62 | +| Promoted in link:0003-access-site-carrier.adoc[ADR-0003] |
| 63 | +(realises Open Question §5 of proposal 0001) |
| 64 | + |
| 65 | +| `typedwasm.region-imports` |
| 66 | +| Cross-module schema agreement (foreign-key regions) |
| 67 | +| `[draft]` at link:../proposals/0003-region-imports-carrier.adoc[Proposal |
| 68 | +0003]; tracks issue |
| 69 | +https://github.com/hyperpolymath/typed-wasm/issues/95[#95] |
| 70 | +|=== |
| 71 | + |
| 72 | +Wire-format detail (encoding, LEB128 boundaries, error variants, |
| 73 | +acceptance criteria) lives in proposal 0001 §"Wire format" and §"Error |
| 74 | +variants". Producer obligations and canonical emit order live in |
| 75 | +proposal 0001 Appendix B ("Producer-readiness checklist") and |
| 76 | +§"Producer obligations". |
| 77 | + |
| 78 | +Key adopted pins: |
| 79 | + |
| 80 | +* **WBool wire width** is **4 bytes** (`i32.store` / `i32.load`) |
| 81 | + matching both shipping producers; the Idris2 `sizeOf WBool` is |
| 82 | + aligned to 4 in PR #113. A 1-byte `WBoolPacked` is reserved as a |
| 83 | + separate `WasmType` constructor, not a width flag on `WBool`. |
| 84 | +* **LEB128 per field** (encoding option B) for all variable-width |
| 85 | + integer fields in the new carriers — uniform with existing wasm |
| 86 | + conventions. |
| 87 | +* **Cardinality-1 producer surface in v1**: both shipping producers |
| 88 | + emit exactly one of each carrier per module; multi-section behaviour |
| 89 | + is reserved for a future amendment. |
| 90 | +* Carriers are **opt-in via Cargo features** (`unstable-l2`, |
| 91 | + `unstable-l15`) until Phase 3. |
| 92 | +
|
| 93 | +## Consequences |
| 94 | + |
| 95 | +### Positive |
| 96 | + |
| 97 | +- Verifier can re-check L2–L6 and L15 on emitted bytes; the |
| 98 | + source-level checker is no longer the only line of defence. |
| 99 | +- Producers in different source languages (AffineScript, Ephapax, |
| 100 | + future) can interoperate through typed shared memory with a single |
| 101 | + wire contract. |
| 102 | +- The carrier-based design generalises: ADR-0003 (access-sites) and |
| 103 | + proposal 0003 (region-imports) and proposal 0004 (capability-grants) |
| 104 | + all reuse the same custom-section pattern. |
| 105 | +- Idris2 spec and Rust verifier converge on a single source of truth |
| 106 | + per carrier (mapping recorded in proposal 0001 Appendix A |
| 107 | + "Wire-vs-Idris2-spec mapping"). |
| 108 | +- Producer-readiness checklist (proposal 0001 Appendix B) gives both |
| 109 | + shipping producers a concrete acceptance test before v1 stabilises. |
| 110 | +
|
| 111 | +### Negative |
| 112 | + |
| 113 | +- Two carriers add ~400-line emitter surface per producer. Both |
| 114 | + AffineScript codegen and Ephapax codegen now have a per-feature debt |
| 115 | + to track (AffineScript Roadmap C1/C2/C3; Ephapax |
| 116 | + `Codegen.region_stack` dead-code removal). |
| 117 | +- Until Phase 3 stabilisation (and feature-gating drop), verifier |
| 118 | + consumers must opt in via `unstable-l2` / `unstable-l15`. |
| 119 | +- Adding two carriers in v1 means the wire-format error surface grows |
| 120 | + by ~12 variants total (proposal 0001 §"Error variants"); each |
| 121 | + variant needs a regression test before stabilisation. |
| 122 | +
|
| 123 | +### Neutral |
| 124 | + |
| 125 | +- The existing `typedwasm.ownership` carrier (4-kind ownership per |
| 126 | + function signature) is unchanged; this ADR is purely additive on |
| 127 | + the wire. |
| 128 | +- L7, L10, L13 (negative form) enforcement is unaffected — the |
| 129 | + ownership carrier still drives them. |
| 130 | +- ADRs 0001 (RSR template) and 0002 (this) are orthogonal: this |
| 131 | + decision lives at the wasm-ABI layer; RSR lives at the |
| 132 | + repo-structure layer. |
| 133 | +
|
| 134 | +## Cross-references |
| 135 | + |
| 136 | +* link:../proposals/0001-multi-producer-carrier-section.adoc[Proposal |
| 137 | + 0001] — full wire-format spec (canonical reference) |
| 138 | +* link:0003-access-site-carrier.adoc[ADR-0003] — access-site carrier |
| 139 | + (companion decision) |
| 140 | +* link:../proposals/0003-region-imports-carrier.adoc[Proposal 0003] — |
| 141 | + region-imports carrier (cross-module, `[draft]`) |
| 142 | +* link:../proposals/0004-capability-grants-carrier.adoc[Proposal 0004] |
| 143 | + — capability-grants carrier (L15-C, `[draft]`) |
| 144 | +* https://github.com/hyperpolymath/typed-wasm/issues/34[issue #34] — |
| 145 | + tracking issue (closed when proposals 0001+0002 reached `[accepted]`) |
| 146 | +* https://github.com/hyperpolymath/typed-wasm/issues/50[issue #50] — |
| 147 | + Phase 2 (multi-producer adoption) |
0 commit comments