Description
I was chatting with @sbc100, and we came up with an idea that seems like it might work, providing a way to do feature testing in a single module. The current suggestion is to call WebAssembly.validate()
on a tiny feature-test module, then use this information to fetch a specific module. We could codify this in a new feature-test section, that must come before all other sections:
Section Name | Code |
---|---|
FeatureTest | X |
Type | 1 |
Function | 2 |
... | ... |
This section would itself contain the modules that should be validated:
FeatureTest
Field | Type | Description |
---|---|---|
count | varuint32 |
count of modules to follow |
modules | module* |
sequence of modules |
a module
is:
Field | Type | Description |
---|---|---|
size | varuint32 |
size of data (in bytes) |
data | bytes |
sequence of size bytes |
This creates an index space of valid and invalid modules.
As an alternative, the FeatureTest
section could just contain a list of names. Each one names a feature. So for example, simd
might mean that the v128
type is available, as well as all of the simd instructions.
FeatureTest (alternative)
Field | Type | Description |
---|---|---|
count | varuint32 |
count of names to follow |
names | name* |
sequence of names |
Then, we allow each section to be specified multiple times, as long as they are still in the correct order. The behavior is as-if the sequence in the section were concatenated. The Start section would behave the same (perhaps we would allow multiple start functions, though that's not required). The DataCount section would sum all of the counts.
We need some way to mark the section as conditional; one way is to use a new section code. Like other sections, it is required to come in a specified order. Unlike other sections, it inherits the order of the section it is emulating. The format could be:
Conditional
Field | Type | Description |
---|---|---|
id | varuint32 |
Section id to emulate |
condition | condition |
Condition that must be met to include this section |
contents | ... |
The regular contents of a section with id id |
I'm not quite sure how we'd want to encode a condition
, but the idea is that each module that was previously validated in the FeatureTest
section gets a bit, and we can perform a logical operation on the bits to determine whether this section is included. One cute way to do it would be by evaluating an expression, perhaps where each local is given the value 1 for a valid module (or feature name that engine supports) and 0 for invalid (or feature name that engine doesn't support), e.g.
;; include this section if either module 0 or module 1 are valid
(i32.or (local.get 0) (local.get 1))
;; include this section if module 0 is not valid
(i32.eqz (local.get 0))
When an engine is validating a module, it will skip any Conditional section where the condition is not met. This does mean that the index space may be different, depending on which features are enabled. It's up to the tool generating the module to ensure that this makes sense.
Example
For SIMD, we expect that there will be many functions that are shared, and only a few that need to have scalar fallback. At first, a tool can generate both sections and interleave them in the binary:
Section Index | Section | Condition | Count |
---|---|---|---|
0 | FeatureTest | N/A | |
1 | Type | !SIMD | 5 |
2 | Type | SIMD | 6 |
3 | Function | !SIMD | 20 |
4 | Function | SIMD | 30 |
Note that it is not necessary for the number of entries in each section to be the same.
Later, the tool could optimize this and produce a common section where possible:
Section Index | Section | Condition | Count |
---|---|---|---|
0 | FeatureTest | * | N/A |
1 | Type | * | 5 |
2 | Type | SIMD | 1 |
3 | Function | * | 17 |
4 | Function | !SIMD | 3 |
5 | Function | SIMD | 13 |
In this example, types 0 through 4 can be shared, and only type 5 needs SIMD. Similarly, functions 0 through 16 are shared. Functions 17 through 19 have different implementations for SIMD and !SIMD. Logically, these should have the same behavior, but there's no way to enforce this, of course. Functions 20 through 29 are only valid when SIMD is enabled.
Globals
Using globals, we can even export the results of the feature test:
Section Index | Section | Condition | Count |
---|---|---|---|
0 | FeatureTest | * | N/A |
1 | Global | SIMD | 1 |
2 | Global | !SIMD | 1 |
Where we set the value of global 0 to 1 if SIMD is true.
Imports and Exports
We could potentially have different imports and exports. For example, we could export an additional function when SIMD is enabled.
Similarly, if we allow the feature-tested modules to reference imports, we could provide a (admittedly clunky) weak-import mechanism. If an import is not provided, we can generate a stub function instead. This would be tricky to get right, since the imports have to come before all defined functions, but it may be possible.
You could use this for an optional memory import too. If the memory import is provided, then use it. Otherwise generate your own memory section. Not sure why you'd want to do this, however. :-)