From 6374503adf9d9a985df1509753eeec3fd1b314cf Mon Sep 17 00:00:00 2001 From: Ryan Hunt Date: Tue, 4 May 2021 15:58:26 +0000 Subject: [PATCH] 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 --- js/moz.configure | 25 ++++ js/public/WasmFeatures.h | 12 ++ .../tests/wasm/extended-const/basic.js | 95 ++++++++++++++ .../tests/wasm/extended-const/directives.txt | 1 + .../tests/wasm/extended-const/disabled.js | 22 ++++ js/src/jit-test/tests/wasm/features.js | 31 ++++- js/src/wasm/WasmBinary.h | 1 + js/src/wasm/WasmInitExpr.cpp | 118 ++++++++++++++++++ modules/libpref/init/StaticPrefList.yaml | 7 ++ 9 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 js/src/jit-test/tests/wasm/extended-const/basic.js create mode 100644 js/src/jit-test/tests/wasm/extended-const/directives.txt create mode 100644 js/src/jit-test/tests/wasm/extended-const/disabled.js diff --git a/js/moz.configure b/js/moz.configure index 1eed9799f0a6..4272f8ff59c7 100644 --- a/js/moz.configure +++ b/js/moz.configure @@ -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 # ===================================================== diff --git a/js/public/WasmFeatures.h b/js/public/WasmFeatures.h index 402f4fd4dcdd..4911db571a78 100644 --- a/js/public/WasmFeatures.h +++ b/js/public/WasmFeatures.h @@ -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, \ diff --git a/js/src/jit-test/tests/wasm/extended-const/basic.js b/js/src/jit-test/tests/wasm/extended-const/basic.js new file mode 100644 index 000000000000..ec3fe1c37568 --- /dev/null +++ b/js/src/jit-test/tests/wasm/extended-const/basic.js @@ -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); diff --git a/js/src/jit-test/tests/wasm/extended-const/directives.txt b/js/src/jit-test/tests/wasm/extended-const/directives.txt new file mode 100644 index 000000000000..c0893ecce9e6 --- /dev/null +++ b/js/src/jit-test/tests/wasm/extended-const/directives.txt @@ -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 diff --git a/js/src/jit-test/tests/wasm/extended-const/disabled.js b/js/src/jit-test/tests/wasm/extended-const/disabled.js new file mode 100644 index 000000000000..7cb83fa39470 --- /dev/null +++ b/js/src/jit-test/tests/wasm/extended-const/disabled.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); +} diff --git a/js/src/jit-test/tests/wasm/features.js b/js/src/jit-test/tests/wasm/features.js index 3d9dd2163737..c91761f92209 100644 --- a/js/src/jit-test/tests/wasm/features.js +++ b/js/src/jit-test/tests/wasm/features.js @@ -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) { diff --git a/js/src/wasm/WasmBinary.h b/js/src/wasm/WasmBinary.h index fe52379f4216..5c173541219b 100644 --- a/js/src/wasm/WasmBinary.h +++ b/js/src/wasm/WasmBinary.h @@ -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); diff --git a/js/src/wasm/WasmInitExpr.cpp b/js/src/wasm/WasmInitExpr.cpp index 039f96becd69..ad2141dc7b22 100644 --- a/js/src/wasm/WasmInitExpr.cpp +++ b/js/src/wasm/WasmInitExpr.cpp @@ -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(); } diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 87f02acfa804..5c94c0d67704 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -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