From 692b1648a8f6637db6de30a29e774ccd68f499c1 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Tue, 30 Jun 2026 16:14:57 -0500 Subject: [PATCH] Clarify 'alias outer' rules --- design/mvp/Binary.md | 47 +++---- design/mvp/Explainer.md | 147 +++++++++++++------- test/validation/outer-alias.wast | 230 +++++++++++++++++++++++++++++++ 3 files changed, 352 insertions(+), 72 deletions(-) create mode 100644 test/validation/outer-alias.wast diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md index d8e0a88b..cd3a70a0 100644 --- a/design/mvp/Binary.md +++ b/design/mvp/Binary.md @@ -116,24 +116,23 @@ Notes: (See [Alias Definitions](Explainer.md#alias-definitions) in the explainer.) ```ebnf -alias ::= s: t: => (alias t (s)) -aliastarget ::= 0x00 i: n: => export i n - | 0x01 i: n: => core export i n - | 0x02 ct: idx: => outer ct idx +alias ::= s: 0x00 i: n: => (alias export i n (s)) + | s: 0x01 i: n: => (alias core export i n (s)) + | s: 0x02 ct: idx: => (alias outer ct idx (s)) (if s in outeraliassort) ``` Notes: * Reused Core binary rules: (variable-length encoded) [`core:u32`] -* For `export` aliases, `i` is validated to refer to an instance in the - instance index space that exports `n` with the specified `sort`. -* For `outer` aliases, `ct` is validated to be *less or equal than* the number - of enclosing `component`s and `type`s and `i` is validated to be a valid index - in the `sort` index space of the targeted `component`/`type` (counting - outward, starting with `0` referring to the current `component`/`type`). -* For `outer` aliases, validation restricts the `sort` to one of `type`, - `module` or `component`. -* For `outer` aliases that reach across a `component` boundary (as opposed to - a `type` boundary), validation additionally requires that any outer-aliased - `type` does not transitively refer to a `resource` type. +* For (`core`) `export` aliases, `i` is validated to refer to a (core) instance + in the (core) instance index space that exports `n` with the specified `sort`. +* For `outer` aliases: + * `ct` is validated to be *less than or equal to* the number of enclosing + "scopes" (where a "scope" is one of: a `component` definition, a `component` + type, an `instance` type) + * `i` is validated to be a valid index in the `s` index space of the target + scope (counting outward, starting with `0` referring to the current scope) + * if `ct` is greater than 0 and crosses a component (as opposed to type) + boundary and `s` is `type`, the target type may not transitively refer to a + resource type. ## Type Definitions @@ -149,8 +148,7 @@ core:moduledecl ::= 0x00 i: => i | 0x01 t: => t | 0x02 a: => a | 0x03 e: => e -core:alias ::= s: t: => (alias t (s)) -core:aliastarget ::= 0x01 ct: idx: => outer ct idx +core:alias ::= 0x10 0x01 ct: idx: => (alias outer ct idx (type)) core:importdecl ::= i: => i core:exportdecl ::= n: t: => (export n t) ``` @@ -171,8 +169,9 @@ Notes: core type index space will not contain any core module types. * As described in the explainer, each module type is validated with an initially-empty type index space. -* `alias` declarators currently only allow `outer` `type` aliases but - would add `export` aliases when core wasm adds type exports. +* In `core:alias`, the first `0x10` is the opcode for `type` in `core:sort` and + `0x01` is an opcode to distinguish `outer` aliases from potential future + `export` aliases. ```ebnf type ::= dt: => (type dt) @@ -260,11 +259,13 @@ Notes: [TODO](Concurrency.md#TODO). * Validation of `resourcetype` requires the destructor (if present) to have type `[i32] -> []`. -* Validation of `instancedecl` (currently) only allows the `type` and - `instance` sorts in `alias` declarators. +* In addition to the validation rules for `alias` *definitions* mentioned above, + validation of `alias` *declarators* also requires: + * `export` aliases only alias the `instance` and `type` sorts + * `outer` aliases only alias the `core type` and `type` sorts * As described in the explainer, each component and instance type is validated - with an initially-empty type index space. Outer aliases can be used to pull - in type definitions from containing components. + with an initially-empty type index space. (Outer aliases can be used to pull + in type definitions from containing `component` and `type` scopes.) * `exportdecl` introduces a new type index that can be used by subsequent type definitions. In the `(eq i)` case, the new type index is effectively an alias to type `i`. In the `(sub resource)` case, the new type index refers to a diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index c4869747..5f724d82 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -338,10 +338,13 @@ there are three kinds of "targets" for an alias: the `export` of a component instance, the `core export` of a core module instance and a definition of an `outer` component (containing the current component): ```ebnf -alias ::= (alias ( ?)) -aliastarget ::= export - | core export - | outer +alias ::= (alias export ( ?)) + | (alias core export (core ?)) + | (alias outer ( ?)) +outeraliassort ::= core module + | core type + | component + | type ``` If present, the `id` of the alias is bound to the new index added by the alias and can be used anywhere a normal `id` can be used. @@ -351,37 +354,44 @@ target instance and has a matching sort. In the case of `outer` aliases, the `u32` pair serves as a [de Bruijn index], with the first `u32` being the number of enclosing `component`s or `type`s to -skip and the second `u32` being an index into the target's sort's index space. -In particular, the first `u32` can be `0`, in which case the outer alias refers -to the current `component`/`type`. To maintain the acyclicity of module -instantiation, outer aliases are only allowed to refer to *preceding* outer -definitions. - -Components containing outer aliases effectively produce a [closure] at -instantiation time, including a copy of the outer-aliased definitions. Because -components, like modules, are pure values, outer aliases that reach across -`component` boundaries are restricted to only refer to pure definitions: -modules, components, and types that do not transitively refer to a `resource` -type. (As described in the next section, resource types are *generative* and -thus not pure.) For example, in the following component, `$alias{1,2,3,4,5}` are -allowed (b/c they only reach across a `type` boundary) while `$alias{6,7}` are -disallowed (b/c they reach across a `component` boundary): +skip and the second `u32` being an index into the target's `outeraliassort`'s +index space. In particular, the first `u32` can be `0`, in which case the outer +alias refers to the current `component`/`type`. To maintain the acyclicity of +module instantiation, outer aliases are only allowed to refer to *preceding* +outer definitions. `outer` aliases are currently restricted to only refer to +the 4 `outeraliassort`s, as opposed to the full set of `sort`s, but this +may be relaxed in the future if needed. + +To ensure that nested components can be arbitrarily "unbundled" (transforming +inline `component` definitions into `import`s of out-of-line `component` +definitions), `outer` aliases that cross component boundaries must only refer to +definitions that can be substituted inline, if needed for unbundling. Of the +available `outeraliassort`s: `core module`, `core type` and `component` +definitions are *always* substitutable. However, due to the *generativity* of +`resource` types (discussed in the next section), `type` definitions that +transitively refer to a `resource` type may *not* be `outer`-aliased and are +thus rejected by validation. + +For example, in the following component, `$T2` and `$T3` cannot be `outer`- +aliased by `$E`: ```wat (component $C (component $D) - (type $T (record (field "x" u32) (field "y" u32))) - (type $R (resource (rep i32))) - (type $B (borrow $R)) - (type (component - (alias outer $C $T (type $alias1)) - (alias outer $C $R (type $alias2)) - (alias outer $C $B (type $alias3)) - )) - (component - (alias outer $C $D (component $alias4)) - (alias outer $C $T (type $alias5)) - ;; (alias outer $C $R (type $alias6)) ❌ - ;; (alias outer $C $B (type $alias7)) ❌ + (type $T1 (record (field "x" u32) (field "y" u32))) + (type $T2 (resource (rep i32))) + (type $T3 (borrow $T2)) + (import "f" (func $f)) + (alias outer $C $D (component $D')) + (alias outer $C $T1 (type $T1')) + (alias outer $C $T2 (type $T2')) + (alias outer $C $T3 (type $T3')) + ;; (alias outer $C $f (func $f)) ❌ 'func' not in 'outeraliassort' + (component $E + (alias outer $C $D (component $D)) + (alias outer $C $T1 (type $T1)) + ;; (alias outer $C $T2 (type $T2)) ❌ resource type + ;; (alias outer $C $T3 (type $T3)) ❌ refers to resource type + ;; (alias outer $C $f (func $f)) ❌ 'func' not in 'outeraliassort' ) ) ``` @@ -492,8 +502,7 @@ core:moduledecl ::= | | | -core:alias ::= (alias ( ?)) -core:aliastarget ::= outer +core:alias ::= (alias outer (type ?)) core:importdecl ::= (import ) core:exportdecl ::= (export strip-id()) @@ -539,11 +548,9 @@ In this example, `$M` has a distinct type index space from `$C`, where element implicitly-created `func` type referring to both. Lastly, the `core:alias` module declarator allows a module type definition to -reuse (rather than redefine) type definitions in the enclosing component's core -type index space via `outer` `type` alias. In the MVP, validation restricts -`core:alias` module declarators to *only* allow `outer` `type` aliases (into an -enclosing component's or component-type's core type index space). In the -future, more kinds of aliases would be meaningful and allowed. +reuse (rather than redefine) `type` definitions in the enclosing component's +`core type` index space via `outer` alias. In the future, more kinds and sorts +of aliases may be useful and added. As an example, the following component defines two semantically-equivalent module types, where the former defines the function type via `type` declarator @@ -914,20 +921,62 @@ to some preceding type definition. This allows: could generate in C++ a `typedef std::vector bytes` or in JS an exported field named `bytes` that aliases `Uint64Array`. -Relaxing the restrictions of `core:alias` declarators mentioned above, `alias` -declarators allow both `outer` and `export` aliases of `type` and `instance` -sorts. This allows the type exports of `instance`-typed import and export -declarators to be used by subsequent declarators in the type: +The `alias` declarator reuses the syntax for component-level `alias` definitions +so that `export` and `outer` aliases can be used inside `component` and +`instance` *types*. For example, `export` aliases are necessary to define the +type of a component that uses an imported `resource` type in an exported +function parameter type: ```wat (component - (import "fancy-fs" (instance $fancy-fs - (export "fs" (instance $fs + (type $CT (component + (import "fs" (instance $fs (export "file" (type (sub resource))) - ;; ... + ;; file methods )) - (alias export $fs "file" (type $file)) - (export "fancy-op" (func (param "f" (borrow $file)))) + (alias export $fs "file" (type $fileT)) + (export "process" (func (param "file" (borrow $fileT)))) + )) + (import "fs-component" (component (type $CT))) +) +``` +Similarly, `outer` aliases are necessary to define an `instance` or `component` +type that uses a `resource` type defined or imported by the containing +component, since `instance` and `component` types start with empty type index +spaces: +```wat +(component $C + (import "r" (type $R (sub resource))) + (type $IT (instance + (alias outer $C $R (type $R)) + (export "process" (func (param "input" (borrow $R)))) )) + (import "uses-r" (instance (type $IT))) +) +``` +In terms of validation, `alias` declarators inherit all the previously-mentioned +validation rules of `alias` definitions and add the following rules: +* `alias export` declarators may only alias the `instance` and `type` sorts +* `alias outer` declarators may only alias the `core type` and `type` sorts + +```wat +(component $C + (component $D) + (type $T1 (record (field "x" u32) (field "y" u32))) + (type $T2 (resource (rep i32))) + (type $T3 (borrow $T2)) + (type (component + ;; (alias outer $C $D (component $D)) ❌ not allowed inside type + (alias outer $C $T1 (type $T1)) + (alias outer $C $T2 (type $T2)) + (alias outer $C $T3 (type $T3)) + )) + (component + (type (component + (alias outer $C $T1 (type $T1)) + ;; (alias outer $C $T2 (type $T2)) ❌ resource type + ;; (alias outer $C $T3 (type $T3)) ❌ refers to resource type + )) + ) ) ``` @@ -3290,7 +3339,6 @@ For some use-case-focused, worked examples, see: [Kebab Case]: https://en.wikipedia.org/wiki/Letter_case#Kebab_case [De Bruijn Index]: https://en.wikipedia.org/wiki/De_Bruijn_index -[Closure]: https://en.wikipedia.org/wiki/Closure_(computer_programming) [Empty Type]: https://en.wikipedia.org/w/index.php?title=Empty_type [IEEE754]: https://en.wikipedia.org/wiki/IEEE_754 [Unicode Scalar Values]: https://unicode.org/glossary/#unicode_scalar_value @@ -3306,6 +3354,7 @@ For some use-case-focused, worked examples, see: [Existential Types]: https://en.wikipedia.org/wiki/System_F [Unit]: https://en.wikipedia.org/wiki/Unit_type [Trampolines]: https://en.wikipedia.org/wiki/Trampoline_(computing) +[Free Variables]: https://en.wikipedia.org/wiki/Free_variables_and_bound_variables [Generative]: https://www.researchgate.net/publication/2426300_A_Syntactic_Theory_of_Type_Generativity_and_Sharing [Avoidance Problem]: https://counterexamples.org/avoidance.html diff --git a/test/validation/outer-alias.wast b/test/validation/outer-alias.wast new file mode 100644 index 00000000..f3a8e6b2 --- /dev/null +++ b/test/validation/outer-alias.wast @@ -0,0 +1,230 @@ +;; Validation of `outer` aliases: the allowed sorts, the cross-`component` +;; no-resource-types rule, de Bruijn counting, and `alias` declarator restrictions. + +;; aliasing from the current component (ct=0): + +(component definition $C + (core module $M (func (export "f"))) + (core type $CT (module)) + (component $D) + (type $T (record (field "x" u32))) + (alias outer 0 0 (core module $cm)) + (alias outer 0 0 (core type $ct)) + (alias outer 0 0 (component $comp)) + (alias outer 0 0 (type $ty)) + (alias outer $C $M (core module $cm2)) + (alias outer $C $CT (core type $ct2)) + (alias outer $C $D (component $comp2)) + (alias outer $C $T (type $ty2)) +) + +;; across a `component` boundary only substitutable definitions may be aliased +;; (core modules, core types, components, and resource-free types); generative +;; `resource`s may not. + +(component definition $C + (core module $M (func (export "f"))) + (core type $CT (module)) + (component $D (import "r" (type (sub resource)))) + (type $Pure (record (field "x" u32))) + (component + (alias outer $C $M (core module $cm)) + (alias outer $C $CT (core type $ct)) + (alias outer $C $D (component $comp)) + (alias outer $C $Pure (type $pure)) + ) +) + +(assert_invalid + (component $C + (type $R (resource (rep i32))) + (component (alias outer $C $R (type $a))) + ) + "transitively refers to resources") + +(assert_invalid + (component $C + (type $R (resource (rep i32))) + (type $B (borrow $R)) + (component (alias outer $C $B (type $a))) + ) + "transitively refers to resources") + +(assert_invalid + (component $C + (type $R (resource (rep i32))) + (type $Rec (record (field "h" (own $R)))) + (component (alias outer $C $Rec (type $a))) + ) + "transitively refers to resources") + +(assert_invalid + (component $C + (type $R (resource (rep i32))) + (component (component (alias outer 2 0 (type $a)))) + ) + "transitively refers to resources") + +(assert_invalid + (component $C + (type $R (resource (rep i32))) + (component (type (component + (alias outer 2 0 (type $a)) + ))) + ) + "transitively refers to resources") + +;; ct=0 (no boundary) +(component definition $C + (type $R (resource (rep i32))) + (type $B (borrow $R)) + (alias outer 0 0 (type $r)) + (alias outer 0 1 (type $b)) +) + +;; only a `type` boundary (component-type declarator) +(component definition $C + (type $R (resource (rep i32))) + (type $B (borrow $R)) + (type $L (list (own $R))) + (type (component + (alias outer $C $R (type $r)) + (alias outer $C $B (type $b)) + (alias outer $C $L (type $l)) + )) + (type (instance + (alias outer $C $R (type $r)) + )) +) + +;; mix of valid +(component definition $C + (type $Pure (record (field "x" u32))) + (component + (alias outer $C $Pure (type $pure)) + (type $R (resource (rep i32))) + (alias outer 0 1 (type $localR)) + ) +) + +;; de Bruijn counting: both a real `component` and a `(type (component ...))` +;; declarator count as one enclosing scope. +(component definition $C + (type $T (record (field "x" u32))) + (type (component + (alias outer 1 0 (type $parent)) + (type (component + (alias outer 2 0 (type $grandparent)) + )) + )) + (component + (alias outer 1 0 (type $parent)) + ) + (component $D + (alias outer $C $T (type $aD)) + (component + (alias outer $D $aD (type $aE)) + (type $use (tuple $aE $aE)) + ) + ) +) + +;; inside a `component`/`instance` type, `alias export` may only target +;; `instance`/`type` and `alias outer` may only target `core type`/`type` (a +;; subset of the sorts allowed for `alias` definitions). + +(component definition $C + (type (component + (import "i" (instance $i + (export "t" (type (sub resource))) + (export "j" (instance)) + )) + (alias export $i "t" (type $t)) + (alias export $i "j" (instance $j)) + )) + (core type $FT (func (param i32) (result i32))) + (type $T (record (field "x" u32))) + (type (component + (alias outer $C $FT (core type $ct)) + (alias outer $C $T (type $t)) + )) +) + +(assert_invalid + (component + (type (component + (import "i" (instance $i (export "f" (func)))) + (alias export $i "f" (func $a)) + )) + ) + "may only refer to types or instances") + +(assert_invalid + (component $C + (component $D) + (type (component (alias outer $C $D (component $a)))) + ) + "may only refer to types or instances") + +(assert_invalid + (component $C + (core module $M) + (type (component (alias outer $C $M (core module $a)))) + ) + "may only refer to types or instances") + +(assert_invalid + (component $C + (component $D) + (type (instance (alias outer $C $D (component $a)))) + ) + "may only refer to types or instances") + +;; acyclicity and index bounds + +(assert_invalid + (component $C + (alias outer 0 0 (type $a)) + (type $T (record (field "x" u32))) + ) + "index out of bounds") + +(assert_invalid + (component $C + (component (alias outer 1 0 (type $a))) + (type $T (record (field "x" u32))) + ) + "index out of bounds") + +(assert_invalid + (component $C + (type $T (record (field "x" u32))) + (component (alias outer 5 0 (type $a))) + ) + "invalid outer alias count") + +(assert_invalid + (component (alias outer 1 0 (type $a))) + "invalid outer alias count") + +(assert_invalid + (component $C + (type $T (record (field "x" u32))) + (component (alias outer 1 9 (type $a))) + ) + "index out of bounds") + +;; core:alias + +(component definition $C + (core type $FT (func (param i32) (result i32))) + (core type $MT (module + (alias outer $C $FT (type $a)) + (export "f" (func (type $a))) + )) + (core type $MT2 (module + (alias outer $C $FT (type)) + (import "a" "b" (func (type 0))) + (export "c" (func (type 0))) + )) +)