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:
Ryan Hunt 2023-11-30 00:46:32 +00:00
parent 18e79d7998
commit edf14d2ca2
20 changed files with 1091 additions and 59 deletions

View File

@ -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

View File

@ -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")

View File

@ -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;
}

View 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
);
}
}

View File

@ -0,0 +1 @@
|jit-test| --wasm-gc; --wasm-js-string-builtins; test-also=--wasm-compiler=optimizing; include:wasm.js

View File

@ -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]

View File

@ -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;

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -163,6 +163,7 @@ enum class SymbolicAddress {
enum class FailureMode : uint8_t {
Infallible,
FailOnNegI32,
FailOnMaxI32,
FailOnNullPtr,
FailOnInvalidRef
};

View File

@ -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"

View File

@ -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.

View File

@ -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,

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}