Description
I have started implementing type imports and exports in Binaryen. Binaryen provides a tool, wasm-merge
which allows to link together several Wasm modules, connecting exports and imports. Type imports and exports can be useful in this context even if no Wasm engine supports them yet.
Here are some thoughts and remarks I have gathered in the process.
Linking process
The proposal currently does not say anything about how imports and exports are matched. The read the imports phase of the WebAssembly JavaScript Interface will need to be updated.
Module component ordering in the text format
The imported types should occur before other types.
Abbreviations in the text format
Similarly to other module components, we can have the following abbreviations:
- for exports:
(type $t (export "t") ...)
; - for imports:
(type $t (import "foo" "bar" (sub ht)))
.
One should require that the size of recursive groups containing an imported type should be one. Or, maybe, that the imported types should occur in the same recursive group in the same order?
Casts in the extern
hierarchy
At the moment, there is no non-trivial casts in the extern
hierarchy. This could change with type imports. Maybe one should disallow casts in this type hierarchy to leave more implementation freedom.
Interaction with shared annotations
Given a type import (import "foo" "bar" (type $t (sub ht)))
, we can probably consider that the type $t
is shared iff the bound ht
is shared.
Type imports as composite types
Types are typically handled very differently from other module components by Wasm engines and tools. They don't keep a table of type definitions. Instead, types are directly canonicalized, type indices being replaced by references to canonical types. It does not make much sense to consider that low indices correspond to imports in this context. Instead, I have basically implemented type imports by adding another kind of composite type: (import "foo" "bar" (sub ht))
. Note that this works well with canonicalization: two imports can be considered equivalent if they have the same names and bounds.
It might actually make sense to do the same for the specification and the binary format. This could resolve the issue of repeated / interleaved sections, since type imports would be in the type section. This might also make it possible to relax the restriction of import bounds to abstract heap types. One could possibly go one step further and handle type imports as subtypes (sub ht (import "foo" "bar"))
, using the same encoding for the supertype and the import bound.
Types no longer closed under intersection
Binaryen makes some assumptions on types that no longer hold with type imports. In particular, there are several places in the code that assume that types are closed under intersection. Or that two heap types that are not in a subtyping relation (either $t ≤ $t'
or $t' ≤ $t
) have no common value.
I don't think this is an issue for this proposal. But implementers need to be aware that some assumptions they may have made previously might no longer be valid.
As a concrete example, Binaryen replaces the type of a cast by the greatest upper bound of this type and the type of the input expression. This is no longer correct: if $t
is an imported type and $t'
is not, their greatest upper bound is none
, but both types might actually become equal after instantiation. Disallowing casts for all imported typed would not avoid this cast optimization issue, since casting to a concrete type an expression with an imported type would still be allowed. Also, imported types do not need to be directly involved. These two composite types can become equal after instantiation, depending on the definitions of $u1
and $u2
:
(type $t1 (struct (field $u1)))
(type $t2 (struct (field $u2)))