有关激励方案,请参见rationale。
[后 MVP:unicorn:][未来通用],应用程序将能够通过 [ has_feature
或类似的 API:unicorn:][未来功能测试] 查询支持哪些功能。这解释了不同引擎在不同时间以不同顺序交付功能的实际情况。
下面是这样一个特性测试功能的草图。
由于某些 WebAssembly 功能添加了运算符,并且模块中的所有 WebAssembly 代码都是提前验证的,因此通常的 JavaScript 功能检测模式:
if (foo)
foo();
else
alternativeToFoo();
在 WebAssembly 中不起作用(如果 foo
不支持, foo()
将无法验证)。
相反,应用程序可以使用以下策略之一:
-
编译一个模块的多个版本,每个版本假定不同的功能支持,并使用
has_feature
测试来确定要加载哪个版本。 -
在期间“特定”层解码(将在 MVP无论如何的用户代码中发生),使用
has_feature
确定支持哪些功能,然后将不支持的功能使用转换为多边形填充或陷印。
这两个选项都可以由工具链自动提供,并由编译器标志控制。因为 has_feature
是一个常量表达式,所以 WebAssembly 引擎可以对其进行常量折叠。
为了说明,考虑 4 个例子:
-
i32.min_s
🦄-策略 2 可用于转换(i32.min_s lhs rhs)
为等效表达式,该表达式lhs
将 ANDrhs
存储在局部变量中,然后使用i32.lt_s
ANDselect
。 - Threads:unicorn:-如果应用程序广泛使用
#ifdef
以生成启用/禁用线程的构建,则策略 1 将是合适的。但是,如果应用程序能够将线程的使用抽象为几个原语,则可以使用策略 2 来修补正确的原语实现。 -
mprotect
🦄-如果引擎不能使用操作系统信号处理来有效实现mprotect
,则mprotect
可能成为永久可选功能。因为它的使用mprotect
不是正确性所必需的(而只是捕捉错误),mprotect
可以替换为nop
。如果mprotect
对于正确性是必要的,但存在不依赖mprotect
的替代策略,mprotect
则可以替换为abort()
调用,依赖于要测试(has_feature "mprotect")
的应用程序以避免调用abort()
。has_feature
查询可以通过现有__builtin_cpu_supports
的向 C++ 代码公开。 - SIMD-当 SIMD 运算符具有足够好的 polyfill(例如
f32x4.fma
viaf32x4.mul
/add
)时,可以使用策略 2(类似于上面的i32.min_s
示例)。然而,当 SIMD 特征不具有有效的 polyfill(例如,f64x2
其引入两种运算符和类型)时,需要在加载时提供并选择替代算法。
作为填充 SIMD f64x2
特性的一个假设(未实现)示例,C++ 编译器可以提供一个新的 Function 属性,该属性指示一个函数是另一个函数的优化但依赖于特性的版本(类似于 [ ifunc
Attribute](https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/function-attributes.html#index-g_t_0040code_007bifunc_007d-attribute-2529),但没有回调):
#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
在此示例中,工具链可以发出 foo
和 foo_f64x2
作为“特定层”二进制格式的函数定义。然后,加载时间 polyfill 将替换 foo
为 foo_f64x2
if (has_feature "f64x2")
。许多其他策略可以允许更细或更粗的粒度替换。由于这一切都在用户空间中,策略可以随着时间的推移而发展。
另请参阅 [更好的功能测试支持:unicorn:][未来功能测试] 未来功能。