зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1706124 - wasm: Implement the extended-const proposal. r=lth
This commit implements the extended-constants proposal. * A new feature flag and pref are added. * Basic tests are added. Differential Revision: https://phabricator.services.mozilla.com/D112661
This commit is contained in:
Родитель
17eab55693
Коммит
6374503adf
|
@ -726,6 +726,31 @@ def enable_shared_memory(value):
|
|||
set_config("ENABLE_SHARED_MEMORY", enable_shared_memory)
|
||||
set_define("ENABLE_SHARED_MEMORY", enable_shared_memory)
|
||||
|
||||
# Support for WebAssembly extended constant expressions
|
||||
# =====================================================
|
||||
|
||||
|
||||
@depends(milestone.is_nightly)
|
||||
def default_wasm_extended_const(is_nightly):
|
||||
if is_nightly:
|
||||
return True
|
||||
|
||||
|
||||
option(
|
||||
"--enable-wasm-extended-const",
|
||||
default=default_wasm_extended_const,
|
||||
help="{Enable|Disable} WebAssembly extended constant expressions",
|
||||
)
|
||||
|
||||
|
||||
@depends("--enable-wasm-extended-const")
|
||||
def wasm_extended_const(value):
|
||||
if value:
|
||||
return True
|
||||
|
||||
|
||||
set_config("ENABLE_WASM_EXTENDED_CONST", wasm_extended_const)
|
||||
set_define("ENABLE_WASM_EXTENDED_CONST", wasm_extended_const)
|
||||
|
||||
# Support for WebAssembly SIMD
|
||||
# =====================================================
|
||||
|
|
|
@ -57,6 +57,11 @@
|
|||
#else
|
||||
# define WASM_RELAXED_SIMD_ENABLED 0
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_EXTENDED_CONST
|
||||
# define WASM_EXTENDED_CONST_ENABLED 1
|
||||
#else
|
||||
# define WASM_EXTENDED_CONST_ENABLED 0
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_EXCEPTIONS
|
||||
# define WASM_EXCEPTIONS_ENABLED 1
|
||||
#else
|
||||
|
@ -83,6 +88,13 @@
|
|||
js::jit::JitSupportsWasmSimd(), \
|
||||
/* shell flag */ "simd", \
|
||||
/* preference name */ "simd") \
|
||||
EXPERIMENTAL(/* capitalized name */ ExtendedConst, \
|
||||
/* lower case name */ extendedConst, \
|
||||
/* compile predicate */ WASM_EXTENDED_CONST_ENABLED, \
|
||||
/* compiler predicate */ true, \
|
||||
/* flag predicate */ true, \
|
||||
/* shell flag */ "extended-const", \
|
||||
/* preference name */ "extended_const") \
|
||||
EXPERIMENTAL( \
|
||||
/* capitalized name */ Exceptions, \
|
||||
/* lower case name */ exceptions, \
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
// |jit-test| skip-if: !wasmExtendedConstEnabled()
|
||||
|
||||
function testPrivateGlobal(valtype, expr, result) {
|
||||
// Immutable private globals have a single cell for wasm.
|
||||
let { get } = wasmEvalText(`(module
|
||||
(global $global ${valtype} ${expr})
|
||||
(func (export "get") (result ${valtype})
|
||||
global.get $global
|
||||
)
|
||||
)`).exports;
|
||||
assertEq(get(), result);
|
||||
}
|
||||
function testExportedGlobal(valtype, expr, result) {
|
||||
// Immutable exported globals have a separate cell for wasm and the exported
|
||||
// global object.
|
||||
let { global, get } = wasmEvalText(`(module
|
||||
(global $global (export "global") ${valtype} ${expr})
|
||||
(func (export "get") (result ${valtype})
|
||||
global.get $global
|
||||
)
|
||||
)`).exports;
|
||||
assertEq(get(), result);
|
||||
assertEq(global.value, result);
|
||||
}
|
||||
function testIndirectGlobal(valtype, expr, result) {
|
||||
// Mutable exported globals share an indirect cell for wasm and the exported
|
||||
// global object.
|
||||
let { global } = wasmEvalText(`(module
|
||||
(global (export "global") (mut ${valtype}) ${expr})
|
||||
)`).exports;
|
||||
assertEq(global.value, result);
|
||||
}
|
||||
|
||||
// i32 tests
|
||||
|
||||
const I32_SQ_OVERFLOW = 0xFFFF + 1;
|
||||
const MAX_I32 = 0xFFFF_FFFF;
|
||||
function testI32(expr, result) {
|
||||
testPrivateGlobal('i32', expr, result);
|
||||
testExportedGlobal('i32', expr, result);
|
||||
testIndirectGlobal('i32', expr, result);
|
||||
}
|
||||
testI32('i32.const 1', 1);
|
||||
|
||||
testI32('i32.const 1 i32.const 2 i32.add', 3);
|
||||
testI32(`i32.const ${MAX_I32} i32.const 1 i32.add`, 0);
|
||||
|
||||
testI32('i32.const 1 i32.const 2 i32.sub', 1);
|
||||
testI32(`i32.const 1 i32.const 0 i32.sub`, -1);
|
||||
|
||||
testI32('i32.const 1 i32.const 2 i32.mul', 2);
|
||||
testI32(`i32.const ${I32_SQ_OVERFLOW} i32.const ${I32_SQ_OVERFLOW} i32.mul`, 0);
|
||||
|
||||
// i64 tests
|
||||
|
||||
const I64_SQ_OVERFLOW = 0xFFFF_FFFFn + 1n;
|
||||
const MAX_I64 = 0xFFFF_FFFF_FFFF_FFFFn;
|
||||
function testI64(expr, result) {
|
||||
testPrivateGlobal('i64', expr, result);
|
||||
testExportedGlobal('i64', expr, result);
|
||||
testIndirectGlobal('i64', expr, result);
|
||||
}
|
||||
testI64('i64.const 1', 1n);
|
||||
|
||||
testI64('i64.const 1 i64.const 2 i64.add', 3n);
|
||||
testI64(`i64.const ${MAX_I64} i64.const 1 i64.add`, 0n);
|
||||
|
||||
testI64('i64.const 1 i64.const 2 i64.sub', 1n);
|
||||
testI64(`i64.const 1 i64.const 0 i64.sub`, -1n);
|
||||
|
||||
testI64('i64.const 1 i64.const 2 i64.mul', 2n);
|
||||
testI64(`i64.const ${I64_SQ_OVERFLOW} i64.const ${I64_SQ_OVERFLOW} i64.mul`, 0n);
|
||||
|
||||
// test global.get
|
||||
|
||||
function testGlobalGet(valtype, aExpr, bExpr, cExpr, cResult) {
|
||||
let { a, b } = wasmEvalText(`(module
|
||||
(global (export "a") ${valtype} ${aExpr})
|
||||
(global (export "b") ${valtype} ${bExpr})
|
||||
)`).exports;
|
||||
let { c } = wasmEvalText(`(module
|
||||
(global $a (import "" "a") ${valtype})
|
||||
(global $b (import "" "b") ${valtype})
|
||||
(global (export "c") ${valtype} ${cExpr})
|
||||
)`, {"": {a, b}}).exports;
|
||||
assertEq(c.value, cResult);
|
||||
}
|
||||
|
||||
testGlobalGet('i32', 'i32.const 2', 'i32.const 3', 'global.get $a global.get $b i32.add', 5);
|
||||
testGlobalGet('i32', 'i32.const 2', 'i32.const 3', 'global.get $a global.get $b i32.sub', 1);
|
||||
testGlobalGet('i32', 'i32.const 2', 'i32.const 3', 'global.get $a global.get $b i32.mul', 6);
|
||||
|
||||
testGlobalGet('i64', 'i64.const 2', 'i64.const 3', 'global.get $a global.get $b i64.add', 5n);
|
||||
testGlobalGet('i64', 'i64.const 2', 'i64.const 3', 'global.get $a global.get $b i64.sub', 1n);
|
||||
testGlobalGet('i64', 'i64.const 2', 'i64.const 3', 'global.get $a global.get $b i64.mul', 6n);
|
|
@ -0,0 +1 @@
|
|||
|jit-test| --wasm-extended-const; test-also=--wasm-compiler=optimizing; test-also=--wasm-compiler=baseline; test-also=--test-wasm-await-tier2; include:wasm.js
|
|
@ -0,0 +1,22 @@
|
|||
// |jit-test| skip-if: wasmExtendedConstEnabled()
|
||||
|
||||
const { CompileError, validate } = WebAssembly;
|
||||
|
||||
const DISABLED = /extended constant expressions not enabled|unexpected initializer opcode/;
|
||||
|
||||
let tests = [
|
||||
"(module (global i32 i32.const 0 i32.const 0 i32.add))",
|
||||
"(module (global i32 i32.const 0 i32.const 0 i32.sub))",
|
||||
"(module (global i32 i32.const 0 i32.const 0 i32.mul))",
|
||||
"(module (global i64 i64.const 0 i64.const 0 i64.add))",
|
||||
"(module (global i64 i64.const 0 i64.const 0 i64.sub))",
|
||||
"(module (global i64 i64.const 0 i64.const 0 i64.mul))",
|
||||
];
|
||||
|
||||
// Test that use of extended constants fails when disabled.
|
||||
|
||||
for (let src of tests) {
|
||||
let bin = wasmTextToBinary(src);
|
||||
assertEq(validate(bin), false);
|
||||
wasmCompilationShouldFail(bin, DISABLED);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// |jit-test| test-also=--wasm-exceptions; test-also=--wasm-function-references; test-also=--wasm-gc
|
||||
// |jit-test| test-also=--wasm-extended-const; test-also=--wasm-exceptions; test-also=--wasm-function-references; test-also=--wasm-gc
|
||||
|
||||
// Test that if a feature is 'experimental' then we must be in a nightly build,
|
||||
// and if a feature is 'released' then it must be enabled on release and beta.
|
||||
|
@ -24,9 +24,32 @@ let { release_or_beta } = getBuildConfiguration();
|
|||
let nightly = !release_or_beta;
|
||||
|
||||
let nightlyOnlyFeatures = [
|
||||
['exceptions', wasmExceptionsEnabled(), `(module (type (func)) (event (type 0)))`],
|
||||
['function-references', wasmFunctionReferencesEnabled(), `(module (func (param (ref extern))))`],
|
||||
['gc', wasmGcEnabled(), `(module (type $s (struct)) (func (param (ref null $s))))`],
|
||||
[
|
||||
'extended-const',
|
||||
wasmExtendedConstEnabled(),
|
||||
`(module
|
||||
(global i32
|
||||
i32.const 0
|
||||
i32.const 0
|
||||
i32.add
|
||||
)
|
||||
)`
|
||||
],
|
||||
[
|
||||
'exceptions',
|
||||
wasmExceptionsEnabled(),
|
||||
`(module (type (func)) (event (type 0)))`
|
||||
],
|
||||
[
|
||||
'function-references',
|
||||
wasmFunctionReferencesEnabled(),
|
||||
`(module (func (param (ref extern))))`
|
||||
],
|
||||
[
|
||||
'gc',
|
||||
wasmGcEnabled(),
|
||||
`(module (type $s (struct)) (func (param (ref null $s))))`
|
||||
],
|
||||
];
|
||||
|
||||
for (let [name, enabled, test] of nightlyOnlyFeatures) {
|
||||
|
|
|
@ -455,6 +455,7 @@ class Decoder {
|
|||
|
||||
// Instruction immediates for constant instructions
|
||||
|
||||
[[nodiscard]] bool readBinary() { return true; }
|
||||
[[nodiscard]] bool readGetGlobal(uint32_t* id);
|
||||
[[nodiscard]] bool readI32Const(int32_t* i32);
|
||||
[[nodiscard]] bool readI64Const(int64_t* i64);
|
||||
|
|
|
@ -52,6 +52,9 @@ static bool ValidateInitExpr(Decoder& d, ModuleEnvironment* env,
|
|||
return false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_WASM_EXTENDED_CONST
|
||||
Nothing nothing;
|
||||
#endif
|
||||
NothingVector nothings{};
|
||||
ResultType unusedType;
|
||||
|
||||
|
@ -142,6 +145,32 @@ static bool ValidateInitExpr(Decoder& d, ModuleEnvironment* env,
|
|||
*literal = Some(LitVal(ValType(type)));
|
||||
break;
|
||||
}
|
||||
#ifdef ENABLE_WASM_EXTENDED_CONST
|
||||
case uint16_t(Op::I32Add):
|
||||
case uint16_t(Op::I32Sub):
|
||||
case uint16_t(Op::I32Mul): {
|
||||
if (!env->extendedConstEnabled()) {
|
||||
return d.fail("unexpected initializer opcode");
|
||||
}
|
||||
if (!iter.readBinary(ValType::I32, ¬hing, ¬hing)) {
|
||||
return false;
|
||||
}
|
||||
*literal = Nothing();
|
||||
break;
|
||||
}
|
||||
case uint16_t(Op::I64Add):
|
||||
case uint16_t(Op::I64Sub):
|
||||
case uint16_t(Op::I64Mul): {
|
||||
if (!env->extendedConstEnabled()) {
|
||||
return d.fail("unexpected initializer opcode");
|
||||
}
|
||||
if (!iter.readBinary(ValType::I64, ¬hing, ¬hing)) {
|
||||
return false;
|
||||
}
|
||||
*literal = Nothing();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default: {
|
||||
return d.fail("unexpected initializer opcode");
|
||||
}
|
||||
|
@ -184,6 +213,19 @@ class MOZ_STACK_CLASS InitExprInterpreter {
|
|||
return stack.append(Val(RefType::func(), ref));
|
||||
}
|
||||
|
||||
#ifdef ENABLE_WASM_EXTENDED_CONST
|
||||
int32_t popI32() {
|
||||
uint32_t result = stack.back().i32();
|
||||
stack.popBack();
|
||||
return int32_t(result);
|
||||
}
|
||||
int64_t popI64() {
|
||||
uint64_t result = stack.back().i64();
|
||||
stack.popBack();
|
||||
return int64_t(result);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool evalGetGlobal(uint32_t index) {
|
||||
return stack.append(globalImportValues[index]);
|
||||
}
|
||||
|
@ -200,6 +242,44 @@ class MOZ_STACK_CLASS InitExprInterpreter {
|
|||
return pushFuncRef(FuncRef::fromCompiledCode(fnref));
|
||||
}
|
||||
bool evalRefNull(RefType type) { return pushRef(type, AnyRef::null()); }
|
||||
#ifdef ENABLE_WASM_EXTENDED_CONST
|
||||
bool evalI32Add() {
|
||||
uint32_t a = popI32();
|
||||
uint32_t b = popI32();
|
||||
pushI32(a + b);
|
||||
return true;
|
||||
}
|
||||
bool evalI32Sub() {
|
||||
uint32_t a = popI32();
|
||||
uint32_t b = popI32();
|
||||
pushI32(a - b);
|
||||
return true;
|
||||
}
|
||||
bool evalI32Mul() {
|
||||
uint32_t a = popI32();
|
||||
uint32_t b = popI32();
|
||||
pushI32(a * b);
|
||||
return true;
|
||||
}
|
||||
bool evalI64Add() {
|
||||
uint64_t a = popI64();
|
||||
uint64_t b = popI64();
|
||||
pushI64(a + b);
|
||||
return true;
|
||||
}
|
||||
bool evalI64Sub() {
|
||||
uint64_t a = popI64();
|
||||
uint64_t b = popI64();
|
||||
pushI64(a - b);
|
||||
return true;
|
||||
}
|
||||
bool evalI64Mul() {
|
||||
uint64_t a = popI64();
|
||||
uint64_t b = popI64();
|
||||
pushI64(a * b);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
bool InitExprInterpreter::evaluate(Decoder& d) {
|
||||
|
@ -276,6 +356,44 @@ bool InitExprInterpreter::evaluate(Decoder& d) {
|
|||
}
|
||||
CHECK(evalRefNull(type));
|
||||
}
|
||||
#ifdef ENABLE_WASM_EXTENDED_CONST
|
||||
case uint16_t(Op::I32Add): {
|
||||
if (!d.readBinary()) {
|
||||
return false;
|
||||
}
|
||||
CHECK(evalI32Add());
|
||||
}
|
||||
case uint16_t(Op::I32Sub): {
|
||||
if (!d.readBinary()) {
|
||||
return false;
|
||||
}
|
||||
CHECK(evalI32Sub());
|
||||
}
|
||||
case uint16_t(Op::I32Mul): {
|
||||
if (!d.readBinary()) {
|
||||
return false;
|
||||
}
|
||||
CHECK(evalI32Mul());
|
||||
}
|
||||
case uint16_t(Op::I64Add): {
|
||||
if (!d.readBinary()) {
|
||||
return false;
|
||||
}
|
||||
CHECK(evalI64Add());
|
||||
}
|
||||
case uint16_t(Op::I64Sub): {
|
||||
if (!d.readBinary()) {
|
||||
return false;
|
||||
}
|
||||
CHECK(evalI64Sub());
|
||||
}
|
||||
case uint16_t(Op::I64Mul): {
|
||||
if (!d.readBinary()) {
|
||||
return false;
|
||||
}
|
||||
CHECK(evalI64Mul());
|
||||
}
|
||||
#endif
|
||||
default: {
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
|
|
@ -5792,6 +5792,13 @@
|
|||
mirror: always
|
||||
#endif // defined(ENABLE_WASM_RELAXED_SIMD)
|
||||
|
||||
#if defined(ENABLE_WASM_EXTENDED_CONST)
|
||||
- name: javascript.options.wasm_extended_const
|
||||
type: bool
|
||||
value: @IS_NIGHTLY_BUILD@
|
||||
mirror: always
|
||||
#endif // defined(ENABLE_WASM_EXTENDED_CONST)
|
||||
|
||||
#if defined(ENABLE_WASM_EXCEPTIONS)
|
||||
- name: javascript.options.wasm_exceptions
|
||||
type: bool
|
||||
|
|
Загрузка…
Ссылка в новой задаче