Repository navigation
Review request: typed-wasm carrier-section proposal for L2–L6 / L15 enforcement (typed-wasm#76) #402
Description
Activity
Review: Proposal 0001 wire-format coverage from affinescript producer side
Evidence-based answers to the four review questions, citing current affinescript codegen state (
lib/codegen.ml,lib/wasm.ml,lib/tw_ownership_section.ml,lib/tw_verify.ml,lib/tw_interface.ml). Verdict per question + seam-risk flag + producer-asymmetry note.1. WasmType coverage (11-variant enum)
Verdict: Covered (narrow) — 5 of 11 used, including WBool as i32.
lib/wasm.ml:11-16definesvalue_type = I32 | I64 | F32 | F64(no narrow ints, no v128, no reference types).lib/codegen.ml:180-190(type_to_wasm) maps every AffineScript type into those 4 wasm value types.WBoolis emitted asI32(line 183:TyCon id when id.name = "Bool" -> Ok I32). No 1-byte packing.- No
U8/U16/U32/U64/I8/I16codegen paths; no SIMD; noexternref/funcref.
2. PtrKind coverage (4-variant enum)
Verdict: Exact fit — all 4 kinds map 1:1 to AffineScript qualifiers.
lib/tw_ownership_section.ml:30-34defines the 4-kind enumUnrestricted | Linear | SharedBorrow | ExclBorrowmatching proposal'sScalar | PtrOwning | PtrBorrow | PtrExclusive(with the renameScalar↔Unrestrictedreconciled intw_interface.ml).- AffineScript surface qualifiers (
own,ref,mut) map toLinear,SharedBorrow,ExclBorrowrespectively; scalar parameters useUnrestricted. - No
&mut-equivalent or other modes outside this set exist in codegen today.
3. Variable-length cardinality
Verdict: Fixed-only — N/A for variable-length at v0.
- Array layout is
[length: I32][elem0][elem1]...(perlib/codegen.ml:1235), with statically-known cardinality at type definition time. - AffineScript has no dependent types; all records are fixed-shape.
- The proposal's
n>1(fixed-array) case is reachable from affinescript if/when array fields land;0(unbounded/dynamic-bounds) and1(single value) cover today's emission.
4. Capability indices ordering
Verdict: Not emitted yet —
typedwasm.capabilitiessection absent from codegen.- No capability table, no per-function required-capability emission path exists in
lib/codegen.mlorlib/tw_ownership_section.ml. tw_ownership_section.ml:77-96iterates ownership annotations in source order without sorting. When capabilities land, aList.sort_uniq compareat build time will satisfy strictly-increasing trivially.
Seam risk discovered while reviewing
build_sectionis duplicated between producer and verifier copies:lib/codegen.ml:159-177defines the producer-side section assembly (called atcodegen.ml:2778).lib/tw_ownership_section.ml:77-96defines an identical-encoding section assembly, used by the verifier-side reader (tw_interface.ml).
Both currently encode the same fixed format, so this is not a correctness seam today, but:
- It is a merge-risk seam if either changes unilaterally.
- The proposal adds two more sections (
typedwasm.regions,typedwasm.capabilities) — replicating this pattern would create a 3× duplication problem.
Suggestion for landing the proposal: extract a single
Tw_section.encodemodule that producer and verifier both consume. Worth filing as a separate prep-work issue before the proposal's codegen plumbing lands. I'll file it as a sibling.Cross-cutting observations (seam-analyst hat on)
- v0 producer surface is narrower than the spec. Across affinescript (this review) and ephapax (docs: refresh #135 triage (r2) after slices 5-8 #165), no producer emits v128/SIMD, 128-bit ints, reference types, capabilities, or variable-cardinality fields at v0. The
wasm_tybyte's 11 variants are forward-compat headroom — cheap to keep, worth flagging as "reserved at v0" so consumers don't preallocate handling for codepoints no producer will hit. - Producer asymmetry: affinescript exposes all 4
PtrKinds in codegen; ephapax (per docs: refresh #135 triage (r2) after slices 5-8 #165) exposes only 2 (Linear,Unrestricted). The verifier's contract for partial carriers (proposal lines 169–172: "Either emit all fields of all regions it accesses, or emit none") covers this gracefully — affinescript will emit dense, ephapax will emit sparse, both are spec-conforming.
Bottom line
Proposal 0001 is sound from affinescript's perspective. The wire format covers AffineScript's emission needs and forward-compat headroom is reasonable. The duplicate-
build_sectionseam is a prep-work item, not a proposal defect.Greenlight from affinescript for proposal-0001 promotion to
[accepted].
Refs hyperpolymath/typed-wasm#34 hyperpolymath/typed-wasm#76 hyperpolymath/ephapax#165
Review: Proposal 0001 (multi-producer carrier sections) + Proposal 0002 (access-site carrier)
Reviewing for affinescript-the-producer (
lib/tw_*.ml+lib/codegen.ml). I've covered both proposals together because proposal 0002 (typed-wasm PR #86, merged 2026-05-28) landed after this review-request was filed and answers proposal 0001's Open Question §5 — the two now have to be assessed jointly. Surveyed affinescriptmainat the file:line refs cited below.Headline verdict. The wire formats in both proposals are sound and emittable from affinescript in principle. Adopting them in practice will require producer-side IR work that affinescript has explicitly Roadmap-deferred (
borrow.ml:1724-1730defers Polonius-surface region inference to Roadmap C1; capability tracking is C2). Recommendation: promote 0001 + 0002 from[draft]→[review](wire-format-validated for affinescript), with explicit acknowledgement that affinescript L2 enforcement on emitted wasm is gated on Roadmap C1 / C2 / a new C3 (access-site instrumentation).
§1 — Section name rename (typed-wasm#65) status
Rename to
typedwasm.ownershipis COMPLETE in all active source files. Every emitter and consumer inlib/,bin/,test/,tests/uses"typedwasm.ownership". Survivingaffinescript.ownershipstrings appear only in frozen historical records (.machine_readable/6a2/STATE.a2ml,META.a2ml,docs/history/,CHANGELOG.md) describing the section's name at a prior commit — i.e., historical record, not active code.Adjacent observation:
affinescript.tea_layout(inlib/tea_bridge.ml,lib/tea_cs_bridge.ml,lib/tea_router.ml) is a separate, AffineScript-private custom section in theaffinescript.*namespace that has not been renamed. This is correct — typed-wasm never claimed that name; it is genuinely AffineScript-specific. Worth a one-line note in proposal 0001 §"Producer obligations" that producers MAY emit private sections under their own namespace alongside thetypedwasm.*set, to avoid future confusion.No seam mismatch. ✅
§2 — Section codec readiness (the easy lane)
The custom-section infrastructure is
Wasm.wasm_module.custom_sections : (string * bytes) list(wasm.ml:260) — additive list entries, no structural constraint. Addingtypedwasm.regionsandtypedwasm.capabilitiesbuilders+parsers is mechanical.Adjacent gap (worth surfacing to AffineScript maintainers): there is a duplicate
build_ownership_sectionimplementation. The extraction landed per Tranche A3 (2026-05-24) intolib/tw_ownership_section.ml:77-96, butlib/codegen.ml:151-177still contains a verbatim copy ANDcodegen.ml:2778calls the codegen.ml copy, not the extracted one. Both are alive; only one is reachable. This is a small but real source-of-truth seam — if proposal 0001 wire format ever evolves, both copies would have to be kept in sync until the codegen.ml copy is deleted. Recommend filing an affinescript follow-up to delete the codegen.ml duplicate and redirect line 2778 to callTw_ownership_section.build_section. Not gating on this proposal but exactly the kind of latent inconsistency that bites multi-section maintainers later.Section codec verdict: ✅ no objection. Wire format is clean for affinescript to emit. Suggest fixing the duplicate-builder seam first as a defence against per-section maintenance drift.
§3 — Answers to the four explicit questions in #402
Q1 —
WasmTypeenum mapping (11 variants)type_to_wasm(lib/codegen.ml:180-190) maps every source type to one of{I32, F64}only:Bool, Int, Char, Nat, TyCon, TyApp, TyTuple, TyRecord, TyArrow→I32Float→F64- catch-all →
I32
I64,F32are present inWasm.value_type(wasm.ml:13-16) andI64Const/F32Constinstructions are partially generated, buttype_to_wasmnever produces them for source types. Narrow widths (U8/U16/I8/I16) are entirely unused —I32Load8U/I32Store8etc. exist inWasm.instr(wasm.ml:54-68) butcodegen.mlgenerates zero uses of any narrow load/store.Critical for the proposal:
Boollowers to a 4-byteI32, not a 1-byte store.LitBoolemitsI32Const 0l / 1l(codegen.ml:373); subsequentI32Storewrites a full 4-byte word. The proposal'sWasmTypeenum hasWBool = 10as a discrete variant but does not specify whetherWBoolis 1-byte or 4-byte on the wire. AffineScript would emit it as 4-byte i32. Recommend the proposal pin this explicitly in §"Wire format":WBool: producer choice (i32.store / i32.store8 / i32.store16). Verifier validates that the load width matches the store width within a function and that the value never exceeds 0/1 by store-narrowing.
Without this, AffineScript will emit one width and a future producer might emit a different width and the verifier has no canonical answer.
Coverage conclusion: the 11-variant set covers AffineScript's current emission with massive room to spare — AS emits exactly 2 of 11. No reserved encoding slot needed for AS-specific types.
Q2 —
PtrKinddistinction at emissionTracked at the function-parameter level only, not propagated to memory fields. The
ownership_annotstable in thecontextstruct (lib/codegen.ml:22-67) carriesownership_kindper function parameter, populated atgen_declTopFndispatch (codegen.ml:2381-2397). The four kinds (Unrestricted, Linear, SharedBorrow, ExclBorrow) correspond to proposal's(Scalar, PtrOwning, PtrBorrow, PtrExclusive).However: the distinction is invisible at the memory-field layer. There is no
ptr_kind_mapin thecontext. All pointers lower toI32heap addresses; the source-levelTyOwn/TyRef/TyMutdistinctions surface in the borrow checker and as ownership_kind annotations on function parameters, but are not attached to memory fields. Thefield_layouts/struct_layoutstables incodegen.mltrack byte offsets and widths, not ptr-kind.Producer-side gap: emitting
typedwasm.regionswith per-fieldPtrKindannotations would require extending the layout tables with ptr-kind metadata that survives lowering. This is straightforward (add a column tofield_layouts) but is not free — it's a deliberatecodegen.mlchange touching every struct emission site.Not a proposal-blocker. Filing a sub-issue against affinescript ("propagate
PtrKindtofield_layouts") would be a useful precondition for AS to start being an L2-conforming producer.Q3 — Variable-length region fields
None today. Struct emission uses fixed 4-byte-per-field offsets (
i * 4) throughoutcodegen.ml. All struct fields have static, fixed-width layouts. Cardinality=0 (unbounded) has no current equivalent in AffineScript emission and no infrastructure for it.The proposal's "downgrade to L5-runtime-check" default for unbounded fields is fine; AffineScript will never exercise that path in v1 — all emitted regions will have
cardinality ≥ 1.Q4 — Strictly-increasing capability indices
No capability tracking in AffineScript codegen or type checker. The
capabilitytype inlib/backends/architecture.ml:30is a backend-capability enum (SIMD support, threads, etc. — selects backend features) and is unrelated to typed-wasm capability semantics.lib/borrow.ml:1725places session-types + capability tracking under Roadmap item C2 ("not yet started").Emitting sorted, distinct
u32le[]per function would be entirely new work. The format itself is fine — sort+dedup at emit-time is trivial — but the source of the data does not yet exist in AffineScript's pipeline.Producer-side gap, not a proposal-blocker. Filing a sub-issue against affinescript ("kick off Roadmap C2 — capability tracking →
typedwasm.capabilitiesemission") would be useful pre-emptive scoping work.
§4 — The Roadmap-deferred elephant
This is the strategic finding the original #402 questions don't surface. Proposals 0001 + 0002 expect three families of IR data that AffineScript has explicitly Roadmap-deferred:
-
Region inference.
lib/borrow.ml:1724-1730documents that origin/region variables (Polonius surface) are deferred under Roadmap C1, ADR-gated, and not yet in M1. The borrow checker today uses lexical lifetimes only. Thecontextrecord incodegen.mlhas noregion_annots/region_map. There is no region IR at any pipeline stage.typedwasm.regionscannot be populated until C1 lands — and C1 is not currently scheduled. -
Capability tracking. Roadmap C2 — not started. (§3-Q4 above.)
-
Access-site mapping (proposal 0002). No instruction-to-region mapping.
I32Loadinstructions emit inline ingen_exprwith computed offsets (codegen.ml:506, 566, 678, 920, 1028, 1033, 1566, 1600, 1640) and no annotation linking the load back to a region/field. Threading site metadata through the codegen pass is a separate, large change — let's call it Roadmap C3.
The proposal correctly anticipates this in 0002's "Producer obligations" §1 ("AFTER any post-codegen wasm rewrite"), but the producer-obligations sections of both 0001 and 0002 understate the IR readiness required. Recommend adding a §"Producer readiness checklist" to both proposals — (a) stable region-id allocation, (b) field schema IR with PtrKind, (c) access-site instrumentation surviving post-codegen rewrite. AffineScript's existing Roadmap items C1 / C2 / (new) C3 map onto exactly this list and would let language teams scope their adoption explicitly.
For affinescript specifically: the v1 verifier surface (carrier-section parsing + lenient-reader, behind
unstable-l2etc.) can be wired up immediately because that's verifier-side, not producer-side. Actually emitting populatedtypedwasm.regionsfrom AffineScript is downstream of C1.
§5 — Proposal 0002 — access-site carrier
The wire format (LEB128 per field, ~5 B/access, ~1.1% module overhead) is the right choice for AffineScript — AS modules tend to be larger with denser typed access (more aggressive struct/record usage than ephapax), and encoding B's per-access overhead saves real bytes vs A's flat width.
Three specific concerns for AffineScript:
-
"AFTER any post-codegen wasm rewrite" producer obligation (0002 §"Producer obligations" ci: Bump actions/setup-node from 4.0.2 to 6.3.0 #1). AffineScript does not currently invoke
wasm-optor any post-codegen rewriter; the section can be emitted directly afterCodegen.gen_module. However: any futurewasm-opt-style pass added to the AS pipeline would have to be co-designed with access-site emission (re-emit the section after rewrite, or maintain an offset-mapping side-table through the rewrite). The proposal mentionsAccessSiteMisalignmentas the failure mode but does not specify a way for the producer to detect drift proactively. Recommend: acrates/typed-wasm-verify/lint that flags access-site offsets falling in the middle of multi-byte opcodes (probable sign of a rewrite-without-re-emit), and a corresponding AS producer-side smoke test intests/. Not gating; defensive. -
"NOT required to be sorted in v1" (0002 §"Wire format" notes). AS codegen emits in deterministic source order, which would happen to be sorted by
(func_idx, instruction_byte_offset)by construction. Free ordering in v1 is fine; the "future v2 MAY require sorted" hedge means AS's natural emission is already v2-compatible — recommend documenting this explicitly in 0002 so future producers don't accidentally regress by reordering. -
Producer obligation ci: Bump dtolnay/rust-toolchain from f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 to efa25f7f19611383d5b0ccf2d1c8914531636bf9 #2 ("Emit a
typedwasm.regionssection in the same module"). For AS, this means C1 must land before C3 makes sense — emit-order in the rollout. This is internally consistent for AS; just flagging that the proposal's strict dependency could be a sequencing constraint other producers might want to challenge. Suggest "or the verifier accepts access-sites without regions in a lenient mode" as an alternative path for incremental rollout — but accepting your "MissingDependentCarrier hard-error" stance for v1 is fine if access-sites isn't valuable without regions anyway.
§6 — Cross-references already covered
- Versioning policy (
u16le versionfixed-width, lenient additive, breaking-bumps-version): correct for AS. Thetw_ownership_section.mlcodec extends toversion: u16trivially. - Independent optionality: correct; matches AS's stance for
typedwasm.tea_layout(private AS section is optional + parallel to typedwasm.* set). - L13 cross-module Open Question §5 in 0001 (region-id stability across module boundaries): not yet relevant for AS single-module emission but flag for when AS multi-module + cross-language imports happens — AS's module-import strategy will need to interact with this. Suggest filing typed-wasm sub-proposal 0003 (
typedwasm.region-imports) proactively rather than discovering it later when AS+ephapax cross-import lands. - ADR-020 (v2
0xAFsentinel + version byte) fortypedwasm.ownership: AS verifier-side parse support is shipped; emit-flip deferred per Q-001 ledger. Stays unaffected by proposals 0001 / 0002.
§7 — Open questions back to typed-wasm
WBoolwidth. 1-byte or 4-byte on the wire? (§3-Q1 above.) AS emits 4-byte; ephapax would too (cross-validated in docs: refresh #135 triage (r2) after slices 5-8 #165). Proposal must pin a canonical answer or specify per-function-store-width validation.- Producer-readiness contract. §4 above — recommend a
§Producer-readiness checklistappended to 0001 (and 0002 referencing it). typedwasm.capability-grants(L15-C deferred). Is there a tracking issue yet? If not, suggest filing now so AS can scope C2 + a future C2.5 (per-call-site grants) together.- Multi-section emit ordering. When AS emits
typedwasm.ownership+typedwasm.regions+typedwasm.access-sites+typedwasm.capabilities, is the emit-order specified or free? Suggest fixed order(ownership, regions, capabilities, access-sites)matching their dependency graph, for consumer predictability — but a "any order" stance is also fine if documented.
Recommendation
[draft]→[review]for both proposals, conditional on §7 open questions being addressed (or explicitly deferred with tracking issues filed).AffineScript has no objection to the wire formats. The producer-side IR work to actually populate
typedwasm.regionsfrom AffineScript is gated on Roadmap C1; capability tracking on C2; access-site instrumentation on a (new) C3. Recording these explicitly in the proposals' producer-obligations sections would be honest expectation-setting and let AS plan its adoption schedule.Filing this comment satisfies AffineScript's gating role per 0001 §"Coordination with downstream producers."
Refs: typed-wasm#34, typed-wasm#76 (proposal 0001 PR, merged), typed-wasm#78 (access-site carrier RFC, resolved), typed-wasm#86 (proposal 0002 PR, merged), typed-wasm#50 (Phase 2 tracking), affinescript Roadmap C1 / C2.
Producer-side citations (all on affinescript
mainHEAD, 2026-05-30):lib/codegen.ml:22-67, 151-177, 180-190, 373, 506, 566, 678, 920, 1028, 1033, 1566, 1600, 1640, 2381-2397, 2778lib/tw_verify.ml:90-105, 124-154, 169-181, 218-303, 314-337, 343-359lib/tw_interface.ml:75-80lib/tw_ownership_section.ml:44-48, 77-96lib/borrow.ml:1724-1730lib/backends/architecture.ml:30wasm.ml:13-16, 54-68, 260
🤖 Reviewer context: typed-wasm + affinescript cross-repo review for proposal-0001 + 0002, filed 2026-05-30 by Claude Opus 4.7 on behalf of @hyperpolymath per #402's gating request.
2026-05-30 — PR #104 filed: proposals 0001 + 0002 status
[draft] → [review]Promoting both proposals (0001 via this issue's gating role; 0002 via #449's) from
[draft]→[review]in typed-wasm#104. Both producer reviews on file in this issue recommended[review](not directly[accepted]), pending remaining acceptance criteria (codec landing + spec doc + cross-repo codegen).Once PR #104 lands, this issue's gating role is fulfilled. Leaving open as the producer-side tracker until full
[accepted]lands (per 0001 §Acceptance criteria 3+4+5).Adjacent CI fix typed-wasm#105 clears two pre-existing red checks (Cargo.lock dup + nextgen-languages allowlist) blocking auto-merge.
🤖 Status update from review-promotion closeout.
Closing — both proposals are now
[accepted]:- Proposal 0001 ([accepted] in typed-wasm#115) — multi-producer carrier sections for L2–L6 and L15.
- Proposal 0002 ([accepted] in typed-wasm#115; also tracked at Cross-repo review request: typed-wasm proposal 0002 (typedwasm.access-sites carrier) #449) —
typedwasm.access-sitescarrier.
Both promoted to ADRs (ADR-0002 + ADR-0003). AffineScript-side codegen follow-up is tracked at #462.
typed-wasm has filed PR #76 — proposal 0001 Multi-Producer Carrier Sections for L2–L6 and L15, tracking typed-wasm#34. AffineScript is one of two current producers of
typedwasm.ownership, so its review is gating before the proposal moves from[draft]→[review]→[accepted].Why you specifically
The proposal explicitly names AffineScript's producer touchpoints:
lib/tw_verify.ml— the OCaml reference verifier (the Rust crate is a faithful port of this).lib/tw_interface.ml— section codec on the source side.typedwasm.ownership.Any wire format we adopt has to be something AffineScript codegen can actually emit cleanly, and
tw_verify.ml's structure has to remain a sensible parity oracle.What the proposal adds
Two new producer-neutral custom sections alongside the existing
typedwasm.ownership:typedwasm.regions— carries L2 (region binding), L3 (type-compatible access), L4 (null safety), L5 (bounds-proof), L6 (result-type).typedwasm.capabilities— carries the L15 capability lattice (excluding L15-C per-call-site grants, which are deferred to a follow-up proposal).Both versioned, lenient-readable, same shape as the existing
section.rscodec. Field-by-field mapping toRegion.idr/Pointer.idr/ResourceCapabilities.idris in the proposal's §Proposal table.L14 (session) and L16 (choreography) are explicitly out of scope — they need surface work in AffineScript first.
Specific things to look at
WasmTypeenum mapping — 11 variants (U8…WBool) encoded asu8 wasm_ty. Does this cover every type AffineScript actually emits in region fields? IsWBoolactually a 1-byte store, or does AffineScript pack it?PtrKindencoding —0=Scalar, 1=PtrOwning, 2=PtrBorrow, 3=PtrExclusive. MatchesPointer.idrbut I want to confirm AffineScript's codegen distinguishes these at emission, not just at typing.u32le cardinality(1=single, n>1=fixed array, 0=unbounded with verifier downgrade). Does AffineScript emit any region field that's variable-length today?u32le[]of capability indices. This structurally encodesDistinctCaps. Is the strictly-increasing requirement a problem for AffineScript's codegen ordering, or is it free?Ask
Please review the wire format in PR #76 (§Proposal, ~lines 60–140) and flag anything that's missing, wrongly typed, or expensive for AffineScript to emit. Once you've signed off, I'll promote the proposal to
[review], then[accepted]and start landing the codec in the Rust verifier behindcargo feature = "unstable-l2".No code changes asked of this repo yet — codegen of the new sections is the follow-up after
[accepted], not this proposal.Refs
🤖 Generated with Claude Code
Update 2026-05-30 — Proposal 0002 landed + paired reviews completed
Two material changes since this issue was filed:
Proposal 0002 (access-site carrier) merged. typed-wasm PR ci: bump actions/checkout from 6.0.1 to 6.0.2 #86 (2026-05-28) added
docs/proposals/0002-access-site-carrier.adoc, which specifies thetypedwasm.access-sitessection (LEB128-per-field, ~5 B/access). This addresses Open Question §5 of proposal 0001 (option A — per-instruction access-site carrier). The wire format includes(func_idx, instruction_byte_offset, region_id, field_id)per typed access. Both this issue's review scope AND the ephapax paired review (docs: refresh #135 triage (r2) after slices 5-8 #165) now span BOTH proposals.Two review comments received 2026-05-30 answering the four questions above + assessing proposal 0002:
Both reviews greenlight the wire formats. Both recommend
[draft] → [review]promotion conditional on tracking issues being filed for outstanding open questions. As of 2026-05-30 the following gating issues exist:WBoolwire widthtypedwasm.region-imports) cross-module placeholdertypedwasm.capability-grants(L15-C v1.4.x) trackingTw_section.{Encode,Decode}(dedupbuild_section)Ty::BorrowAffineScript sign-off status: the two reviews together satisfy this issue's gating role per proposal 0001 §"Coordination with downstream producers." Owner action: bump proposal 0001 + 0002 status
[draft] → [review]when ready.Note that AffineScript producer-side IR work to populate
typedwasm.regionsis gated on Roadmap C1 (region inference, currently deferred —lib/borrow.ml:1724-1730);typedwasm.capabilitieson Roadmap C2; access-site instrumentation needs a new Roadmap C3. These are downstream of proposal acceptance, not blockers on it.🤖 Updated 2026-05-30 from paired-review closeout.