Skip to content

Commit 1ba8538

Browse files
feat(verify): L13 region-imports codec + self-consistency pass (proposal 0003) (#154)
## Summary Pre-stages typed-wasm **proposal 0003** (`typedwasm.region-imports` custom section) behind a new `unstable-l13-imports` cargo feature. Closes the verifier-pass slice of **#140** bullet 1. - **Codec** (`section.rs`): `RegionImportEntry` / `ImportedFieldEntry`, `parse_/build_region_imports_section_payload`, `IMPORT_TABLE_BASE` (= `0x8000_0000`). - **Public API** (`lib.rs`): `REGION_IMPORTS_SECTION_NAME`, `RegionImportsError` enum (5 variants), `verify_region_imports_from_module` entry point. - **In-module self-consistency pass** (`verify.rs`): - `MissingDependentRegions` — region-imports without regions (producer obligation #1) - `MissingDependentRegionImports` — regions has import-bit `target_region` but no region-imports section - `DuplicateImport` — non-unique `(producer_module_name, region_name)` pairs - `PointerInImportNotSupportedInV1` — v1 restriction (producer obligation #5) - `ImportTargetOutOfRange` — import-bit `target_region` resolves past the import-table bounds ## Scope (deliberately partial) This is the unblocking half of #140 bullet 1. **Out of scope:** - Cross-module schema agreement (`SchemaSub expected actual` from `MultiModule.idr`). Defers to a future `verify_link_graph(modules: &[Module])` pass per proposal 0003 §"Open questions" #4 default option a. - Producer codegen for the new section. AffineScript and Ephapax still emit single-module — codegen for both lands separately (AffineScript roadmap C3 etc., proposal 0003 §"Coordination with downstream producers"). - Wiring `examples/02-multi-module.twasm` through the producer — blocked by #127 (front-end → IR seam), per #140 bullet 3. ## Feature gate `unstable-l13-imports = ["unstable-l2"]` — implies l2 because the import-bit convention extends `typedwasm.regions`'s `target_region` value space. Default verifier surface is unchanged; this is opt-in until proposal 0003 leaves `[draft]`. ## Test plan - [x] `cargo check -p typed-wasm-verify` (default features) — clean - [x] `cargo check -p typed-wasm-verify --all-features` — clean - [x] `cargo check -p typed-wasm-verify --features unstable-l13-imports` — clean - [x] `cargo test -p typed-wasm-verify --features unstable-l13-imports` — **85 tests pass** (66 baseline + 19 new): - 9 codec tests (roundtrip, truncation, UTF-8-lossy, version mismatch, pointer-kinds tolerance, large field counts) - 10 verifier-pass tests (all 5 error variants + clean path + NO_TARGET_REGION sentinel safety + empty-table) - [x] `cargo clippy -p typed-wasm-verify --features unstable-l13-imports` — clippy-clean for new code (one pre-existing collapsible-match warning at `verify.rs:323`) ## Refs Refs #140 (bullet 1 verifier-pass slice), refs #95, refs #50, part of proposal 0003 §"Acceptance criteria" items 3 + 4. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1acac8f commit 1ba8538

4 files changed

Lines changed: 835 additions & 0 deletions

File tree

crates/typed-wasm-verify/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ unstable-l2 = []
3030
# `FunctionCapabilities` + `verify_capabilities_from_module` (PR #109).
3131
# L15-C (per-call-site grants) is a separate proposal 0004 (`[draft]`).
3232
unstable-l15 = []
33+
# L13 cross-module region-imports carrier for typed-wasm proposal 0003
34+
# (`[draft]`, typed-wasm#140 refs #95). Implies `unstable-l2`: the import
35+
# table cross-references `typedwasm.regions` field entries via the
36+
# high-bit `target_region` convention. Enables:
37+
# - `parse_region_imports_section_payload` /
38+
# `build_region_imports_section_payload` +
39+
# `RegionImportEntry` / `ImportedFieldEntry` + `IMPORT_TABLE_BASE`
40+
# - `verify_region_imports_from_module` (this PR) — self-consistency
41+
# only; cross-module schema agreement (`SchemaSub`) defers to a
42+
# later `verify_link_graph` pass per proposal 0003 §"Open
43+
# questions" #4 default option a.
44+
unstable-l13-imports = ["unstable-l2"]
3345

3446
[dependencies]
3547
# Exact pins: wasmparser's 0.x line ships API breaks on every minor bump

crates/typed-wasm-verify/src/lib.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// SPDX-License-Identifier: MPL-2.0
2+
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
23
//
34
// typed-wasm post-codegen verifier.
45
//
@@ -31,6 +32,12 @@ pub use section::{
3132
Nullability, RegionEntry, WasmTy, REGIONS_SECTION_VERSION,
3233
};
3334

35+
#[cfg(feature = "unstable-l13-imports")]
36+
pub use section::{
37+
build_region_imports_section_payload, parse_region_imports_section_payload,
38+
ImportedFieldEntry, RegionImportEntry, IMPORT_TABLE_BASE, REGION_IMPORTS_SECTION_VERSION,
39+
};
40+
3441
/// Ownership kinds matching the OCaml `Codegen.ownership_kind` enum.
3542
/// Wire encoding in the `typedwasm.ownership` custom section: a single
3643
/// u8 per kind, values 0/1/2/3 as below.
@@ -148,6 +155,14 @@ pub const CAPABILITIES_SECTION_NAME: &str = "typedwasm.capabilities";
148155
#[cfg(feature = "unstable-l2")]
149156
pub const ACCESS_SITES_SECTION_NAME: &str = "typedwasm.access-sites";
150157

158+
/// Custom-section name carrying cross-module region-import declarations
159+
/// (proposal 0003, typed-wasm#140 refs #95). Companion to
160+
/// `typedwasm.regions`: a module's `target_region` foreign keys with the
161+
/// import-table bit set (`>= IMPORT_TABLE_BASE`) resolve through this
162+
/// section's entries. UNSTABLE.
163+
#[cfg(feature = "unstable-l13-imports")]
164+
pub const REGION_IMPORTS_SECTION_NAME: &str = "typedwasm.region-imports";
165+
151166
/// L15 capability-section violation (parsing succeeded, content invalid).
152167
#[cfg(feature = "unstable-l15")]
153168
#[derive(Debug, Clone, PartialEq, Eq, Error)]
@@ -203,6 +218,62 @@ pub enum AccessSiteError {
203218
},
204219
}
205220

221+
/// L13 region-imports section violation. Self-consistency only; cross-
222+
/// module schema-agreement (`SchemaSub expected actual`, `SchemaImportMismatch`)
223+
/// belongs to a future `verify_link_graph` pass (proposal 0003 §"Open
224+
/// questions" #4 default option a).
225+
#[cfg(feature = "unstable-l13-imports")]
226+
#[derive(Debug, Clone, PartialEq, Eq, Error)]
227+
pub enum RegionImportsError {
228+
/// Proposal 0003 §"Producer obligations" #1: a module emitting
229+
/// `typedwasm.region-imports` MUST also emit `typedwasm.regions` (the
230+
/// import-table foreign keys in `typedwasm.regions`'s field entries
231+
/// would otherwise dangle).
232+
#[error("Level 13 violation: typedwasm.region-imports section emitted without companion typedwasm.regions section (MissingDependentCarrier)")]
233+
MissingDependentRegions,
234+
235+
/// Inverse companion check: a `typedwasm.regions` field entry has a
236+
/// `target_region` value with the import-table bit set, but no
237+
/// `typedwasm.region-imports` section is present to resolve it
238+
/// against. Emitted at most once per module (further occurrences
239+
/// would spam).
240+
#[error("Level 13 violation: typedwasm.regions has target_region with import-table bit set (value {target_region:#010x}) but no typedwasm.region-imports section present to resolve it")]
241+
MissingDependentRegionImports { target_region: u32 },
242+
243+
/// Proposal 0003 §"Wire format" Notes: imports MUST have unique
244+
/// `(producer_module_name, region_name)` pairs.
245+
#[error("Level 13 violation: duplicate import: (producer_module_name = {producer_module_name:?}, region_name = {region_name:?}) appears at import-table indices {first_idx} and {duplicate_idx}")]
246+
DuplicateImport {
247+
first_idx: u32,
248+
duplicate_idx: u32,
249+
producer_module_name: String,
250+
region_name: String,
251+
},
252+
253+
/// Proposal 0003 §"Producer obligations" #5: imported regions MUST
254+
/// have scalar-only expected schemas in v1. Transitive pointer-chain
255+
/// resolution is deferred to v2 (see proposal 0003 §"Open questions" #1).
256+
#[error("Level 13 violation: import-table entry {import_idx}: expected field {field_idx} ({field_name:?}) has pointer kind {kind:?}; pointer fields are not supported in imported regions in v1 (proposal 0003 §Producer obligations 5)")]
257+
PointerInImportNotSupportedInV1 {
258+
import_idx: u32,
259+
field_idx: u32,
260+
field_name: String,
261+
kind: FieldKind,
262+
},
263+
264+
/// A `typedwasm.regions` field entry has a `target_region` value
265+
/// with the import-table bit set, but the resolved index points past
266+
/// the end of the `typedwasm.region-imports` table.
267+
#[error("Level 13 violation: typedwasm.regions region {region_idx} field {field_idx}: target_region value {target_region:#010x} resolves to import-table index {resolved_idx} but only {import_count} imports are declared")]
268+
ImportTargetOutOfRange {
269+
region_idx: u32,
270+
field_idx: u32,
271+
target_region: u32,
272+
resolved_idx: u32,
273+
import_count: u32,
274+
},
275+
}
276+
206277
// ----------------------------------------------------------------------
207278
// Public entry points (stubbed in C1; implementations land in C2-C4).
208279
// ----------------------------------------------------------------------
@@ -260,6 +331,36 @@ pub fn verify_access_sites_from_module(
260331
verify::verify_access_sites_from_module(wasm_bytes)
261332
}
262333

334+
/// Verify the L13 region-imports section's in-module self-consistency by
335+
/// reading its embedded `typedwasm.region-imports` and `typedwasm.regions`
336+
/// custom sections. Modules emitting neither section verify trivially.
337+
///
338+
/// Checks:
339+
///
340+
/// 1. `MissingDependentRegions`: region-imports present without regions
341+
/// is a hard error (proposal 0003 §"Producer obligations" #1).
342+
/// 2. `MissingDependentRegionImports`: regions present with at least one
343+
/// `target_region` value `>= IMPORT_TABLE_BASE` (i.e. claiming an
344+
/// import) without region-imports is a hard error (emitted at most
345+
/// once per module).
346+
/// 3. `DuplicateImport`: imports MUST have unique
347+
/// `(producer_module_name, region_name)` pairs.
348+
/// 4. `PointerInImportNotSupportedInV1`: imported regions' expected
349+
/// fields MUST all be `kind == Scalar` in v1.
350+
/// 5. `ImportTargetOutOfRange`: every `target_region` value with the
351+
/// import-table bit set MUST resolve within the import-table bounds.
352+
///
353+
/// Does NOT verify cross-module schema agreement (`SchemaSub expected
354+
/// actual` from `MultiModule.idr`); that requires the producer module's
355+
/// bytes and is the subject of a future `verify_link_graph(modules)` pass
356+
/// (proposal 0003 §"Open questions" #4 default option a).
357+
#[cfg(feature = "unstable-l13-imports")]
358+
pub fn verify_region_imports_from_module(
359+
wasm_bytes: &[u8],
360+
) -> Result<Vec<RegionImportsError>, VerifyError> {
361+
verify::verify_region_imports_from_module(wasm_bytes)
362+
}
363+
263364
/// Ownership-annotated signature for one exported function.
264365
/// Mirrors OCaml `Tw_interface.func_interface`.
265366
#[derive(Debug, Clone, PartialEq, Eq)]

0 commit comments

Comments
 (0)