See rationale for motivating scenarios.
Post-MVP 🦄, applications will be able to query which features are
supported via
has_feature or a similar API 🦄. This
accounts for the pragmatic reality that features are shipped in different orders
at different times by different engines.
What follows is a sketch of what such a feature testing capability could look like.
Since some WebAssembly features add operators and all WebAssembly code in a module is validated ahead-of-time, the usual JavaScript feature detection pattern:
if (foo)
foo();
else
alternativeToFoo();
won't work in WebAssembly (if foo isn't supported, foo() will fail to
validate).
Instead, applications may use one of the following strategies:
-
Compile several versions of a module, each assuming different feature support and use
has_featuretests to determine which version to load. -
During the "specific" layer decoding, which will happen in user code in the MVP anyway, use
has_featureto determine which features are supported and then translate unsupported feature use into either a polyfill or a trap.
Both of these options could be automatically provided by the toolchain and
controlled by compiler flags. Since has_feature is a constant expression,
it can be constant-folded by WebAssembly engines.
To illustrate, consider 4 examples:
i32.min_s🦄 - Strategy 2 could be used to translate(i32.min_s lhs rhs)into an equivalent expression that storeslhsandrhsin locals then usesi32.lt_sandselect.- Threads 🦄 - If an application uses
#ifdefextensively to produce thread-enabled/disabled builds, Strategy 1 would be appropriate. However, if the application was able to abstract use of threading to a few primitives, Strategy 2 could be used to patch in the right primitive implementation. mprotect🦄 - If engines aren't able to use OS signal handling to implementmprotectefficiently,mprotectmay become a permanently optional feature. For uses ofmprotectthat are not necessary for correctness (but rather just catching bugs),mprotectcould be replaced withnop. Ifmprotectwas necessary for correctness but an alternative strategy existed that did not rely onmprotect,mprotectcould be replaced with anabort()call, relying on the application to test(has_feature "mprotect")to avoid calling theabort(). Thehas_featurequery could be exposed to C++ code via the existing__builtin_cpu_supports.- SIMD - When SIMD operators have a good-enough
polyfill, e.g.,
f32x4.fmaviaf32x4.mul/add, Strategy 2 could be used (similar to thei32.min_sexample above). However, when a SIMD feature has no efficient polyfill (e.g.,f64x2, which introduces both operators and types), alternative algorithms need to be provided and selected at load time.
As a hypothetical (not implemented) example polyfilling the SIMD f64x2
feature, the C++ compiler could provide a new function attribute that indicated
that one function was an optimized, but feature-dependent, version of another
function (similar to the
ifunc attribute,
but without the callback):
#include <xmmintrin.h>
void foo(...) {
__m128 x, y; // -> f32x4 locals
...
x = _mm_add_ps(x, y); // -> f32x4.add
...
}
void foo_f64x2(...) __attribute__((optimizes("foo","f64x2"))) {
__m256 x, y; // -> f64x2 locals
...
x = _m_add_pd(x, y); // -> f64x2.add
...
}
...
foo(...); // calls either foo or foo_f64x2
In this example, the toolchain could emit both foo and foo_f64x2 as
function definitions in the "specific layer" binary format. The load-time
polyfill would then replace foo with foo_f64x2 if
(has_feature "f64x2"). Many other strategies are possible to allow finer or
coarser granularity substitution. Since this is all in userspace, the strategy
can evolve over time.
See also the better feature testing support 🦄 future feature.