Repository navigation
codegen: emit typedwasm.access-sites carrier (typed-wasm proposal 0002 Gate 6) #462
Description
Context
typed-wasm proposal 0002 (access-site carrier) reached [review] 2026-05-30 via hyperpolymath/typed-wasm PR #104. The codec + verifier pass + spec doc landed in:
- typed-wasm PR #107 — codec (
parse_/build_access_sites_section_payload) - typed-wasm PR #108 — spec doc §"Wire-Level Carrier Sections"
- typed-wasm PR #109 —
verify_access_sites_from_modulepass
Promoting proposal 0002 to [accepted] requires §Gate 6: cross-repo codegen issues filed against producers. Reviewed and signed off via #449; this issue is the codegen counterpart.
Wire format
Per typed-wasm/docs/proposals/0002-access-site-carrier.adoc §"Wire format":
u16le version (= 1)
u32le_leb128 entry_count
for each entry (in producer-emission order):
u32le_leb128 func_idx (index into wasm function section)
u32le_leb128 instruction_byte_offset (offset within function body bytecode;
FIRST byte of typed access opcode)
u32le_leb128 region_id (foreign key into typedwasm.regions
region table)
u32le_leb128 field_id (foreign key into that region's
field table)
Custom-section name: typedwasm.access-sites (companion to typedwasm.regions and typedwasm.ownership).
Producer obligations (proposal 0002 §"Producer obligations")
- Emit AFTER any post-codegen wasm rewrite (
wasm-opt,snip,wasm-gc, custom passes). Theinstruction_byte_offsetfield is fragile — any pass that reorders, removes, or inserts instructions desyncs the offset space. Emitting before such a pass produces an unverifiable module; the verifier flagsAccessSiteMisalignment. - Companion
typedwasm.regionsis mandatory — theregion_id/field_idfields are dangling pointers otherwise. v1 verifier rejects access-sites without regions withMissingDependentCarrier(now enforced by typed-wasm#109). - Stable region indices across both sections (proposal 0001 §"Producer obligations" ¶2).
- All-or-nothing per module: entry for EVERY typed access in EVERY function, OR for NONE. Partial emission triggers
AccessSiteUnboundon unlisted accesses. - NOT emit entries for non-typed accesses (e.g., raw memory the producer chose to leave outside the type system). An entry is a producer assertion that the access IS typed.
Where this lands in affinescript
The AffineScript codegen pipeline emits typedwasm.ownership today at lib/codegen.ml (build_ownership_section, called at codegen.ml:~2778). The new section emit point is the same vicinity, alongside the regions/capabilities emit points to be added per proposal 0001 codegen issue:
- New helper:
build_access_sites_section : (int * int * int * int) list -> bytes(mirroring the existingbuild_sectionshape; LEB128 per field). - Hook into the codegen pass: every typed access (
region.get $r .f,region.set $r .f, …) records its emission position. The hook needs the post-rewrite byte offset within the function body, so the access-list construction has to happen AFTERwasm-opt(or whatever final-passlib/codegen.mlruns). - Emit
typedwasm.access-sitesAFTERtypedwasm.regionsin the section order (matching the verifier's two-section dependency, though section order is technically free).
Tw_section.encode dedup intersects here
#444 extracts Tw_section.Encode from the existing 2-copy build_section pattern in preparation for proposal 0001's regions + capabilities sections. Adding access-sites makes it 4 sections × 2 copies = 8 encoders without the dedup — even more reason to do #444 first, or fold this work into the same Tw_section.Encode module:
module Tw_section : sig
module Encode : sig
val ownership : Tw_ownership_section.t -> bytes
val regions : Tw_regions_section.t -> bytes (* from #444's prep *)
val capabilities : Tw_capabilities_section.t -> bytes (* from #444's prep *)
val access_sites : (int * int * int * int) list -> bytes (* this issue *)
end
module Decode : sig
(* symmetric *)
end
endAcceptance
-
Tw_section.Encode.access_sitesand.Decode.access_sitesimplemented (round-trip property test passes). - Codegen pass emits
typedwasm.access-sitesAFTER post-codegen rewrite, with one entry per typed access. - Region IDs match
typedwasm.regions(cross-section consistency). - All-or-nothing: a module either emits a complete access-sites section or omits the section entirely (the source-language toggle is producer-defined; recommend tying it to the
--emit-typedwasm-regionsflag). - End-to-end smoke: emit an
.affinetest program with multiple typed accesses; feed the resulting.wasmtotyped-wasm-verify(withunstable-l2); confirmverify_access_sites_from_modulereturnsOk(vec![]). - Fixture: at least one
.affinetest that exercises access-sites alongside regions; commit the expected.wasmandtypedwasm.access-sitesbytes for regression detection.
Sequencing
This issue is OPEN but not blocking the typed-wasm [accepted] flip — per the proposal 0001 / 0002 acceptance criteria, "cross-repo codegen issues filed" is a Gate-6-satisfied state, not "codegen shipped." Sequence after #444 (encoder dedup) lands.
Related
- typed-wasm#34 (proposal 0001 umbrella) / typed-wasm#78 (proposal 0002 RFC)
- typed-wasm#106 (
[review] → [accepted]roadmap tracker) - affinescript#444 (Tw_section encoder dedup — prerequisite cleanup)
- affinescript#449 (proposal 0002 review request — satisfied)
- ephapax companion issue (filed concurrently)
🤖 Filed 2026-05-30 to satisfy proposal 0002 §Gate 6 producer-side acceptance criterion.
Activity
hyperpolymath commentedon May 30, 2026
Codegen scoping (local exploration — not a PR)
Read-only walk of the AffineScript codebase to scope what `typedwasm.access-sites` emission involves. typed-wasm proposal 0002 is now `[accepted]` 2026-05-30 + promoted to typed-wasm ADR-0003; this is the producer-side gate.
Typed load/store codegen sites in `lib/codegen.ml`:
These all consult `field_layouts : (string * (string * int) list) list` from the codegen context (line 42), populated at lines 1729 + 2249 — variable-level field metadata (name → offset).
Custom-section infrastructure is already present and proven:
The actual encoder + assembly work is ~25 LOC:
The blocker is upstream: AST nodes for typed access currently carry no `(region_id, field_id)` — they're resolved at typecheck (`lib/borrow.ml` or adjacent), recomputed locally from `field_layouts` at codegen, and the semantic binding is lost between the two passes. To emit access-sites, the codegen needs `(region_id, field_id)` threaded through to every `i32.load` / `i32.store` emission site. Options:
Most natural is probably (1) — minimal AST churn since the field/tuple-index AST variants already carry per-site data, just no region+field IDs.
Honest complexity assessment: multi-file refactor, blocked on the typechecking→codegen threading. The encoder + tests are a single-day slice; the threading is the bulk of the work and touches `lib/borrow.ml` (or wherever the resolution happens) + `lib/codegen.ml` field-emission sites.
Posting this as scoping data so the issue carries a concrete plan. Not filing the encoder PR — it would land cleanly but `Encode.access_sites` with no codegen feeder is just dead code. The threading PR should land first or alongside.
🤖 Generated with Claude Code
docs(tw_section): update stale typed-wasm proposal 0001 reference (ac…
hyperpolymath commentedon May 30, 2026
Handoff brief now available
Self-contained handoff brief for this work landed at typed-wasm
docs/developer/handoff-typed-access-sites-codegen.adoc(filed via typed-wasm#122). Covers both this issue and the Ephapax counterpart ephapax#251, since the blocker (typecheck → codegen threading of(region_id, field_id)) is shared.The brief includes:
Direct link: https://github.com/hyperpolymath/typed-wasm/blob/main/docs/developer/handoff-typed-access-sites-codegen.adoc