feat(tw-verify): #10 — enforce L13 module-isolation on emitted wasm by hyperpolymath · Pull Request #280 · hyperpolymath/affinescript · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions docs/ECOSYSTEM.adoc
6 changes: 5 additions & 1 deletion docs/TECH-DEBT.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,11 @@ structural-conservative recogniser) |S2 |open #234
|CONV-03 |#225/#160 convergence ABI matured to "shared with Ephapax" |S1
|partial (PR3a/b/c merged)
|CONV-04 |Widen emitted-wasm enforcement beyond L7+L10 toward L1–6/L13–16 |S2
|planned (Stage E)
|L13 (module isolation, negative form) DONE —
`Tw_verify.verify_module_isolation` (carrier-free, no ABI change).
L1–6/L14–16 need a new carrier section = multi-producer ABI proposal
(filed for typed-wasm; NOT unilateral). Rust-verifier mirror + C5.1 =
cross-repo follow-ups
|CONV-05 |INT-12: AffineScript-emitted fixtures into typed-wasm
`crates/typed-wasm-verify/tests/cross_compat.rs` (typed-wasm "C5.1") |S1
|planned — coordinate Refs `hyperpolymath/typed-wasm`
Expand Down
54 changes: 53 additions & 1 deletion lib/tw_verify.ml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ type ownership_error =
| ExclBorrowAliased of { func_idx : int; param_idx : int; count : int }
(** Level 7: ExclBorrow parameter was loaded [count] times — aliasing violation.
An exclusive borrow must not have multiple simultaneous references. *)
| ModuleNotIsolated of { reason : string }
(** Level 13 (module isolation — the typed-wasm contract widening,
issue #234/#10). The emitted module owns its own linear memory
yet ALSO imports a memory or table: a cross-module shared-state
channel outside the declared function-import boundary.
AffineScript codegen always emits a private memory and never
imports one, so this is the negative L13 property and a violation
is a codegen/isolation regression — exactly the class
[tw_verify] exists to catch on emitted wasm. Additive: NO
ownership-section wire-format change, so the multi-producer ABI
(ephapax + typed-wasm) is untouched. *)

(* ============================================================================
Per-path use-range analysis
Expand Down Expand Up @@ -222,6 +233,40 @@ let parse_ownership_section_payload
(func_idx, param_kinds, ret_kind)
)

(** Level 13 — module isolation (negative form). A well-isolated
module owns its private linear memory and reaches other modules
ONLY through the declared function-import boundary. If the module
has its own memory yet also imports a memory or a table, that
imported entity is a shared-state channel outside the boundary —
not isolated. (A module with no own memory that imports one is a
pure consumer shim, not in scope here; AffineScript codegen never
emits that shape.) Carrier-free: read only the standard wasm
import/memory sections, so no multi-producer ABI change. *)
let verify_module_isolation
(wasm_mod : Wasm.wasm_module)
: ownership_error list =
if wasm_mod.Wasm.mems = [] then []
else
List.filter_map
(fun (im : Wasm.import) ->
match im.Wasm.i_desc with
| Wasm.ImportMemory ->
Some (ModuleNotIsolated {
reason =
Printf.sprintf
"module owns linear memory yet imports memory '%s.%s' \
(cross-module shared memory breaks L13 isolation)"
im.Wasm.i_module im.Wasm.i_name })
| Wasm.ImportTable ->
Some (ModuleNotIsolated {
reason =
Printf.sprintf
"module owns linear memory yet imports table '%s.%s' \
(externally-backed table breaks L13 isolation)"
im.Wasm.i_module im.Wasm.i_name })
| Wasm.ImportFunc _ -> None)
wasm_mod.Wasm.imports

(** Verify a Wasm module using the embedded [affinescript.ownership] custom
section. This is the primary entry point for the pipeline and the CLI.

Expand All @@ -236,7 +281,12 @@ let verify_from_module
Ok ()
| Some payload ->
let annots = parse_ownership_section_payload payload in
let errors = verify_module wasm_mod annots in
(* L7/L10 (ownership) + L13 (module isolation). The isolation check
is gated behind the same ownership-section presence so the
"no section ⇒ Ok" contract is preserved. *)
let errors =
verify_module wasm_mod annots @ verify_module_isolation wasm_mod
in
if errors = [] then Ok () else Error errors

(* ============================================================================
Expand Down Expand Up @@ -266,6 +316,8 @@ let pp_error (fmt : Format.formatter) (err : ownership_error) : unit =
"Level 7 violation: function %d, param %d — ExclBorrow (mut) param \
aliased (%d simultaneous references; at most 1 permitted)"
func_idx param_idx count
| ModuleNotIsolated { reason } ->
Format.fprintf fmt "Level 13 violation: %s" reason

(** Format a full verification report. Prints "OK" if no errors. *)
let pp_report (fmt : Format.formatter) (errs : ownership_error list) : unit =
Expand Down
1 change: 1 addition & 0 deletions test/test_main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ let () =
("Examples", Test_golden.example_tests);
("Effects (#59)", Test_effects.tests);
("Effect-sites (#234 S2a)", Test_effect_sites.tests);
("TW L13 isolation (#10)", Test_tw_isolation.tests);
] @ Test_e2e.tests @ Test_stdlib_aot.tests)
94 changes: 94 additions & 0 deletions test/test_tw_isolation.ml
Loading