Skip to content

feat(verify): scaffold typed-wasm-verify Rust crate (C1)#19

Merged
hyperpolymath merged 1 commit into
mainfrom
feat/typed-wasm-verify-rust-port
May 15, 2026
Merged

feat(verify): scaffold typed-wasm-verify Rust crate (C1)#19
hyperpolymath merged 1 commit into
mainfrom
feat/typed-wasm-verify-rust-port

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

Summary

  • Adds a Cargo workspace at the repo root with one member: crates/typed-wasm-verify.
  • Scaffolds the public surface (types, error enums, stubbed entry points) for the Rust port of hyperpolymath/affinescript:lib/{tw_verify,tw_interface}.ml.
  • Real implementations land in follow-up PRs C2 (custom-section parser) → C3 (intra-fn verifier) → C4 (cross-module verifier) → C5 (cross-compat test).

Why

The ReScript parser/checker at src/parser/*.res is on its way out — ReScript is banned across the org. typed-wasm's Rust toolchain has to start somewhere; the verifier is the most self-contained piece (operates on emitted wasm, not on .twasm source), so it doesn't block on a .twasm parser. Downstream consumers (hyperpolymath/ephapax directly, hyperpolymath/affinescript via subprocess) want a Rust home for this code.

Spec of record

Until the cross-compat suite (C5) is green, the OCaml files in hyperpolymath/affinescript:

  • lib/tw_verify.ml (~246 LOC) — per-fn min/max use-range analysis
  • lib/tw_interface.ml (~245 LOC) — cross-module boundary checks

remain authoritative.

Build

$ cargo build -p typed-wasm-verify
    Finished `dev` profile [unoptimized + debuginfo] target(s)
$ cargo test  -p typed-wasm-verify
running 1 test
test tests::ownership_kind_byte_roundtrip ... ok
test result: ok. 1 passed; 0 failed; 0 ignored

Follow-up

  • C2 — wire the custom-section parser (u32le count + per-entry header)
  • C3 — port count_uses_range and the intra-function verifier
  • C4 — port extract_exports + verify_cross_module
  • C5 — cross-compat regression test against affinescript-emitted wasm
  • C6/C7 — hyperpolymath/ephapax consumer wiring (separate PR there)

Adds a new Cargo workspace at the repo root with one member:
`crates/typed-wasm-verify`. The crate is the Rust home for typed-wasm's
post-codegen L7 (aliasing) + L10 (linearity) verifier. This commit lands
the scaffolding only — public types, error enums, and stubbed entry
points. Real implementations come in follow-ups C2/C3/C4.

Why a Rust crate here?
----------------------

- The ReScript parser/checker in `src/parser/*.res` is on the way out
  (ReScript ban across hyperpolymath repos), so the canonical
  surface-level checker will be Rust. Starting with the post-codegen
  verifier — a self-contained piece that operates on emitted wasm
  rather than on `.twasm` source — lets the port begin without first
  needing a `.twasm` parser.
- Downstream consumers want a Rust dependency: ephapax (already Rust)
  to call into directly via a Cargo path/git dep, and affinescript
  (OCaml) to invoke as a subprocess until full FFI lands.

Spec of record
--------------

Both modules are direct ports of:

  - hyperpolymath/affinescript:lib/tw_verify.ml    (~246 LOC OCaml)
  - hyperpolymath/affinescript:lib/tw_interface.ml (~245 LOC OCaml)

Until the cross-compat suite (C5) is green, those OCaml files remain
authoritative.

Public surface (this commit)
----------------------------

- `OwnershipKind` — 4-variant enum with `from_byte` decoder matching the
  OCaml fallback semantics (anything outside 0..=3 → Unrestricted).
- `OwnershipError` — 4 variants for intra-function violations
  (LinearNotUsed / LinearDroppedOnSomePath / LinearUsedMultiple /
  ExclBorrowAliased), each carrying `func_idx` + `param_idx` and a
  `thiserror`-derived Display matching the OCaml `pp_error` output.
- `CrossError` — 2 variants for cross-module boundary violations
  (LinearImportCalledMultiple / LinearImportDroppedOnSomePath) carrying
  `caller_func_idx`, `import_func_idx`, `import_name`.
- `FuncInterface` — exported-function signature carrying its ownership
  kinds, mirroring `Tw_interface.func_interface`.
- `VerifyError` — top-level error type covering wasmparser failures plus
  the two violation vectors.
- `OWNERSHIP_SECTION_NAME` — the `affinescript.ownership` custom-section
  name constant.
- Three stubbed entry points (`verify_from_module`, `extract_exports`,
  `verify_cross_module`) — all `todo!()` until C2/C3/C4 land. Signatures
  are pinned now so consumers can already start writing call sites.

Dependencies
------------

  - wasmparser 0.221 — for module/instruction walking in C3/C4
  - thiserror 2     — error-enum derive
  - wasm-encoder 0.221 (dev) — for synthesising test fixtures in C2/C3

Build status
------------

  $ cargo build -p typed-wasm-verify
    Finished `dev` profile [unoptimized + debuginfo] target(s)
  $ cargo test  -p typed-wasm-verify
  running 1 test
  test tests::ownership_kind_byte_roundtrip ... ok
  test result: ok. 1 passed; 0 failed; 0 ignored

Follow-up tasks
---------------

  C2 (#37) — wire the `affinescript.ownership` custom-section parser
             (u32le count + per-entry header)
  C3 (#38) — port `tw_verify.count_uses_range` over wasmparser ops and
             implement the per-fn / per-module verifier proper
  C4 (#39) — port `tw_interface.{extract_exports, verify_cross_module}`
  C5 (#40) — cross-compat test against affinescript-emitted modules
  C6 (#41) — ephapax-wasm emits the custom section
  C7 (#42) — ephapax-cli gets `--verify-ownership`

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hyperpolymath hyperpolymath force-pushed the feat/typed-wasm-verify-rust-port branch from 2d63b52 to 1e247de Compare May 15, 2026 05:58
hyperpolymath added a commit that referenced this pull request May 15, 2026
Implements the parser and encoder for the `affinescript.ownership`
custom-section wire format. The parser is a faithful port of OCaml
`Tw_verify.parse_ownership_section_payload`; the encoder mirrors the
matching emitter on the affinescript side (`Codegen.build_ownership_section`)
so downstream emitters in ephapax (C6) and elsewhere can reuse it.

Wire format
-----------

Little-endian, byte-aligned:

  u32le  count
  for each entry:
    u32le  func_idx
    u8     n_params
    u8[n]  param_kinds  (0=Unrestricted, 1=Linear, 2=SharedBorrow, 3=ExclBorrow)
    u8     ret_kind

Cross-impl parity decision
--------------------------

The OCaml parser is lenient: reading past the end of the payload yields
zero bytes (interpreted as `Unrestricted` kinds and `func_idx = 0`).
This Rust port preserves that exactly. A correctly-emitted section
will never be truncated, but matching the leniency means the C5
cross-compat suite sees identical results on every payload the OCaml
side accepts. Stricter validation is a future opt-in (`parse_strict`),
not the default.

The `OwnershipKind::from_byte` fallback (anything outside 0..=3 →
`Unrestricted`) was already in C1 and is reused here.

New API
-------

  - `OwnershipEntry { func_idx, param_kinds, ret_kind }` — named struct
    replacing the OCaml 3-tuple for readability.
  - `parse_ownership_section_payload(&[u8]) -> Vec<OwnershipEntry>` —
    lenient parser (matches OCaml).
  - `build_ownership_section_payload(&[OwnershipEntry]) -> Vec<u8>` —
    inverse encoder; panics if any entry has >255 params (the n_params
    field is u8, but real wasm modules don't hit this limit).
  - `OwnershipKind::to_byte(self) -> u8` — encode side of the existing
    `from_byte`.

Tests
-----

11/11 unit tests, covering:

  - empty payload
  - count=0 with no entries
  - single entry with zero params
  - single entry with all four kind values (encode + decode)
  - multi-entry round-trip
  - unknown kind byte falls back to `Unrestricted` (parity guard)
  - truncated payload reads zeros past EOF (parity guard)
  - empty round-trip (entries → bytes → entries)
  - realistic 2-entry round-trip
  - exact wire-format byte sequence for a known input

  $ cargo test -p typed-wasm-verify
  running 11 tests
  test section::tests::build_emits_correct_wire_format ... ok
  test section::tests::empty_payload_yields_no_entries ... ok
  test section::tests::roundtrip_empty ... ok
  test section::tests::count_zero_yields_no_entries ... ok
  test section::tests::multiple_entries ... ok
  test section::tests::roundtrip_realistic ... ok
  test section::tests::single_entry_no_params ... ok
  test section::tests::single_entry_with_all_kinds ... ok
  test section::tests::truncated_payload_reads_zeros_past_end ... ok
  test section::tests::unknown_kind_byte_decodes_to_unrestricted ... ok
  test tests::ownership_kind_byte_roundtrip ... ok
  test result: ok. 11 passed; 0 failed; 0 ignored

Stacked on top of #19 (C1 scaffold). Next: C3 — port the per-path
use-range analysis and intra-function verifier.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hyperpolymath hyperpolymath merged commit 90e894b into main May 15, 2026
22 of 31 checks passed
@hyperpolymath hyperpolymath deleted the feat/typed-wasm-verify-rust-port branch May 15, 2026 06:01
@hyperpolymath hyperpolymath restored the feat/typed-wasm-verify-rust-port branch May 15, 2026 06:02
hyperpolymath added a commit that referenced this pull request May 15, 2026
Implements the parser and encoder for the `affinescript.ownership`
custom-section wire format. The parser is a faithful port of OCaml
`Tw_verify.parse_ownership_section_payload`; the encoder mirrors the
matching emitter on the affinescript side (`Codegen.build_ownership_section`)
so downstream emitters in ephapax (C6) and elsewhere can reuse it.

Wire format
-----------

Little-endian, byte-aligned:

  u32le  count
  for each entry:
    u32le  func_idx
    u8     n_params
    u8[n]  param_kinds  (0=Unrestricted, 1=Linear, 2=SharedBorrow, 3=ExclBorrow)
    u8     ret_kind

Cross-impl parity decision
--------------------------

The OCaml parser is lenient: reading past the end of the payload yields
zero bytes (interpreted as `Unrestricted` kinds and `func_idx = 0`).
This Rust port preserves that exactly. A correctly-emitted section
will never be truncated, but matching the leniency means the C5
cross-compat suite sees identical results on every payload the OCaml
side accepts. Stricter validation is a future opt-in (`parse_strict`),
not the default.

The `OwnershipKind::from_byte` fallback (anything outside 0..=3 →
`Unrestricted`) was already in C1 and is reused here.

New API
-------

  - `OwnershipEntry { func_idx, param_kinds, ret_kind }` — named struct
    replacing the OCaml 3-tuple for readability.
  - `parse_ownership_section_payload(&[u8]) -> Vec<OwnershipEntry>` —
    lenient parser (matches OCaml).
  - `build_ownership_section_payload(&[OwnershipEntry]) -> Vec<u8>` —
    inverse encoder; panics if any entry has >255 params (the n_params
    field is u8, but real wasm modules don't hit this limit).
  - `OwnershipKind::to_byte(self) -> u8` — encode side of the existing
    `from_byte`.

Tests
-----

11/11 unit tests, covering:

  - empty payload
  - count=0 with no entries
  - single entry with zero params
  - single entry with all four kind values (encode + decode)
  - multi-entry round-trip
  - unknown kind byte falls back to `Unrestricted` (parity guard)
  - truncated payload reads zeros past EOF (parity guard)
  - empty round-trip (entries → bytes → entries)
  - realistic 2-entry round-trip
  - exact wire-format byte sequence for a known input

  $ cargo test -p typed-wasm-verify
  running 11 tests
  test section::tests::build_emits_correct_wire_format ... ok
  test section::tests::empty_payload_yields_no_entries ... ok
  test section::tests::roundtrip_empty ... ok
  test section::tests::count_zero_yields_no_entries ... ok
  test section::tests::multiple_entries ... ok
  test section::tests::roundtrip_realistic ... ok
  test section::tests::single_entry_no_params ... ok
  test section::tests::single_entry_with_all_kinds ... ok
  test section::tests::truncated_payload_reads_zeros_past_end ... ok
  test section::tests::unknown_kind_byte_decodes_to_unrestricted ... ok
  test tests::ownership_kind_byte_roundtrip ... ok
  test result: ok. 11 passed; 0 failed; 0 ignored

Stacked on top of #19 (C1 scaffold). Next: C3 — port the per-path
use-range analysis and intra-function verifier.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request May 15, 2026
…#20)

Implements the parser and encoder for the `affinescript.ownership`
custom-section wire format. The parser is a faithful port of OCaml
`Tw_verify.parse_ownership_section_payload`; the encoder mirrors the
matching emitter on the affinescript side (`Codegen.build_ownership_section`)
so downstream emitters in ephapax (C6) and elsewhere can reuse it.

Wire format
-----------

Little-endian, byte-aligned:

  u32le  count
  for each entry:
    u32le  func_idx
    u8     n_params
    u8[n]  param_kinds  (0=Unrestricted, 1=Linear, 2=SharedBorrow, 3=ExclBorrow)
    u8     ret_kind

Cross-impl parity decision
--------------------------

The OCaml parser is lenient: reading past the end of the payload yields
zero bytes (interpreted as `Unrestricted` kinds and `func_idx = 0`).
This Rust port preserves that exactly. A correctly-emitted section
will never be truncated, but matching the leniency means the C5
cross-compat suite sees identical results on every payload the OCaml
side accepts. Stricter validation is a future opt-in (`parse_strict`),
not the default.

The `OwnershipKind::from_byte` fallback (anything outside 0..=3 →
`Unrestricted`) was already in C1 and is reused here.

New API
-------

  - `OwnershipEntry { func_idx, param_kinds, ret_kind }` — named struct
    replacing the OCaml 3-tuple for readability.
  - `parse_ownership_section_payload(&[u8]) -> Vec<OwnershipEntry>` —
    lenient parser (matches OCaml).
  - `build_ownership_section_payload(&[OwnershipEntry]) -> Vec<u8>` —
    inverse encoder; panics if any entry has >255 params (the n_params
    field is u8, but real wasm modules don't hit this limit).
  - `OwnershipKind::to_byte(self) -> u8` — encode side of the existing
    `from_byte`.

Tests
-----

11/11 unit tests, covering:

  - empty payload
  - count=0 with no entries
  - single entry with zero params
  - single entry with all four kind values (encode + decode)
  - multi-entry round-trip
  - unknown kind byte falls back to `Unrestricted` (parity guard)
  - truncated payload reads zeros past EOF (parity guard)
  - empty round-trip (entries → bytes → entries)
  - realistic 2-entry round-trip
  - exact wire-format byte sequence for a known input

  $ cargo test -p typed-wasm-verify
  running 11 tests
  test section::tests::build_emits_correct_wire_format ... ok
  test section::tests::empty_payload_yields_no_entries ... ok
  test section::tests::roundtrip_empty ... ok
  test section::tests::count_zero_yields_no_entries ... ok
  test section::tests::multiple_entries ... ok
  test section::tests::roundtrip_realistic ... ok
  test section::tests::single_entry_no_params ... ok
  test section::tests::single_entry_with_all_kinds ... ok
  test section::tests::truncated_payload_reads_zeros_past_end ... ok
  test section::tests::unknown_kind_byte_decodes_to_unrestricted ... ok
  test tests::ownership_kind_byte_roundtrip ... ok
  test result: ok. 11 passed; 0 failed; 0 ignored

Stacked on top of #19 (C1 scaffold). Next: C3 — port the per-path
use-range analysis and intra-function verifier.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@hyperpolymath hyperpolymath deleted the feat/typed-wasm-verify-rust-port branch May 15, 2026 06:04
hyperpolymath added a commit that referenced this pull request May 15, 2026
)

The Rust verifier crate at `crates/typed-wasm-verify/` landed across
PRs #19 (C1) → #23 (C5) on 2026-05-15 but the repo-level documentation
didn't catch up. This commit fixes that.

Changes
-------

* CHANGELOG.md `[Unreleased] → Added`: one entry summarising the
  crate, plus a sub-bullet per phase (C1-C5) with PR refs.
* CHANGELOG.md `[Unreleased] → Changed`: note that the repo is now
  polyglot Idris2 + Zig + Rust (root Cargo workspace).
* LEVEL-STATUS.md: new "Post-codegen verifier (Rust)" section after
  the Idris2 proof inventory, explaining the verifier's place in the
  three-layer story (spec proofs / source checker / post-codegen) and
  documenting coverage (L7 + L10 only for now). Lists current
  consumers (ephapax-wasm + ephapax-cli).
* ROADMAP.adoc: v0.4 (ecosystem) gets a [x] bullet for the Rust crate;
  v1.0 gets two new entries — [x] for the post-codegen verifier
  itself (partial v1.0 deliverable, separate from `.twasm` parsing),
  [ ] for the cross-compat parity proof against affinescript's OCaml
  reference (deferred as "C5.1").
* 0-AI-MANIFEST.a2ml: new `verify_rust` entry in `[canonical_locations]`
  so AI agents reading the manifest can find the crate.

No code changes — this is pure-docs cleanup. The crate itself, its
tests, and consumer integrations are already on `main`.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant