From edf14d2ca2d3ce967e23ba9604edbce3944c0d14 Mon Sep 17 00:00:00 2001 From: Ryan Hunt Date: Thu, 30 Nov 2023 00:46:32 +0000 Subject: [PATCH] Bug 1863794 - wasm: Implement js-string-builtins. r=yury,jandem This commit implements the core of js-string-builtins. (1) Builtin module funcs are added for all the operations. (2) Instantiation of a module is updated to instantiate a js-string builtin module and provide it to imports, if the builtin module was requested. (3) Validation of imports is updated to check that imports that refer to the js-string builtin module will match the later imported module. This ensures that there will be no link errors later. Testing is done by defining a polyfill module and cross-checking the behavior of the builtins. The type signatures of these builtin functions are not quite correct when it comes to (array i16) that the proposal has. Right now they are defined as anyref with a dynamic type check. This is deferred to a followup commit. A key piece of js-string-builtins will be emitting inline code for some of these builtins (such as getCodeUnit). This is deferred to a future patch. Depends on D193113 Differential Revision: https://phabricator.services.mozilla.com/D193114 --- js/moz.configure | 9 +- js/public/friend/ErrorNumbers.msg | 1 + js/src/builtin/TestingFunctions.cpp | 3 +- .../wasm/builtin-modules/js-string/basic.js | 316 ++++++++++++++++++ .../builtin-modules/js-string/directives.txt | 1 + js/src/jit/ABIFunctionType.yaml | 9 + js/src/jit/MacroAssembler.cpp | 4 + js/src/vm/StringType.h | 29 ++ js/src/wasm/WasmAnyRef.h | 2 + js/src/wasm/WasmBuiltinModule.cpp | 102 +++++- js/src/wasm/WasmBuiltinModule.h | 45 ++- js/src/wasm/WasmBuiltinModule.yaml | 147 ++++++++ js/src/wasm/WasmBuiltins.cpp | 1 + js/src/wasm/WasmBuiltins.h | 1 + js/src/wasm/WasmCompile.cpp | 2 + js/src/wasm/WasmInstance.cpp | 270 +++++++++++++++ js/src/wasm/WasmInstance.h | 20 ++ js/src/wasm/WasmJS.cpp | 104 +++--- js/src/wasm/WasmSerialize.cpp | 2 +- js/src/wasm/WasmValidate.cpp | 82 ++++- 20 files changed, 1091 insertions(+), 59 deletions(-) create mode 100644 js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js create mode 100644 js/src/jit-test/tests/wasm/builtin-modules/js-string/directives.txt diff --git a/js/moz.configure b/js/moz.configure index 5cd1fa398a08..267b40cf587c 100644 --- a/js/moz.configure +++ b/js/moz.configure @@ -777,6 +777,7 @@ set_define("ENABLE_WASM_GC", wasm_gc) # Support for WebAssembly JS String Builtins # ========================================== + @depends(milestone.is_nightly) def default_wasm_js_string_builtins(is_nightly): if is_nightly: @@ -784,13 +785,13 @@ def default_wasm_js_string_builtins(is_nightly): option( - "--enable-wasm-js-string-builtins", default=default_wasm_js_string_builtins, help="{Enable|Disable} WebAssembly JS String Builtins" + "--enable-wasm-js-string-builtins", + default=default_wasm_js_string_builtins, + help="{Enable|Disable} WebAssembly JS String Builtins", ) -@depends( - "--enable-wasm-js-string-builtins", "--wasm-no-experimental" -) +@depends("--enable-wasm-js-string-builtins", "--wasm-no-experimental") def wasm_js_string_builtins(value, no_experimental): if no_experimental or not value: return diff --git a/js/public/friend/ErrorNumbers.msg b/js/public/friend/ErrorNumbers.msg index b62e33438b01..3236831ae288 100644 --- a/js/public/friend/ErrorNumbers.msg +++ b/js/public/friend/ErrorNumbers.msg @@ -484,6 +484,7 @@ MSG_DEF(JSMSG_WASM_BAD_FUNCTION_VALUE, 0, JSEXN_TYPEERR, "second argument mu MSG_DEF(JSMSG_WASM_BAD_COMPILE_OPTIONS, 0, JSEXN_TYPEERR, "argument cannot be converted to a CompileOptions") MSG_DEF(JSMSG_WASM_UNKNOWN_BUILTIN, 0, JSEXN_TYPEERR, "unknown builtin") MSG_DEF(JSMSG_WASM_DUPLICATE_BUILTIN, 0, JSEXN_TYPEERR, "duplicate builtin") +MSG_DEF(JSMSG_WASM_BAD_CODEPOINT, 0, JSEXN_WASMRUNTIMEERROR, "bad codepoint") MSG_DEF(JSMSG_WASM_NO_TRANSFER, 0, JSEXN_TYPEERR, "cannot transfer WebAssembly/asm.js ArrayBuffer") MSG_DEF(JSMSG_WASM_TEXT_FAIL, 1, JSEXN_SYNTAXERR, "wasm text error: {0}") MSG_DEF(JSMSG_WASM_MISSING_MAXIMUM, 0, JSEXN_TYPEERR, "'shared' is true but maximum is not specified") diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 9e68f99092ca..c7f90ce1f083 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -2118,9 +2118,8 @@ static bool WasmBuiltinI8VecMul(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - wasm::BuiltinModuleFuncId ids[] = {wasm::BuiltinModuleFuncId::I8VecMul}; Rooted module(cx); - if (!wasm::CompileBuiltinModule(cx, ids, Some(wasm::Shareable::False), + if (!wasm::CompileBuiltinModule(cx, wasm::BuiltinModuleId::SelfTest, &module)) { return false; } diff --git a/js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js b/js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js new file mode 100644 index 000000000000..dce6204fee74 --- /dev/null +++ b/js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js @@ -0,0 +1,316 @@ +// |jit-test| skip-if: !wasmJSStringBuiltinsEnabled(); + +let testModule = wasmTextToBinary(`(module + (func + (import "wasm:js-string" "fromWTF16Array") + (param anyref i32 i32) + (result externref) + ) + (export "fromWTF16Array" (func 0)) + + (func + (import "wasm:js-string" "toWTF16Array") + (param externref anyref i32) + (result i32) + ) + (export "toWTF16Array" (func 1)) + + (func + (import "wasm:js-string" "fromCharCode") + (param i32) + (result externref) + ) + (export "fromCharCode" (func 2)) + + (func + (import "wasm:js-string" "fromCodePoint") + (param i32) + (result externref) + ) + (export "fromCodePoint" (func 3)) + + (func + (import "wasm:js-string" "charCodeAt") + (param externref i32) + (result i32) + ) + (export "charCodeAt" (func 4)) + + (func + (import "wasm:js-string" "codePointAt") + (param externref i32) + (result i32) + ) + (export "codePointAt" (func 5)) + + (func + (import "wasm:js-string" "length") + (param externref) + (result i32) + ) + (export "length" (func 6)) + + (func + (import "wasm:js-string" "concatenate") + (param externref externref) + (result externref) + ) + (export "concatenate" (func 7)) + + (func + (import "wasm:js-string" "substring") + (param externref i32 i32) + (result externref) + ) + (export "substring" (func 8)) + + (func + (import "wasm:js-string" "equals") + (param externref externref) + (result i32) + ) + (export "equals" (func 9)) + + (func + (import "wasm:js-string" "compare") + (param externref externref) + (result i32) + ) + (export "compare" (func 10)) +)`); + +let { + createArray, + arrayLength, + arraySet, + arrayGet +} = wasmEvalText(`(module + (type $i16Array (array (mut i16))) + (func (export "createArray") (param i32) (result anyref) + i32.const 0 + local.get 0 + array.new $i16Array + ) + (func (export "arrayLength") (param arrayref) (result i32) + local.get 0 + array.len + ) + (func (export "arraySet") (param (ref $i16Array) i32 i32) + local.get 0 + local.get 1 + local.get 2 + array.set $i16Array + ) + (func (export "arrayGet") (param (ref $i16Array) i32) (result i32) + local.get 0 + local.get 1 + array.get_u $i16Array + ) +)`).exports; + +function throwIfNotString(a) { + if (typeof a !== "string") { + throw new WebAssembly.RuntimeError(); + } +} +let polyFillImports = { + fromWTF16Array: (array, arrayStart, arrayCount) => { + arrayStart |= 0; + arrayCount |= 0; + let length = arrayLength(array); + if (BigInt(arrayStart) + BigInt(arrayCount) > BigInt(length)) { + throw new WebAssembly.RuntimeError(); + } + let result = ''; + for (let i = arrayStart; i < arrayStart + arrayCount; i++) { + result += String.fromCharCode(arrayGet(array, i)); + } + return result; + }, + toWTF16Array: (string, arr, arrayStart) => { + arrayStart |= 0; + throwIfNotString(string); + let arrLength = arrayLength(arr); + let stringLength = string.length; + if (BigInt(arrayStart) + BigInt(stringLength) > BigInt(arrLength)) { + throw new WebAssembly.RuntimeError(); + } + for (let i = 0; i < stringLength; i++) { + arraySet(arr, arrayStart + i, string[i].charCodeAt(0)); + } + return stringLength; + }, + fromCharCode: (charCode) => { + charCode |= 0; + return String.fromCharCode(charCode); + }, + fromCodePoint: (codePoint) => { + codePoint |= 0; + return String.fromCodePoint(codePoint); + }, + charCodeAt: (string, stringIndex) => { + stringIndex |= 0; + throwIfNotString(string); + if (stringIndex >= string.length) + throw new WebAssembly.RuntimeError(); + return string.charCodeAt(stringIndex); + }, + codePointAt: (string, stringIndex) => { + stringIndex |= 0; + throwIfNotString(string); + if (stringIndex >= string.length) + throw new WebAssembly.RuntimeError(); + return string.codePointAt(stringIndex); + }, + length: (string) => { + throwIfNotString(string); + return string.length; + }, + concatenate: (stringA, stringB) => { + throwIfNotString(stringA); + throwIfNotString(stringB); + return stringA + stringB; + }, + substring: (string, startIndex, endIndex) => { + startIndex |= 0; + endIndex |= 0; + throwIfNotString(string); + if (startIndex > string.length, + endIndex > string.length, + endIndex < startIndex) { + return ""; + } + return string.substring(startIndex, endIndex); + }, + equals: (stringA, stringB) => { + throwIfNotString(stringA); + throwIfNotString(stringB); + return stringA === stringB; + }, + compare: (stringA, stringB) => { + throwIfNotString(stringA); + throwIfNotString(stringB); + if (stringA < stringB) { + return -1; + } + return stringA === stringB ? 0 : 1; + }, +}; + +function assertSameBehavior(funcA, funcB, ...params) { + let resultA; + let errA = null; + try { + resultA = funcA(...params); + } catch (err) { + errA = err; + } + + let resultB; + let errB = null; + try { + resultB = funcB(...params); + } catch (err) { + errB = err; + } + + if (errA || errB) { + assertEq(errA === null, errB === null, errA ? errA.message : errB.message); + assertEq(Object.getPrototypeOf(errA), Object.getPrototypeOf(errB)); + assertEq(errA.message, errB.message); + } + assertEq(resultA, resultB); + + if (errA) { + throw errA; + } + return resultA; +} + +let builtinExports = new WebAssembly.Instance(new WebAssembly.Module(testModule, {builtins: ["js-string"]}), {}).exports; +let polyfillExports = new WebAssembly.Instance(new WebAssembly.Module(testModule), { 'wasm:js-string': polyFillImports }).exports; + +let testStrings = ["", "a", "1", "ab", "hello, world", "\n", "☺", "☺smiley", String.fromCodePoint(0x10000, 0x10001)]; +let testCharCodes = [1, 2, 3, 10, 0x7f, 0xff, 0xfffe, 0xffff]; +let testCodePoints = [1, 2, 3, 10, 0x7f, 0xff, 0xfffe, 0xffff, 0x10000, 0x10001]; + +for (let a of testCharCodes) { + assertSameBehavior( + builtinExports['fromCharCode'], + polyfillExports['fromCharCode'], + a + ); +} + +for (let a of testCodePoints) { + assertSameBehavior( + builtinExports['fromCodePoint'], + polyfillExports['fromCodePoint'], + a + ); +} + +for (let a of testStrings) { + let length = assertSameBehavior( + builtinExports['length'], + polyfillExports['length'], + a + ); + + for (let i = 0; i < length; i++) { + let charCode = assertSameBehavior( + builtinExports['charCodeAt'], + polyfillExports['charCodeAt'], + a, i + ); + } + for (let i = 0; i < length; i++) { + let charCode = assertSameBehavior( + builtinExports['codePointAt'], + polyfillExports['codePointAt'], + a, i + ); + } + + let array = createArray(length); + assertSameBehavior( + builtinExports['toWTF16Array'], + polyfillExports['toWTF16Array'], + a, array, 0 + ); + assertSameBehavior( + builtinExports['fromWTF16Array'], + polyfillExports['fromWTF16Array'], + array, 0, length + ); + + for (let i = 0; i < length; i++) { + for (let j = 0; j < length; j++) { + assertSameBehavior( + builtinExports['substring'], + polyfillExports['substring'], + a, i, j + ); + } + } +} + +for (let a of testStrings) { + for (let b of testStrings) { + assertSameBehavior( + builtinExports['concatenate'], + polyfillExports['concatenate'], + a, b + ); + assertSameBehavior( + builtinExports['equals'], + polyfillExports['equals'], + a, b + ); + assertSameBehavior( + builtinExports['compare'], + polyfillExports['compare'], + a, b + ); + } +} diff --git a/js/src/jit-test/tests/wasm/builtin-modules/js-string/directives.txt b/js/src/jit-test/tests/wasm/builtin-modules/js-string/directives.txt new file mode 100644 index 000000000000..8c5e6882eb88 --- /dev/null +++ b/js/src/jit-test/tests/wasm/builtin-modules/js-string/directives.txt @@ -0,0 +1 @@ +|jit-test| --wasm-gc; --wasm-js-string-builtins; test-also=--wasm-compiler=optimizing; include:wasm.js diff --git a/js/src/jit/ABIFunctionType.yaml b/js/src/jit/ABIFunctionType.yaml index 24356baf6286..4d115e05cb2c 100644 --- a/js/src/jit/ABIFunctionType.yaml +++ b/js/src/jit/ABIFunctionType.yaml @@ -149,6 +149,9 @@ - ret: General args: [General, Int32, Int32, General, Int32] +- name: General_GeneralGeneralInt32Int32 + ret: General + args: [General, General, Int32, Int32] # int32_t f(...) variants - ret: Int32 @@ -160,6 +163,12 @@ - ret: Int32 args: [General, General, General] +- ret: Int32 + args: [General, General, General, Int32] + +- ret: Int32 + args: [General, General, Int32] + - ret: Int32 args: [General, General, Int32, General] diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index 411b9f7a5486..ebd09abed0a5 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -5482,6 +5482,10 @@ CodeOffset MacroAssembler::wasmCallBuiltinInstanceMethod( case wasm::FailureMode::FailOnNegI32: branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &noTrap); break; + case wasm::FailureMode::FailOnMaxI32: + branchPtr(Assembler::NotEqual, ReturnReg, ImmWord(uintptr_t(INT32_MAX)), + &noTrap); + break; case wasm::FailureMode::FailOnNullPtr: branchTestPtr(Assembler::NonZero, ReturnReg, ReturnReg, &noTrap); break; diff --git a/js/src/vm/StringType.h b/js/src/vm/StringType.h index e8ed53a50d75..5a6da6828e0f 100644 --- a/js/src/vm/StringType.h +++ b/js/src/vm/StringType.h @@ -491,6 +491,7 @@ class JSString : public js::gc::CellWithLengthAndFlags { bool empty() const { return length() == 0; } inline bool getChar(JSContext* cx, size_t index, char16_t* code); + inline bool getCodePoint(JSContext* cx, size_t index, char32_t* codePoint); /* Strings have either Latin1 or TwoByte chars. */ bool hasLatin1Chars() const { return flags() & LATIN1_CHARS_BIT; } @@ -1837,6 +1838,34 @@ MOZ_ALWAYS_INLINE bool JSString::getChar(JSContext* cx, size_t index, return true; } +MOZ_ALWAYS_INLINE bool JSString::getCodePoint(JSContext* cx, size_t index, + char32_t* code) { + // C++ implementation of https://tc39.es/ecma262/#sec-codepointat + size_t size = length(); + MOZ_ASSERT(index < size); + + char16_t first; + if (!getChar(cx, index, &first)) { + return false; + } + if (!js::unicode::IsLeadSurrogate(first) || index + 1 == size) { + *code = first; + return true; + } + + char16_t second; + if (!getChar(cx, index + 1, &second)) { + return false; + } + if (!js::unicode::IsTrailSurrogate(second)) { + *code = first; + return true; + } + + *code = js::unicode::UTF16Decode(first, second); + return true; +} + MOZ_ALWAYS_INLINE JSLinearString* JSString::ensureLinear(JSContext* cx) { return isLinear() ? &asLinear() : asRope().flatten(cx); } diff --git a/js/src/wasm/WasmAnyRef.h b/js/src/wasm/WasmAnyRef.h index aa8d36d30428..1675a9fa8d23 100644 --- a/js/src/wasm/WasmAnyRef.h +++ b/js/src/wasm/WasmAnyRef.h @@ -357,7 +357,9 @@ class WrappedPtrOperations { bool isNull() const { return value().isNull(); } bool isI31() const { return value().isI31(); } bool isJSObject() const { return value().isJSObject(); } + bool isJSString() const { return value().isJSString(); } JSObject& toJSObject() const { return value().toJSObject(); } + JSString* toJSString() const { return value().toJSString(); } }; // If the Value is a GC pointer type, call |f| with the pointer cast to that diff --git a/js/src/wasm/WasmBuiltinModule.cpp b/js/src/wasm/WasmBuiltinModule.cpp index b66aa39671e1..1c111946cc9b 100644 --- a/js/src/wasm/WasmBuiltinModule.cpp +++ b/js/src/wasm/WasmBuiltinModule.cpp @@ -94,10 +94,10 @@ bool EncodeFuncBody(const BuiltinModuleFunc& builtinModuleFunc, return encoder.writeOp(Op::End); } -bool wasm::CompileBuiltinModule(JSContext* cx, - const mozilla::Span ids, - mozilla::Maybe memory, - MutableHandle result) { +bool CompileBuiltinModule(JSContext* cx, + const mozilla::Span ids, + mozilla::Maybe memory, + MutableHandle result) { // Create the options manually, enabling intrinsics FeatureOptions featureOptions; featureOptions.isBuiltinModule = true; @@ -251,3 +251,97 @@ bool wasm::CompileBuiltinModule(JSContext* cx, result.set(WasmModuleObject::create(cx, *module, proto)); return !!result; } + +static BuiltinModuleFuncId SelfTestFuncs[] = {BuiltinModuleFuncId::I8VecMul}; + +static BuiltinModuleFuncId IntGemmFuncs[] = { +#ifdef ENABLE_WASM_MOZ_INTGEMM + BuiltinModuleFuncId::I8PrepareB, + BuiltinModuleFuncId::I8PrepareBFromTransposed, + BuiltinModuleFuncId::I8PrepareBFromQuantizedTransposed, + BuiltinModuleFuncId::I8PrepareA, + BuiltinModuleFuncId::I8PrepareBias, + BuiltinModuleFuncId::I8MultiplyAndAddBias, + BuiltinModuleFuncId::I8SelectColumnsOfB +#endif // ENABLE_WASM_MOZ_INTGEMM +}; + +static BuiltinModuleFuncId JSStringFuncs[] = { +#ifdef ENABLE_WASM_JS_STRING_BUILTINS + BuiltinModuleFuncId::StringFromWTF16Array, + BuiltinModuleFuncId::StringToWTF16Array, + BuiltinModuleFuncId::StringFromCharCode, + BuiltinModuleFuncId::StringFromCodePoint, + BuiltinModuleFuncId::StringCharCodeAt, + BuiltinModuleFuncId::StringCodePointAt, + BuiltinModuleFuncId::StringLength, + BuiltinModuleFuncId::StringConcatenate, + BuiltinModuleFuncId::StringSubstring, + BuiltinModuleFuncId::StringEquals, + BuiltinModuleFuncId::StringCompare +#endif // ENABLE_WASM_JS_STRING_BUILTINS +}; +static const char* JSStringModuleName = "wasm:js-string"; + +Maybe wasm::ImportMatchesBuiltinModule( + Span importName, BuiltinModuleIds enabledBuiltins) { + if (enabledBuiltins.jsString && + importName == mozilla::MakeStringSpan(JSStringModuleName)) { + return Some(BuiltinModuleId::JSString); + } + // Not supported for implicit instantiation yet + MOZ_RELEASE_ASSERT(!enabledBuiltins.selfTest && !enabledBuiltins.intGemm); + return Nothing(); +} + +Maybe wasm::ImportMatchesBuiltinModuleFunc( + mozilla::Span importName, BuiltinModuleId module) { + // Not supported for implicit instantiation yet + MOZ_RELEASE_ASSERT(module == BuiltinModuleId::JSString); + for (BuiltinModuleFuncId funcId : JSStringFuncs) { + const BuiltinModuleFunc& func = BuiltinModuleFunc::getFromId(funcId); + if (importName == mozilla::MakeStringSpan(func.exportName)) { + return Some(&func); + } + } + return Nothing(); +} + +bool wasm::CompileBuiltinModule(JSContext* cx, BuiltinModuleId module, + MutableHandle result) { + switch (module) { + case BuiltinModuleId::SelfTest: + return CompileBuiltinModule(cx, SelfTestFuncs, Some(Shareable::False), + result); +#ifdef ENABLE_WASM_MOZ_INTGEMM + case BuiltinModuleId::IntGemm: + return CompileBuiltinModule(cx, IntGemmFuncs, Some(Shareable::False), + result); +#endif // ENABLE_WASM_MOZ_INTGEMM +#ifdef ENABLE_WASM_JS_STRING_BUILTINS + case BuiltinModuleId::JSString: + return CompileBuiltinModule(cx, JSStringFuncs, Nothing(), result); +#endif // ENABLE_WASM_JS_STRING_BUILTINS + default: + MOZ_CRASH(); + } +} + +bool wasm::InstantiateBuiltinModule(JSContext* cx, BuiltinModuleId module, + MutableHandleObject result) { + Rooted moduleObj(cx); + if (!CompileBuiltinModule(cx, module, &moduleObj)) { + ReportOutOfMemory(cx); + return false; + } + ImportValues imports; + Rooted instanceObj(cx); + RootedObject instanceProto(cx); + if (!moduleObj->module().instantiate(cx, imports, instanceProto, + &instanceObj)) { + MOZ_RELEASE_ASSERT(cx->isThrowingOutOfMemory()); + return false; + } + result.set(&instanceObj->exportsObj()); + return true; +} diff --git a/js/src/wasm/WasmBuiltinModule.h b/js/src/wasm/WasmBuiltinModule.h index 67861cd13bbb..42faffec7391 100644 --- a/js/src/wasm/WasmBuiltinModule.h +++ b/js/src/wasm/WasmBuiltinModule.h @@ -19,18 +19,46 @@ #ifndef wasm_builtin_module_h #define wasm_builtin_module_h +#include "mozilla/EnumeratedArray.h" #include "mozilla/Maybe.h" #include "mozilla/Span.h" #include "wasm/WasmBuiltins.h" #include "wasm/WasmCompileArgs.h" #include "wasm/WasmConstants.h" +#include "wasm/WasmSerialize.h" #include "wasm/WasmTypeDecls.h" #include "wasm/WasmTypeDef.h" namespace js { namespace wasm { +struct MOZ_STACK_CLASS BuiltinModuleInstances { + explicit BuiltinModuleInstances(JSContext* cx) + : selfTest(cx), intGemm(cx), jsString(cx) {} + + Rooted selfTest; + Rooted intGemm; + Rooted jsString; + + MutableHandle operator[](BuiltinModuleId module) { + switch (module) { + case BuiltinModuleId::SelfTest: { + return &selfTest; + } + case BuiltinModuleId::IntGemm: { + return &intGemm; + } + case BuiltinModuleId::JSString: { + return &jsString; + } + default: { + MOZ_CRASH(); + } + } + } +}; + // An builtin module func is a natively implemented function that may be // compiled into a 'builtin module', which may be instantiated with a provided // memory yielding an exported WebAssembly function wrapping the builtin module. @@ -55,12 +83,21 @@ struct BuiltinModuleFunc { static const BuiltinModuleFunc& getFromId(BuiltinModuleFuncId id); }; -// Compile and return the builtin module for a given set of operations. -bool CompileBuiltinModule(JSContext* cx, - const mozilla::Span ids, - mozilla::Maybe memory, +Maybe ImportMatchesBuiltinModule( + mozilla::Span importName, BuiltinModuleIds enabledBuiltins); +Maybe ImportMatchesBuiltinModuleFunc( + mozilla::Span importName, BuiltinModuleId module); + +// Compile and return the builtin module for a particular +// builtin module. +bool CompileBuiltinModule(JSContext* cx, BuiltinModuleId module, MutableHandle result); +// Compile, instantiate and return the builtin module instance for a particular +// builtin module. +bool InstantiateBuiltinModule(JSContext* cx, BuiltinModuleId module, + MutableHandle result); + } // namespace wasm } // namespace js diff --git a/js/src/wasm/WasmBuiltinModule.yaml b/js/src/wasm/WasmBuiltinModule.yaml index ee1c59b73439..88c2f5a57592 100644 --- a/js/src/wasm/WasmBuiltinModule.yaml +++ b/js/src/wasm/WasmBuiltinModule.yaml @@ -216,3 +216,150 @@ uses_memory: true #endif // ENABLE_WASM_MOZ_INTGEMM + +#if defined(ENABLE_WASM_JS_STRING_BUILTINS) + +- op: StringFromWTF16Array + symbolic_address: + name: StringFromWTF16Array + type: Args_General_GeneralGeneralInt32Int32 + entry: Instance::stringFromWTF16Array + export: fromWTF16Array + params: + - 'ValType(RefType::any())' + - 'ValType::i32()' + - 'ValType::i32()' + result: 'ValType(RefType::extern_())' + fail_mode: FailOnNullPtr + uses_memory: false + +- op: StringToWTF16Array + symbolic_address: + name: StringToWTF16Array + type: Args_Int32_GeneralGeneralGeneralInt32 + entry: Instance::stringToWTF16Array + export: toWTF16Array + params: + - 'ValType(RefType::extern_())' + - 'ValType(RefType::any())' + - 'ValType::i32()' + result: 'ValType::i32()' + fail_mode: FailOnNegI32 + uses_memory: false + +- op: StringFromCharCode + symbolic_address: + name: StringFromCharCode + type: Args_General_GeneralInt32 + entry: Instance::stringFromCharCode + export: fromCharCode + params: + - 'ValType::i32()' + result: 'ValType(RefType::extern_())' + fail_mode: FailOnNullPtr + uses_memory: false + +- op: StringFromCodePoint + symbolic_address: + name: StringFromCodePoint + type: Args_General_GeneralInt32 + entry: Instance::stringFromCodePoint + export: fromCodePoint + params: + - 'ValType::i32()' + result: 'ValType(RefType::extern_())' + fail_mode: FailOnNullPtr + uses_memory: false + +- op: StringCharCodeAt + symbolic_address: + name: StringCharCodeAt + type: Args_Int32_GeneralGeneralInt32 + entry: Instance::stringCharCodeAt + export: charCodeAt + params: + - 'ValType(RefType::extern_())' + - 'ValType::i32()' + result: 'ValType::i32()' + fail_mode: FailOnNegI32 + uses_memory: false + +- op: StringCodePointAt + symbolic_address: + name: StringCodePointAt + type: Args_Int32_GeneralGeneralInt32 + entry: Instance::stringCodePointAt + export: codePointAt + params: + - 'ValType(RefType::extern_())' + - 'ValType::i32()' + result: 'ValType::i32()' + fail_mode: FailOnNegI32 + uses_memory: false + +- op: StringLength + symbolic_address: + name: StringLength + type: Args_Int32_GeneralGeneral + entry: Instance::stringLength + export: length + params: + - 'ValType(RefType::extern_())' + result: 'ValType::i32()' + fail_mode: FailOnNegI32 + uses_memory: false + +- op: StringConcatenate + symbolic_address: + name: StringConcatenate + type: Args_General3 + entry: Instance::stringConcatenate + export: concatenate + params: + - 'ValType(RefType::extern_())' + - 'ValType(RefType::extern_())' + result: 'ValType(RefType::extern_())' + fail_mode: FailOnNullPtr + uses_memory: false + +- op: StringSubstring + symbolic_address: + name: StringSubstring + type: Args_General_GeneralGeneralInt32Int32 + entry: Instance::stringSubstring + export: substring + params: + - 'ValType(RefType::extern_())' + - 'ValType::i32()' + - 'ValType::i32()' + result: 'ValType(RefType::extern_())' + fail_mode: FailOnNullPtr + uses_memory: false + +- op: StringEquals + symbolic_address: + name: StringEquals + type: Args_Int32_GeneralGeneralGeneral + entry: Instance::stringEquals + export: equals + params: + - 'ValType(RefType::extern_())' + - 'ValType(RefType::extern_())' + result: 'ValType::i32()' + fail_mode: FailOnNegI32 + uses_memory: false + +- op: StringCompare + symbolic_address: + name: StringCompare + type: Args_Int32_GeneralGeneralGeneral + entry: Instance::stringCompare + export: compare + params: + - 'ValType(RefType::extern_())' + - 'ValType(RefType::extern_())' + result: 'ValType::i32()' + fail_mode: FailOnMaxI32 + uses_memory: false + +#endif // ENABLE_WASM_JS_STRING_BUILTINS diff --git a/js/src/wasm/WasmBuiltins.cpp b/js/src/wasm/WasmBuiltins.cpp index 13bba936b211..be3e2f3f8317 100644 --- a/js/src/wasm/WasmBuiltins.cpp +++ b/js/src/wasm/WasmBuiltins.cpp @@ -74,6 +74,7 @@ static const unsigned BUILTIN_THUNK_LIFO_SIZE = 64 * 1024; #define _END MIRType::None #define _Infallible FailureMode::Infallible #define _FailOnNegI32 FailureMode::FailOnNegI32 +#define _FailOnMaxI32 FailureMode::FailOnMaxI32 #define _FailOnNullPtr FailureMode::FailOnNullPtr #define _FailOnInvalidRef FailureMode::FailOnInvalidRef diff --git a/js/src/wasm/WasmBuiltins.h b/js/src/wasm/WasmBuiltins.h index 222a4ae57485..5d6884df0863 100644 --- a/js/src/wasm/WasmBuiltins.h +++ b/js/src/wasm/WasmBuiltins.h @@ -163,6 +163,7 @@ enum class SymbolicAddress { enum class FailureMode : uint8_t { Infallible, FailOnNegI32, + FailOnMaxI32, FailOnNullPtr, FailOnInvalidRef }; diff --git a/js/src/wasm/WasmCompile.cpp b/js/src/wasm/WasmCompile.cpp index b3a4b667a822..67eec8bf3385 100644 --- a/js/src/wasm/WasmCompile.cpp +++ b/js/src/wasm/WasmCompile.cpp @@ -24,6 +24,7 @@ #include "js/Equality.h" #include "js/ForOfIterator.h" +#include "js/PropertyAndElement.h" #ifndef __wasi__ # include "jit/ProcessExecutableMemory.h" @@ -33,6 +34,7 @@ #include "jit/JitOptions.h" #include "util/Text.h" #include "vm/HelperThreads.h" +#include "vm/JSAtomState.h" #include "vm/Realm.h" #include "wasm/WasmBaselineCompile.h" #include "wasm/WasmFeatures.h" diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp index 134caf57e604..4e85a96398d9 100644 --- a/js/src/wasm/WasmInstance.cpp +++ b/js/src/wasm/WasmInstance.cpp @@ -26,6 +26,7 @@ #include "jsmath.h" +#include "builtin/String.h" #include "gc/Barrier.h" #include "gc/Marking.h" #include "jit/AtomicOperations.h" @@ -38,6 +39,7 @@ #include "js/Stack.h" // JS::NativeStackLimitMin #include "util/StringBuffer.h" #include "util/Text.h" +#include "util/Unicode.h" #include "vm/ArrayBufferObject.h" #include "vm/BigIntType.h" #include "vm/Compartment.h" @@ -1941,6 +1943,274 @@ static bool ArrayCopyFromElem(JSContext* cx, Handle arrayObj, return 0; } +// TODO: this cast is irregular and not representable in wasm, as it does not +// take into account the enclosing recursion group of the type. This is +// temporary until builtin module functions can specify a precise array type +// for params/results. +static WasmArrayObject* CastToI16Array(HandleAnyRef ref, bool needMutable) { + if (!ref.isJSObject()) { + return nullptr; + } + JSObject& object = ref.toJSObject(); + if (!object.is()) { + return nullptr; + } + WasmArrayObject& array = object.as(); + const ArrayType& type = array.typeDef().arrayType(); + if (type.elementType_ != FieldType::I16) { + return nullptr; + } + if (needMutable && !type.isMutable_) { + return nullptr; + } + return &array; +} + +/* static */ +void* Instance::stringFromWTF16Array(Instance* instance, void* arrayArg, + uint32_t arrayStart, uint32_t arrayCount) { + JSContext* cx = instance->cx(); + RootedAnyRef arrayRef(cx, AnyRef::fromCompiledCode(arrayArg)); + Rooted array(cx); + if (!(array = CastToI16Array(arrayRef, false))) { + ReportTrapError(cx, JSMSG_WASM_BAD_CAST); + return nullptr; + } + + CheckedUint32 lastIndexPlus1 = + CheckedUint32(arrayStart) + CheckedUint32(arrayCount); + if (!lastIndexPlus1.isValid() || + lastIndexPlus1.value() > array->numElements_) { + ReportTrapError(cx, JSMSG_WASM_OUT_OF_BOUNDS); + return nullptr; + } + + JSLinearString* string = NewStringCopyN( + cx, (char16_t*)array->data_ + arrayStart, arrayCount); + if (!string) { + return nullptr; + } + return AnyRef::fromJSString(string).forCompiledCode(); +} + +/* static */ +int32_t Instance::stringToWTF16Array(Instance* instance, void* stringArg, + void* arrayArg, uint32_t arrayStart) { + JSContext* cx = instance->cx(); + AnyRef stringRef = AnyRef::fromCompiledCode(stringArg); + if (!stringRef.isJSString()) { + ReportTrapError(cx, JSMSG_WASM_BAD_CAST); + return -1; + } + Rooted string(cx, stringRef.toJSString()); + size_t stringLength = string->length(); + + RootedAnyRef arrayRef(cx, AnyRef::fromCompiledCode(arrayArg)); + Rooted array(cx); + if (!(array = CastToI16Array(arrayRef, true))) { + ReportTrapError(cx, JSMSG_WASM_BAD_CAST); + return -1; + } + + CheckedUint32 lastIndexPlus1 = CheckedUint32(arrayStart) + stringLength; + if (!lastIndexPlus1.isValid() || + lastIndexPlus1.value() > array->numElements_) { + ReportTrapError(cx, JSMSG_WASM_OUT_OF_BOUNDS); + return -1; + } + + JSLinearString* linearStr = string->ensureLinear(cx); + if (!linearStr) { + return -1; + } + char16_t* arrayData = reinterpret_cast(array->data_); + CopyChars(arrayData + arrayStart, *linearStr); + return stringLength; +} + +void* Instance::stringFromCharCode(Instance* instance, uint32_t charCode) { + JSContext* cx = instance->cx(); + + RootedValue rval(cx, NumberValue(charCode)); + if (!str_fromCharCode_one_arg(cx, rval, &rval)) { + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + return nullptr; + } + + return AnyRef::fromJSString(rval.toString()).forCompiledCode(); +} + +void* Instance::stringFromCodePoint(Instance* instance, uint32_t codePoint) { + JSContext* cx = instance->cx(); + + // Check for any error conditions before calling fromCodePoint so we report + // the correct error + if (codePoint > int32_t(unicode::NonBMPMax)) { + ReportTrapError(cx, JSMSG_WASM_BAD_CODEPOINT); + return nullptr; + } + + RootedValue rval(cx, Int32Value(codePoint)); + if (!str_fromCodePoint_one_arg(cx, rval, &rval)) { + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + return nullptr; + } + + return AnyRef::fromJSString(rval.toString()).forCompiledCode(); +} + +int32_t Instance::stringCharCodeAt(Instance* instance, void* stringArg, + uint32_t index) { + JSContext* cx = instance->cx(); + AnyRef stringRef = AnyRef::fromCompiledCode(stringArg); + if (!stringRef.isJSString()) { + ReportTrapError(cx, JSMSG_WASM_BAD_CAST); + return -1; + } + + Rooted string(cx, stringRef.toJSString()); + if (index >= string->length()) { + ReportTrapError(cx, JSMSG_WASM_OUT_OF_BOUNDS); + return -1; + } + + char16_t c; + if (!string->getChar(cx, index, &c)) { + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + return false; + } + return c; +} + +int32_t Instance::stringCodePointAt(Instance* instance, void* stringArg, + uint32_t index) { + JSContext* cx = instance->cx(); + AnyRef stringRef = AnyRef::fromCompiledCode(stringArg); + if (!stringRef.isJSString()) { + ReportTrapError(cx, JSMSG_WASM_BAD_CAST); + return -1; + } + + Rooted string(cx, stringRef.toJSString()); + if (index >= string->length()) { + ReportTrapError(cx, JSMSG_WASM_OUT_OF_BOUNDS); + return -1; + } + + char32_t c; + if (!string->getCodePoint(cx, index, &c)) { + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + return false; + } + return c; +} + +int32_t Instance::stringLength(Instance* instance, void* stringArg) { + JSContext* cx = instance->cx(); + AnyRef stringRef = AnyRef::fromCompiledCode(stringArg); + if (!stringRef.isJSString()) { + ReportTrapError(cx, JSMSG_WASM_BAD_CAST); + return -1; + } + + static_assert(JS::MaxStringLength <= INT32_MAX); + return (int32_t)stringRef.toJSString()->length(); +} + +void* Instance::stringConcatenate(Instance* instance, void* firstStringArg, + void* secondStringArg) { + JSContext* cx = instance->cx(); + + AnyRef firstStringRef = AnyRef::fromCompiledCode(firstStringArg); + AnyRef secondStringRef = AnyRef::fromCompiledCode(secondStringArg); + if (!firstStringRef.isJSString() || !secondStringRef.isJSString()) { + ReportTrapError(cx, JSMSG_WASM_BAD_CAST); + return nullptr; + } + + Rooted firstString(cx, firstStringRef.toJSString()); + Rooted secondString(cx, secondStringRef.toJSString()); + JSString* result = ConcatStrings(cx, firstString, secondString); + if (!result) { + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + return nullptr; + } + return AnyRef::fromJSString(result).forCompiledCode(); +} + +void* Instance::stringSubstring(Instance* instance, void* stringArg, + int32_t startIndex, int32_t endIndex) { + JSContext* cx = instance->cx(); + + AnyRef stringRef = AnyRef::fromCompiledCode(stringArg); + if (!stringRef.isJSString()) { + ReportTrapError(cx, JSMSG_WASM_BAD_CAST); + return nullptr; + } + + RootedString string(cx, stringRef.toJSString()); + static_assert(JS::MaxStringLength <= INT32_MAX); + if ((uint32_t)startIndex > string->length() || + (uint32_t)endIndex > string->length() || startIndex > endIndex) { + return AnyRef::fromJSString(cx->names().empty_).forCompiledCode(); + } + + JSString* result = + SubstringKernel(cx, string, startIndex, endIndex - startIndex); + if (!result) { + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + return nullptr; + } + return AnyRef::fromJSString(result).forCompiledCode(); +} + +int32_t Instance::stringEquals(Instance* instance, void* firstStringArg, + void* secondStringArg) { + JSContext* cx = instance->cx(); + + AnyRef firstStringRef = AnyRef::fromCompiledCode(firstStringArg); + AnyRef secondStringRef = AnyRef::fromCompiledCode(secondStringArg); + if (!firstStringRef.isJSString() || !secondStringRef.isJSString()) { + ReportTrapError(cx, JSMSG_WASM_BAD_CAST); + return -1; + } + + bool equals; + if (!EqualStrings(cx, firstStringRef.toJSString(), + secondStringRef.toJSString(), &equals)) { + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + return -1; + } + return equals ? 1 : 0; +} + +int32_t Instance::stringCompare(Instance* instance, void* firstStringArg, + void* secondStringArg) { + JSContext* cx = instance->cx(); + + AnyRef firstStringRef = AnyRef::fromCompiledCode(firstStringArg); + AnyRef secondStringRef = AnyRef::fromCompiledCode(secondStringArg); + if (!firstStringRef.isJSString() || !secondStringRef.isJSString()) { + ReportTrapError(cx, JSMSG_WASM_BAD_CAST); + return INT32_MAX; + } + + int32_t result; + if (!CompareStrings(cx, firstStringRef.toJSString(), + secondStringRef.toJSString(), &result)) { + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + return INT32_MAX; + } + + if (result < 0) { + return -1; + } + if (result > 0) { + return 1; + } + return result; +} + ////////////////////////////////////////////////////////////////////////////// // // Instance creation and related. diff --git a/js/src/wasm/WasmInstance.h b/js/src/wasm/WasmInstance.h index dcce20455740..f88e45b26e69 100644 --- a/js/src/wasm/WasmInstance.h +++ b/js/src/wasm/WasmInstance.h @@ -569,6 +569,26 @@ class alignas(16) Instance { const wasm::TypeDef* typeDef); static int32_t intrI8VecMul(Instance* instance, uint32_t dest, uint32_t src1, uint32_t src2, uint32_t len, uint8_t* memBase); + + static void* stringFromWTF16Array(Instance* instance, void* arrayArg, + uint32_t start, uint32_t len); + static int32_t stringToWTF16Array(Instance* instance, void* stringArg, + void* arrayArg, uint32_t start); + static void* stringFromCharCode(Instance* instance, uint32_t charCode); + static void* stringFromCodePoint(Instance* instance, uint32_t codePoint); + static int32_t stringCharCodeAt(Instance* instance, void* stringArg, + uint32_t index); + static int32_t stringCodePointAt(Instance* instance, void* stringArg, + uint32_t index); + static int32_t stringLength(Instance* instance, void* stringArg); + static void* stringConcatenate(Instance* instance, void* firstStringArg, + void* secondStringArg); + static void* stringSubstring(Instance* instance, void* stringArg, + int32_t startIndex, int32_t endIndex); + static int32_t stringEquals(Instance* instance, void* firstStringArg, + void* secondStringArg); + static int32_t stringCompare(Instance* instance, void* firstStringArg, + void* secondStringArg); }; bool ResultsToJSValue(JSContext* cx, ResultType type, void* registerResultLoc, diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index a340489121d5..e0893af99189 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -135,6 +135,11 @@ bool js::wasm::GetImports(JSContext* cx, const Module& module, const Metadata& metadata = module.metadata(); + BuiltinModuleInstances builtinInstances(cx); + RootedValue importModuleValue(cx); + RootedObject importModuleObject(cx); + RootedValue importFieldValue(cx); + uint32_t tagIndex = 0; const TagDescVector& tags = metadata.tags; uint32_t globalIndex = 0; @@ -142,39 +147,55 @@ bool js::wasm::GetImports(JSContext* cx, const Module& module, uint32_t tableIndex = 0; const TableDescVector& tables = metadata.tables; for (const Import& import : module.imports()) { - RootedId moduleName(cx); - if (!import.module.toPropertyKey(cx, &moduleName)) { - return false; + Maybe builtinModule = ImportMatchesBuiltinModule( + import.module.utf8Bytes(), metadata.builtinModules); + if (builtinModule) { + MutableHandle builtinInstance = + builtinInstances[*builtinModule]; + if (!builtinInstance && !wasm::InstantiateBuiltinModule( + cx, *builtinModule, builtinInstance)) { + return false; + } + importModuleObject = builtinInstance; + } else { + RootedId moduleName(cx); + if (!import.module.toPropertyKey(cx, &moduleName)) { + return false; + } + + if (!GetProperty(cx, importObj, importObj, moduleName, + &importModuleValue)) { + return false; + } + + if (!importModuleValue.isObject()) { + UniqueChars moduleQuoted = import.module.toQuotedString(cx); + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_IMPORT_FIELD, + moduleQuoted.get()); + return false; + } + + importModuleObject = &importModuleValue.toObject(); } + RootedId fieldName(cx); if (!import.field.toPropertyKey(cx, &fieldName)) { return false; } - RootedValue v(cx); - if (!GetProperty(cx, importObj, importObj, moduleName, &v)) { - return false; - } - - if (!v.isObject()) { - UniqueChars moduleQuoted = import.module.toQuotedString(cx); - JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, - JSMSG_WASM_BAD_IMPORT_FIELD, moduleQuoted.get()); - return false; - } - - RootedObject obj(cx, &v.toObject()); - if (!GetProperty(cx, obj, obj, fieldName, &v)) { + if (!GetProperty(cx, importModuleObject, importModuleObject, fieldName, + &importFieldValue)) { return false; } switch (import.kind) { case DefinitionKind::Function: { - if (!IsCallableNonCCW(v)) { + if (!IsCallableNonCCW(importFieldValue)) { return ThrowBadImportType(cx, import.field, "Function"); } - if (!imports->funcs.append(&v.toObject())) { + if (!imports->funcs.append(&importFieldValue.toObject())) { return false; } @@ -182,11 +203,13 @@ bool js::wasm::GetImports(JSContext* cx, const Module& module, } case DefinitionKind::Table: { const uint32_t index = tableIndex++; - if (!v.isObject() || !v.toObject().is()) { + if (!importFieldValue.isObject() || + !importFieldValue.toObject().is()) { return ThrowBadImportType(cx, import.field, "Table"); } - Rooted obj(cx, &v.toObject().as()); + Rooted obj( + cx, &importFieldValue.toObject().as()); if (obj->table().elemType() != tables[index].elemType) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TBL_TYPE_LINK); @@ -199,22 +222,26 @@ bool js::wasm::GetImports(JSContext* cx, const Module& module, break; } case DefinitionKind::Memory: { - if (!v.isObject() || !v.toObject().is()) { + if (!importFieldValue.isObject() || + !importFieldValue.toObject().is()) { return ThrowBadImportType(cx, import.field, "Memory"); } - if (!imports->memories.append(&v.toObject().as())) { + if (!imports->memories.append( + &importFieldValue.toObject().as())) { return false; } break; } case DefinitionKind::Tag: { const uint32_t index = tagIndex++; - if (!v.isObject() || !v.toObject().is()) { + if (!importFieldValue.isObject() || + !importFieldValue.toObject().is()) { return ThrowBadImportType(cx, import.field, "Tag"); } - Rooted obj(cx, &v.toObject().as()); + Rooted obj( + cx, &importFieldValue.toObject().as()); // Checks whether the signature of the imported exception object matches // the signature declared in the exception import's TagDesc. @@ -239,9 +266,10 @@ bool js::wasm::GetImports(JSContext* cx, const Module& module, MOZ_ASSERT(global.importIndex() == index); RootedVal val(cx); - if (v.isObject() && v.toObject().is()) { - Rooted obj(cx, - &v.toObject().as()); + if (importFieldValue.isObject() && + importFieldValue.toObject().is()) { + Rooted obj( + cx, &importFieldValue.toObject().as()); if (obj->isMutable() != global.isMutable()) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, @@ -267,14 +295,15 @@ bool js::wasm::GetImports(JSContext* cx, const Module& module, val = obj->val(); } else { if (!global.type().isRefType()) { - if (global.type() == ValType::I64 && !v.isBigInt()) { + if (global.type() == ValType::I64 && !importFieldValue.isBigInt()) { return ThrowBadImportType(cx, import.field, "BigInt"); } - if (global.type() != ValType::I64 && !v.isNumber()) { + if (global.type() != ValType::I64 && !importFieldValue.isNumber()) { return ThrowBadImportType(cx, import.field, "Number"); } } else { - if (!global.type().isExternRef() && !v.isObjectOrNull()) { + if (!global.type().isExternRef() && + !importFieldValue.isObjectOrNull()) { return ThrowBadImportType(cx, import.field, "Object-or-null value required for " "non-externref reference type"); @@ -287,7 +316,7 @@ bool js::wasm::GetImports(JSContext* cx, const Module& module, return false; } - if (!Val::fromJSValue(cx, global.type(), v, &val)) { + if (!Val::fromJSValue(cx, global.type(), importFieldValue, &val)) { return false; } } @@ -5192,15 +5221,8 @@ static bool WebAssembly_mozIntGemm(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); Rooted module(cx); - wasm::BuiltinModuleFuncId ids[] = { - wasm::BuiltinModuleFuncId::I8PrepareB, - wasm::BuiltinModuleFuncId::I8PrepareBFromTransposed, - wasm::BuiltinModuleFuncId::I8PrepareBFromQuantizedTransposed, - wasm::BuiltinModuleFuncId::I8PrepareA, - wasm::BuiltinModuleFuncId::I8PrepareBias, - wasm::BuiltinModuleFuncId::I8MultiplyAndAddBias, - wasm::BuiltinModuleFuncId::I8SelectColumnsOfB}; - if (!wasm::CompileBuiltinModule(cx, ids, Some(Shareable::False), &module)) { + if (!wasm::CompileBuiltinModule(cx, wasm::BuiltinModuleId::IntGemm, + &module)) { ReportOutOfMemory(cx); return false; } diff --git a/js/src/wasm/WasmSerialize.cpp b/js/src/wasm/WasmSerialize.cpp index ecc496f9b12d..aaa70e7805fc 100644 --- a/js/src/wasm/WasmSerialize.cpp +++ b/js/src/wasm/WasmSerialize.cpp @@ -956,7 +956,7 @@ CoderResult CodeSymbolicLinkArray( template CoderResult CodeLinkData(Coder& coder, CoderArg item) { - WASM_VERIFY_SERIALIZATION_FOR_SIZE(wasm::LinkData, 7968); + WASM_VERIFY_SERIALIZATION_FOR_SIZE(wasm::LinkData, 8760); if constexpr (mode == MODE_ENCODE) { MOZ_ASSERT(item->tier == Tier::Serialized); } diff --git a/js/src/wasm/WasmValidate.cpp b/js/src/wasm/WasmValidate.cpp index 9f97f2fb4908..5a50b091a412 100644 --- a/js/src/wasm/WasmValidate.cpp +++ b/js/src/wasm/WasmValidate.cpp @@ -2140,8 +2140,8 @@ static bool DecodeImport(Decoder& d, ModuleEnvironment* env) { return d.fail("expected valid import module name"); } - CacheableName funcName; - if (!DecodeName(d, &funcName)) { + CacheableName fieldName; + if (!DecodeName(d, &fieldName)) { return d.fail("expected valid import field name"); } @@ -2221,10 +2221,80 @@ static bool DecodeImport(Decoder& d, ModuleEnvironment* env) { return d.fail("unsupported import kind"); } - return env->imports.emplaceBack(std::move(moduleName), std::move(funcName), + return env->imports.emplaceBack(std::move(moduleName), std::move(fieldName), importKind); } +static bool CheckImportsAgainstBuiltinModules(Decoder& d, + ModuleEnvironment* env) { + const BuiltinModuleIds& builtinModules = env->features.builtinModules; + + // Skip this pass if there are no builtin modules enabled + if (builtinModules.hasNone()) { + return true; + } + + // Allocate a type context for builtin types so we can canonicalize them + // and use them in type comparisons + RefPtr builtinTypes = js_new(); + if (!builtinTypes) { + return false; + } + + uint32_t importFuncIndex = 0; + for (auto& import : env->imports) { + Maybe builtinModule = + ImportMatchesBuiltinModule(import.module.utf8Bytes(), builtinModules); + + switch (import.kind) { + case DefinitionKind::Function: { + const FuncDesc& func = env->funcs[importFuncIndex]; + importFuncIndex += 1; + + // Skip this import if it doesn't refer to a builtin module. We do have + // to increment the import function index regardless though. + if (!builtinModule) { + continue; + } + + // Check if this import refers to a builtin module function + Maybe builtinFunc = + ImportMatchesBuiltinModuleFunc(import.field.utf8Bytes(), + *builtinModule); + if (!builtinFunc) { + return d.fail("unrecognized builtin module field"); + } + + // Get a canonicalized type definition for this builtin so we can + // accurately compare it against the import type. + FuncType builtinFuncType; + if (!(*builtinFunc)->funcType(&builtinFuncType)) { + return false; + } + if (!builtinTypes->addType(builtinFuncType)) { + return false; + } + const TypeDef& builtinTypeDef = + builtinTypes->type(builtinTypes->length() - 1); + + const TypeDef& importTypeDef = (*env->types)[func.typeIndex]; + if (!TypeDef::isSubTypeOf(&builtinTypeDef, &importTypeDef)) { + return d.failf("type mismatch in %s", (*builtinFunc)->exportName); + } + break; + } + default: { + if (!builtinModule) { + continue; + } + return d.fail("unrecognized builtin import"); + } + } + } + + return true; +} + static bool DecodeImportSection(Decoder& d, ModuleEnvironment* env) { MaybeSectionRange range; if (!d.startSection(SectionId::Import, env, &range, "import")) { @@ -2856,6 +2926,12 @@ bool wasm::DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env) { return false; } + // Eagerly check imports for future link errors against any known builtin + // module. + if (!CheckImportsAgainstBuiltinModules(d, env)) { + return false; + } + if (!DecodeFunctionSection(d, env)) { return false; }