mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 14:52:16 +00:00
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
This commit is contained in:
parent
18e79d7998
commit
edf14d2ca2
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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<WasmModuleObject*> module(cx);
|
||||
if (!wasm::CompileBuiltinModule(cx, ids, Some(wasm::Shareable::False),
|
||||
if (!wasm::CompileBuiltinModule(cx, wasm::BuiltinModuleId::SelfTest,
|
||||
&module)) {
|
||||
return false;
|
||||
}
|
||||
|
316
js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js
Normal file
316
js/src/jit-test/tests/wasm/builtin-modules/js-string/basic.js
Normal file
@ -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
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
|jit-test| --wasm-gc; --wasm-js-string-builtins; test-also=--wasm-compiler=optimizing; include:wasm.js
|
@ -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]
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -357,7 +357,9 @@ class WrappedPtrOperations<wasm::AnyRef, Wrapper> {
|
||||
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
|
||||
|
@ -94,10 +94,10 @@ bool EncodeFuncBody(const BuiltinModuleFunc& builtinModuleFunc,
|
||||
return encoder.writeOp(Op::End);
|
||||
}
|
||||
|
||||
bool wasm::CompileBuiltinModule(JSContext* cx,
|
||||
const mozilla::Span<BuiltinModuleFuncId> ids,
|
||||
mozilla::Maybe<Shareable> memory,
|
||||
MutableHandle<WasmModuleObject*> result) {
|
||||
bool CompileBuiltinModule(JSContext* cx,
|
||||
const mozilla::Span<BuiltinModuleFuncId> ids,
|
||||
mozilla::Maybe<Shareable> memory,
|
||||
MutableHandle<WasmModuleObject*> 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<BuiltinModuleId> wasm::ImportMatchesBuiltinModule(
|
||||
Span<const char> 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<const BuiltinModuleFunc*> wasm::ImportMatchesBuiltinModuleFunc(
|
||||
mozilla::Span<const char> 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<WasmModuleObject*> 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<WasmModuleObject*> moduleObj(cx);
|
||||
if (!CompileBuiltinModule(cx, module, &moduleObj)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
ImportValues imports;
|
||||
Rooted<WasmInstanceObject*> 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;
|
||||
}
|
||||
|
@ -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<JSObject*> selfTest;
|
||||
Rooted<JSObject*> intGemm;
|
||||
Rooted<JSObject*> jsString;
|
||||
|
||||
MutableHandle<JSObject*> 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<BuiltinModuleFuncId> ids,
|
||||
mozilla::Maybe<Shareable> memory,
|
||||
Maybe<BuiltinModuleId> ImportMatchesBuiltinModule(
|
||||
mozilla::Span<const char> importName, BuiltinModuleIds enabledBuiltins);
|
||||
Maybe<const BuiltinModuleFunc*> ImportMatchesBuiltinModuleFunc(
|
||||
mozilla::Span<const char> importName, BuiltinModuleId module);
|
||||
|
||||
// Compile and return the builtin module for a particular
|
||||
// builtin module.
|
||||
bool CompileBuiltinModule(JSContext* cx, BuiltinModuleId module,
|
||||
MutableHandle<WasmModuleObject*> result);
|
||||
|
||||
// Compile, instantiate and return the builtin module instance for a particular
|
||||
// builtin module.
|
||||
bool InstantiateBuiltinModule(JSContext* cx, BuiltinModuleId module,
|
||||
MutableHandle<JSObject*> result);
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace js
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -163,6 +163,7 @@ enum class SymbolicAddress {
|
||||
enum class FailureMode : uint8_t {
|
||||
Infallible,
|
||||
FailOnNegI32,
|
||||
FailOnMaxI32,
|
||||
FailOnNullPtr,
|
||||
FailOnInvalidRef
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -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<WasmArrayObject*> 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<WasmArrayObject>()) {
|
||||
return nullptr;
|
||||
}
|
||||
WasmArrayObject& array = object.as<WasmArrayObject>();
|
||||
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<WasmArrayObject*> 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<CanGC, char16_t>(
|
||||
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<JSString*> string(cx, stringRef.toJSString());
|
||||
size_t stringLength = string->length();
|
||||
|
||||
RootedAnyRef arrayRef(cx, AnyRef::fromCompiledCode(arrayArg));
|
||||
Rooted<WasmArrayObject*> 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<char16_t*>(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<JSString*> 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<JSString*> 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<JSString*> firstString(cx, firstStringRef.toJSString());
|
||||
Rooted<JSString*> secondString(cx, secondStringRef.toJSString());
|
||||
JSString* result = ConcatStrings<CanGC>(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.
|
||||
|
@ -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,
|
||||
|
@ -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<BuiltinModuleId> builtinModule = ImportMatchesBuiltinModule(
|
||||
import.module.utf8Bytes(), metadata.builtinModules);
|
||||
if (builtinModule) {
|
||||
MutableHandle<JSObject*> 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<WasmTableObject>()) {
|
||||
if (!importFieldValue.isObject() ||
|
||||
!importFieldValue.toObject().is<WasmTableObject>()) {
|
||||
return ThrowBadImportType(cx, import.field, "Table");
|
||||
}
|
||||
|
||||
Rooted<WasmTableObject*> obj(cx, &v.toObject().as<WasmTableObject>());
|
||||
Rooted<WasmTableObject*> obj(
|
||||
cx, &importFieldValue.toObject().as<WasmTableObject>());
|
||||
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<WasmMemoryObject>()) {
|
||||
if (!importFieldValue.isObject() ||
|
||||
!importFieldValue.toObject().is<WasmMemoryObject>()) {
|
||||
return ThrowBadImportType(cx, import.field, "Memory");
|
||||
}
|
||||
|
||||
if (!imports->memories.append(&v.toObject().as<WasmMemoryObject>())) {
|
||||
if (!imports->memories.append(
|
||||
&importFieldValue.toObject().as<WasmMemoryObject>())) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DefinitionKind::Tag: {
|
||||
const uint32_t index = tagIndex++;
|
||||
if (!v.isObject() || !v.toObject().is<WasmTagObject>()) {
|
||||
if (!importFieldValue.isObject() ||
|
||||
!importFieldValue.toObject().is<WasmTagObject>()) {
|
||||
return ThrowBadImportType(cx, import.field, "Tag");
|
||||
}
|
||||
|
||||
Rooted<WasmTagObject*> obj(cx, &v.toObject().as<WasmTagObject>());
|
||||
Rooted<WasmTagObject*> obj(
|
||||
cx, &importFieldValue.toObject().as<WasmTagObject>());
|
||||
|
||||
// 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<WasmGlobalObject>()) {
|
||||
Rooted<WasmGlobalObject*> obj(cx,
|
||||
&v.toObject().as<WasmGlobalObject>());
|
||||
if (importFieldValue.isObject() &&
|
||||
importFieldValue.toObject().is<WasmGlobalObject>()) {
|
||||
Rooted<WasmGlobalObject*> obj(
|
||||
cx, &importFieldValue.toObject().as<WasmGlobalObject>());
|
||||
|
||||
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<WasmModuleObject*> 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;
|
||||
}
|
||||
|
@ -956,7 +956,7 @@ CoderResult CodeSymbolicLinkArray(
|
||||
template <CoderMode mode>
|
||||
CoderResult CodeLinkData(Coder<mode>& coder,
|
||||
CoderArg<mode, wasm::LinkData> 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);
|
||||
}
|
||||
|
@ -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<TypeContext> builtinTypes = js_new<TypeContext>();
|
||||
if (!builtinTypes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t importFuncIndex = 0;
|
||||
for (auto& import : env->imports) {
|
||||
Maybe<BuiltinModuleId> 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<const BuiltinModuleFunc*> 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user