有关激励方案,请参见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_sANDselect。 - 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.fmaviaf32x4.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:][未来功能测试] 未来功能。