diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/wasm/WasmJS.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/wasm/WasmJS.cpp')
-rw-r--r-- | js/src/wasm/WasmJS.cpp | 5524 |
1 files changed, 5524 insertions, 0 deletions
diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp new file mode 100644 index 0000000000..75a37dc194 --- /dev/null +++ b/js/src/wasm/WasmJS.cpp @@ -0,0 +1,5524 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * + * Copyright 2016 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wasm/WasmJS.h" + +#include "mozilla/CheckedInt.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/Maybe.h" +#include "mozilla/RangedPtr.h" + +#include <algorithm> +#include <cstdint> + +#include "jsapi.h" +#include "jsexn.h" + +#include "ds/IdValuePair.h" // js::IdValuePair +#include "frontend/FrontendContext.h" // AutoReportFrontendContext +#include "gc/GCContext.h" +#include "jit/AtomicOperations.h" +#include "jit/FlushICache.h" +#include "jit/JitContext.h" +#include "jit/JitOptions.h" +#include "jit/Simulator.h" +#include "js/ForOfIterator.h" +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/Printf.h" +#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty +#include "js/PropertySpec.h" // JS_{PS,FN}{,_END} +#include "js/Stack.h" // BuildStackString +#include "js/StreamConsumer.h" +#include "util/StringBuffer.h" +#include "util/Text.h" +#include "vm/ErrorObject.h" +#include "vm/FunctionFlags.h" // js::FunctionFlags +#include "vm/GlobalObject.h" // js::GlobalObject +#include "vm/HelperThreadState.h" // js::PromiseHelperTask +#include "vm/Interpreter.h" +#include "vm/JSFunction.h" +#include "vm/PlainObject.h" // js::PlainObject +#include "vm/PromiseObject.h" // js::PromiseObject +#include "vm/SharedArrayObject.h" +#include "vm/StringType.h" +#include "vm/Warnings.h" // js::WarnNumberASCII +#include "vm/WellKnownAtom.h" // js_*_str +#include "wasm/WasmBaselineCompile.h" +#include "wasm/WasmBuiltins.h" +#include "wasm/WasmCompile.h" +#include "wasm/WasmDebug.h" +#include "wasm/WasmInstance.h" +#include "wasm/WasmIntrinsic.h" +#include "wasm/WasmIonCompile.h" +#include "wasm/WasmMemory.h" +#include "wasm/WasmModule.h" +#include "wasm/WasmProcess.h" +#include "wasm/WasmSignalHandlers.h" +#include "wasm/WasmStubs.h" +#include "wasm/WasmValidate.h" + +#include "gc/GCContext-inl.h" +#include "gc/StableCellHasher-inl.h" +#include "vm/ArrayBufferObject-inl.h" +#include "vm/JSObject-inl.h" +#include "vm/NativeObject-inl.h" +#include "wasm/WasmInstance-inl.h" + +/* + * [SMDOC] WebAssembly code rules (evolving) + * + * TlsContext.get() is only to be invoked from functions that have been invoked + * _directly_ by generated code as cold(!) Builtin calls, from code that is + * only used by signal handlers, or from helper functions that have been + * called _directly_ from a simulator. All other code shall pass in a + * JSContext* to functions that need it, or an Instance* or Instance* since + * the context is available through them. + * + * Code that uses TlsContext.get() shall annotate each such call with the + * reason why the call is OK. + */ + +using namespace js; +using namespace js::jit; +using namespace js::wasm; + +using mozilla::CheckedInt; +using mozilla::Nothing; +using mozilla::RangedPtr; +using mozilla::Span; + +// About the fuzzer intercession points: If fuzzing has been selected and only a +// single compiler has been selected then we will disable features that are not +// supported by that single compiler. This is strictly a concession to the +// fuzzer infrastructure. + +static inline bool IsFuzzingIon(JSContext* cx) { + return IsFuzzing() && !cx->options().wasmBaseline() && + cx->options().wasmIon(); +} + +// These functions read flags and apply fuzzing intercession policies. Never go +// directly to the flags in code below, always go via these accessors. + +static inline bool WasmThreadsFlag(JSContext* cx) { + return cx->realm() && + cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled(); +} + +#define WASM_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, FLAG_PRED, \ + ...) \ + static inline bool Wasm##NAME##Flag(JSContext* cx) { \ + return (COMPILE_PRED) && (FLAG_PRED) && cx->options().wasm##NAME(); \ + } +JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE, WASM_FEATURE); +#undef WASM_FEATURE + +static inline bool WasmDebuggerActive(JSContext* cx) { + if (IsFuzzingIon(cx)) { + return false; + } + return cx->realm() && cx->realm()->debuggerObservesWasm(); +} + +/* + * [SMDOC] Compiler and feature selection; compiler and feature availability. + * + * In order to make the computation of whether a wasm feature or wasm compiler + * is available predictable, we have established some rules, and implemented + * those rules. + * + * Code elsewhere should use the predicates below to test for features and + * compilers, it should never try to compute feature and compiler availability + * in other ways. + * + * At the outset, there is a set of selected compilers C containing at most one + * baseline compiler [*] and at most one optimizing compiler [**], and a set of + * selected features F. These selections come from defaults and from overrides + * by command line switches in the shell and javascript.option.wasm_X in the + * browser. Defaults for both features and compilers may be platform specific, + * for example, some compilers may not be available on some platforms because + * they do not support the architecture at all or they do not support features + * that must be enabled by default on the platform. + * + * [*] Currently we have only one, "baseline" aka "Rabaldr", but other + * implementations have additional baseline translators, eg from wasm + * bytecode to an internal code processed by an interpreter. + * + * [**] Currently we have only one, "ion" aka "Baldr". + * + * + * Compiler availability: + * + * The set of features F induces a set of available compilers A: these are the + * compilers that all support all the features in F. (Some of these compilers + * may not be in the set C.) + * + * The sets C and A are intersected, yielding a set of enabled compilers E. + * Notably, the set E may be empty, in which case wasm is effectively disabled + * (though the WebAssembly object is still present in the global environment). + * + * An important consequence is that selecting a feature that is not supported by + * a particular compiler disables that compiler completely -- there is no notion + * of a compiler being available but suddenly failing when an unsupported + * feature is used by a program. If a compiler is available, it supports all + * the features that have been selected. + * + * Equally important, a feature cannot be enabled by default on a platform if + * the feature is not supported by all the compilers we wish to have enabled by + * default on the platform. We MUST by-default disable features on a platform + * that are not supported by all the compilers on the platform. + * + * In a shell build, the testing functions wasmCompilersPresent, + * wasmCompileMode, and wasmIonDisabledByFeatures can be used to probe compiler + * availability and the reasons for a compiler being unavailable. + * + * + * Feature availability: + * + * A feature is available if it is selected and there is at least one available + * compiler that implements it. + * + * For example, --wasm-gc selects the GC feature, and if Baseline is available + * then the feature is available. + * + * In a shell build, there are per-feature testing functions (of the form + * wasmFeatureEnabled) to probe whether specific features are available. + */ + +// Compiler availability predicates. These must be kept in sync with the +// feature predicates in the next section below. +// +// These can't call the feature predicates since the feature predicates call +// back to these predicates. So there will be a small amount of duplicated +// logic here, but as compilers reach feature parity that duplication will go +// away. + +bool wasm::BaselineAvailable(JSContext* cx) { + if (!cx->options().wasmBaseline() || !BaselinePlatformSupport()) { + return false; + } + bool isDisabled = false; + MOZ_ALWAYS_TRUE(BaselineDisabledByFeatures(cx, &isDisabled)); + return !isDisabled; +} + +bool wasm::IonAvailable(JSContext* cx) { + if (!cx->options().wasmIon() || !IonPlatformSupport()) { + return false; + } + bool isDisabled = false; + MOZ_ALWAYS_TRUE(IonDisabledByFeatures(cx, &isDisabled)); + return !isDisabled; +} + +bool wasm::WasmCompilerForAsmJSAvailable(JSContext* cx) { + return IonAvailable(cx); +} + +template <size_t ArrayLength> +static inline bool Append(JSStringBuilder* reason, const char (&s)[ArrayLength], + char* sep) { + if ((*sep && !reason->append(*sep)) || !reason->append(s)) { + return false; + } + *sep = ','; + return true; +} + +bool wasm::BaselineDisabledByFeatures(JSContext* cx, bool* isDisabled, + JSStringBuilder* reason) { + // Baseline cannot be used if we are testing serialization. + bool testSerialization = WasmTestSerializationFlag(cx); + if (reason) { + char sep = 0; + if (testSerialization && !Append(reason, "testSerialization", &sep)) { + return false; + } + } + *isDisabled = testSerialization; + return true; +} + +bool wasm::IonDisabledByFeatures(JSContext* cx, bool* isDisabled, + JSStringBuilder* reason) { + // Ion has no debugging support. + bool debug = WasmDebuggerActive(cx); + if (reason) { + char sep = 0; + if (debug && !Append(reason, "debug", &sep)) { + return false; + } + } + *isDisabled = debug; + return true; +} + +bool wasm::AnyCompilerAvailable(JSContext* cx) { + return wasm::BaselineAvailable(cx) || wasm::IonAvailable(cx); +} + +// Feature predicates. These must be kept in sync with the predicates in the +// section above. +// +// The meaning of these predicates is tricky: A predicate is true for a feature +// if the feature is enabled and/or compiled-in *and* we have *at least one* +// compiler that can support the feature. Subsequent compiler selection must +// ensure that only compilers that actually support the feature are used. + +#define WASM_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, FLAG_PRED, \ + ...) \ + bool wasm::NAME##Available(JSContext* cx) { \ + return Wasm##NAME##Flag(cx) && (COMPILER_PRED); \ + } +JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE, WASM_FEATURE) +#undef WASM_FEATURE + +bool wasm::IsSimdPrivilegedContext(JSContext* cx) { + // This may be slightly more lenient than we want in an ideal world, but it + // remains safe. + return cx->realm() && cx->realm()->principals() && + cx->realm()->principals()->isSystemOrAddonPrincipal(); +} + +bool wasm::SimdAvailable(JSContext* cx) { + return js::jit::JitSupportsWasmSimd(); +} + +bool wasm::ThreadsAvailable(JSContext* cx) { + return WasmThreadsFlag(cx) && AnyCompilerAvailable(cx); +} + +bool wasm::HasPlatformSupport(JSContext* cx) { +#if !MOZ_LITTLE_ENDIAN() + return false; +#else + + if (!HasJitBackend()) { + return false; + } + + if (gc::SystemPageSize() > wasm::PageSize) { + return false; + } + + if (!JitOptions.supportsUnalignedAccesses) { + return false; + } + +# ifndef __wasi__ + // WASI doesn't support signals so we don't have this function. + if (!wasm::EnsureFullSignalHandlers(cx)) { + return false; + } +# endif + + if (!jit::JitSupportsAtomics()) { + return false; + } + + // Wasm threads require 8-byte lock-free atomics. + if (!jit::AtomicOperations::isLockfree8()) { + return false; + } + + // Test only whether the compilers are supported on the hardware, not whether + // they are enabled. + return BaselinePlatformSupport() || IonPlatformSupport(); +#endif +} + +bool wasm::HasSupport(JSContext* cx) { + // If the general wasm pref is on, it's on for everything. + bool prefEnabled = cx->options().wasm(); + // If the general pref is off, check trusted principals. + if (MOZ_UNLIKELY(!prefEnabled)) { + prefEnabled = cx->options().wasmForTrustedPrinciples() && cx->realm() && + cx->realm()->principals() && + cx->realm()->principals()->isSystemOrAddonPrincipal(); + } + // Do not check for compiler availability, as that may be run-time variant. + // For HasSupport() we want a stable answer depending only on prefs. + return prefEnabled && HasPlatformSupport(cx); +} + +bool wasm::StreamingCompilationAvailable(JSContext* cx) { + // This should match EnsureStreamSupport(). + return HasSupport(cx) && AnyCompilerAvailable(cx) && + cx->runtime()->offThreadPromiseState.ref().initialized() && + CanUseExtraThreads() && cx->runtime()->consumeStreamCallback && + cx->runtime()->reportStreamErrorCallback; +} + +bool wasm::CodeCachingAvailable(JSContext* cx) { + // Fuzzilli breaks the out-of-process compilation mechanism, + // so we disable it permanently in those builds. +#ifdef FUZZING_JS_FUZZILLI + return false; +#else + + // At the moment, we require Ion support for code caching. The main reason + // for this is that wasm::CompileAndSerialize() does not have access to + // information about which optimizing compiler it should use. See comments in + // CompileAndSerialize(), below. + return StreamingCompilationAvailable(cx) && IonAvailable(cx); +#endif +} + +// ============================================================================ +// Imports + +static bool ThrowBadImportArg(JSContext* cx) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_IMPORT_ARG); + return false; +} + +static bool ThrowBadImportType(JSContext* cx, const CacheableName& field, + const char* str) { + UniqueChars fieldQuoted = field.toQuotedString(cx); + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_IMPORT_TYPE, fieldQuoted.get(), str); + return false; +} + +bool js::wasm::GetImports(JSContext* cx, const Module& module, + HandleObject importObj, ImportValues* imports) { + if (!module.imports().empty() && !importObj) { + return ThrowBadImportArg(cx); + } + + const Metadata& metadata = module.metadata(); + + uint32_t tagIndex = 0; + const TagDescVector& tags = metadata.tags; + uint32_t globalIndex = 0; + const GlobalDescVector& globals = metadata.globals; + 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; + } + 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)) { + return false; + } + + switch (import.kind) { + case DefinitionKind::Function: { + // For now reject cross-compartment wrappers. These have more + // complicated realm semantics (we use nonCCWRealm in a few places) and + // may require unwrapping to test for specific function types. + if (!IsCallable(v) || IsCrossCompartmentWrapper(&v.toObject())) { + return ThrowBadImportType(cx, import.field, "Function"); + } + + if (!imports->funcs.append(&v.toObject())) { + return false; + } + + break; + } + case DefinitionKind::Table: { + const uint32_t index = tableIndex++; + if (!v.isObject() || !v.toObject().is<WasmTableObject>()) { + return ThrowBadImportType(cx, import.field, "Table"); + } + + Rooted<WasmTableObject*> obj(cx, &v.toObject().as<WasmTableObject>()); + if (obj->table().elemType() != tables[index].elemType) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_TBL_TYPE_LINK); + return false; + } + + if (!imports->tables.append(obj)) { + return false; + } + break; + } + case DefinitionKind::Memory: { + if (!v.isObject() || !v.toObject().is<WasmMemoryObject>()) { + return ThrowBadImportType(cx, import.field, "Memory"); + } + + MOZ_ASSERT(!imports->memory); + imports->memory = &v.toObject().as<WasmMemoryObject>(); + break; + } + case DefinitionKind::Tag: { + const uint32_t index = tagIndex++; + if (!v.isObject() || !v.toObject().is<WasmTagObject>()) { + return ThrowBadImportType(cx, import.field, "Tag"); + } + + Rooted<WasmTagObject*> obj(cx, &v.toObject().as<WasmTagObject>()); + + // Checks whether the signature of the imported exception object matches + // the signature declared in the exception import's TagDesc. + if (obj->resultType() != tags[index].type->resultType()) { + UniqueChars fieldQuoted = import.field.toQuotedString(cx); + UniqueChars moduleQuoted = import.module.toQuotedString(cx); + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_TAG_SIG, moduleQuoted.get(), + fieldQuoted.get()); + return false; + } + + if (!imports->tagObjs.append(obj)) { + ReportOutOfMemory(cx); + return false; + } + break; + } + case DefinitionKind::Global: { + const uint32_t index = globalIndex++; + const GlobalDesc& global = globals[index]; + MOZ_ASSERT(global.importIndex() == index); + + RootedVal val(cx); + if (v.isObject() && v.toObject().is<WasmGlobalObject>()) { + Rooted<WasmGlobalObject*> obj(cx, + &v.toObject().as<WasmGlobalObject>()); + + if (obj->isMutable() != global.isMutable()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_GLOB_MUT_LINK); + return false; + } + if (obj->type() != global.type()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_GLOB_TYPE_LINK); + return false; + } + + if (imports->globalObjs.length() <= index && + !imports->globalObjs.resize(index + 1)) { + ReportOutOfMemory(cx); + return false; + } + imports->globalObjs[index] = obj; + val = obj->val(); + } else { + if (!global.type().isRefType()) { + if (global.type() == ValType::I64 && !v.isBigInt()) { + return ThrowBadImportType(cx, import.field, "BigInt"); + } + if (global.type() != ValType::I64 && !v.isNumber()) { + return ThrowBadImportType(cx, import.field, "Number"); + } + } else { + if (!global.type().isExternRef() && !v.isObjectOrNull()) { + return ThrowBadImportType(cx, import.field, + "Object-or-null value required for " + "non-externref reference type"); + } + } + + if (global.isMutable()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_GLOB_MUT_LINK); + return false; + } + + if (!Val::fromJSValue(cx, global.type(), v, &val)) { + return false; + } + } + + if (!imports->globalValues.append(val)) { + return false; + } + + break; + } + } + } + + MOZ_ASSERT(globalIndex == globals.length() || + !globals[globalIndex].isImport()); + + return true; +} + +static bool DescribeScriptedCaller(JSContext* cx, ScriptedCaller* caller, + const char* introducer) { + // Note: JS::DescribeScriptedCaller returns whether a scripted caller was + // found, not whether an error was thrown. This wrapper function converts + // back to the more ordinary false-if-error form. + + JS::AutoFilename af; + if (JS::DescribeScriptedCaller(cx, &af, &caller->line)) { + caller->filename = + FormatIntroducedFilename(af.get(), caller->line, introducer); + if (!caller->filename) { + ReportOutOfMemory(cx); + return false; + } + } + + return true; +} + +static SharedCompileArgs InitCompileArgs(JSContext* cx, + const char* introducer) { + ScriptedCaller scriptedCaller; + if (!DescribeScriptedCaller(cx, &scriptedCaller, introducer)) { + return nullptr; + } + + FeatureOptions options; + return CompileArgs::buildAndReport(cx, std::move(scriptedCaller), options); +} + +// ============================================================================ +// Testing / Fuzzing support + +bool wasm::Eval(JSContext* cx, Handle<TypedArrayObject*> code, + HandleObject importObj, + MutableHandle<WasmInstanceObject*> instanceObj) { + if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly)) { + return false; + } + + MutableBytes bytecode = cx->new_<ShareableBytes>(); + if (!bytecode) { + return false; + } + + if (!bytecode->append((uint8_t*)code->dataPointerEither().unwrap(), + code->byteLength())) { + ReportOutOfMemory(cx); + return false; + } + + SharedCompileArgs compileArgs = InitCompileArgs(cx, "wasm_eval"); + if (!compileArgs) { + return false; + } + + UniqueChars error; + UniqueCharsVector warnings; + SharedModule module = + CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr); + if (!module) { + if (error) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_COMPILE_ERROR, error.get()); + return false; + } + ReportOutOfMemory(cx); + return false; + } + + Rooted<ImportValues> imports(cx); + if (!GetImports(cx, *module, importObj, imports.address())) { + return false; + } + + return module->instantiate(cx, imports.get(), nullptr, instanceObj); +} + +struct MOZ_STACK_CLASS SerializeListener : JS::OptimizedEncodingListener { + // MOZ_STACK_CLASS means these can be nops. + MozExternalRefCountType MOZ_XPCOM_ABI AddRef() override { return 0; } + MozExternalRefCountType MOZ_XPCOM_ABI Release() override { return 0; } + + DebugOnly<bool> called = false; + Bytes* serialized; + explicit SerializeListener(Bytes* serialized) : serialized(serialized) {} + + void storeOptimizedEncoding(const uint8_t* bytes, size_t length) override { + MOZ_ASSERT(!called); + called = true; + if (serialized->resizeUninitialized(length)) { + memcpy(serialized->begin(), bytes, length); + } + } +}; + +bool wasm::CompileAndSerialize(JSContext* cx, const ShareableBytes& bytecode, + Bytes* serialized) { + // The caller must check that code caching is available + MOZ_ASSERT(CodeCachingAvailable(cx)); + + // Create and manually fill in compile args for code caching + MutableCompileArgs compileArgs = js_new<CompileArgs>(ScriptedCaller()); + if (!compileArgs) { + return false; + } + + // The caller has ensured CodeCachingAvailable(). Moreover, we want to ensure + // we go straight to tier-2 so that we synchronously call + // JS::OptimizedEncodingListener::storeOptimizedEncoding(). + compileArgs->baselineEnabled = false; + compileArgs->forceTiering = false; + + // We always pick Ion here, and we depend on CodeCachingAvailable() having + // determined that Ion is available, see comments at CodeCachingAvailable(). + // To do better, we need to pass information about which compiler that should + // be used into CompileAndSerialize(). + compileArgs->ionEnabled = true; + + // Select features that are enabled. This is guaranteed to be consistent with + // our compiler selection, as code caching is only available if ion is + // available, and ion is only available if it's not disabled by enabled + // features. + compileArgs->features = FeatureArgs::build(cx, FeatureOptions()); + + SerializeListener listener(serialized); + + UniqueChars error; + UniqueCharsVector warnings; + SharedModule module = + CompileBuffer(*compileArgs, bytecode, &error, &warnings, &listener); + if (!module) { + fprintf(stderr, "Compilation error: %s\n", error ? error.get() : "oom"); + return false; + } + + MOZ_ASSERT(module->code().hasTier(Tier::Serialized)); + MOZ_ASSERT(listener.called); + return !listener.serialized->empty(); +} + +bool wasm::DeserializeModule(JSContext* cx, const Bytes& serialized, + MutableHandleObject moduleObj) { + MutableModule module = + Module::deserialize(serialized.begin(), serialized.length()); + if (!module) { + ReportOutOfMemory(cx); + return false; + } + + moduleObj.set(module->createObject(cx)); + return !!moduleObj; +} + +// ============================================================================ +// Common functions + +// '[EnforceRange] unsigned long' types are coerced with +// ConvertToInt(v, 32, 'unsigned') +// defined in Web IDL Section 3.2.4.9. +// +// This just generalizes that to an arbitrary limit that is representable as an +// integer in double form. + +static bool EnforceRange(JSContext* cx, HandleValue v, const char* kind, + const char* noun, uint64_t max, uint64_t* val) { + // Step 4. + double x; + if (!ToNumber(cx, v, &x)) { + return false; + } + + // Step 5. + if (mozilla::IsNegativeZero(x)) { + x = 0.0; + } + + // Step 6.1. + if (!std::isfinite(x)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_ENFORCE_RANGE, kind, noun); + return false; + } + + // Step 6.2. + x = JS::ToInteger(x); + + // Step 6.3. + if (x < 0 || x > double(max)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_ENFORCE_RANGE, kind, noun); + return false; + } + + *val = uint64_t(x); + MOZ_ASSERT(double(*val) == x); + return true; +} + +static bool EnforceRangeU32(JSContext* cx, HandleValue v, const char* kind, + const char* noun, uint32_t* u32) { + uint64_t u64 = 0; + if (!EnforceRange(cx, v, kind, noun, uint64_t(UINT32_MAX), &u64)) { + return false; + } + *u32 = uint32_t(u64); + return true; +} + +static bool EnforceRangeU64(JSContext* cx, HandleValue v, const char* kind, + const char* noun, uint64_t* u64) { + // The max is Number.MAX_SAFE_INTEGER + return EnforceRange(cx, v, kind, noun, (1LL << 53) - 1, u64); +} + +static bool GetLimit(JSContext* cx, HandleObject obj, const char* name, + const char* noun, const char* msg, uint32_t range, + bool* found, uint64_t* value) { + JSAtom* atom = Atomize(cx, name, strlen(name)); + if (!atom) { + return false; + } + RootedId id(cx, AtomToId(atom)); + + RootedValue val(cx); + if (!GetProperty(cx, obj, obj, id, &val)) { + return false; + } + + if (val.isUndefined()) { + *found = false; + return true; + } + *found = true; + // The range can be greater than 53, but then the logic in EnforceRange has to + // change to avoid precision loss. + MOZ_ASSERT(range < 54); + return EnforceRange(cx, val, noun, msg, (uint64_t(1) << range) - 1, value); +} + +static bool GetLimits(JSContext* cx, HandleObject obj, LimitsKind kind, + Limits* limits) { + limits->indexType = IndexType::I32; + + // Memory limits may specify an alternate index type, and we need this to + // check the ranges for initial and maximum, so look for the index type first. + if (kind == LimitsKind::Memory) { +#ifdef ENABLE_WASM_MEMORY64 + // Get the index type field + JSAtom* indexTypeAtom = Atomize(cx, "index", strlen("index")); + if (!indexTypeAtom) { + return false; + } + RootedId indexTypeId(cx, AtomToId(indexTypeAtom)); + + RootedValue indexTypeVal(cx); + if (!GetProperty(cx, obj, obj, indexTypeId, &indexTypeVal)) { + return false; + } + + // The index type has a default value + if (!indexTypeVal.isUndefined()) { + if (!ToIndexType(cx, indexTypeVal, &limits->indexType)) { + return false; + } + + if (limits->indexType == IndexType::I64 && !Memory64Available(cx)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_WASM_NO_MEM64_LINK); + return false; + } + } +#endif + } + + const char* noun = (kind == LimitsKind::Memory ? "Memory" : "Table"); + // 2^48 is a valid value, so the range goes to 49 bits. Values above 2^48 are + // filtered later, just as values above 2^16 are filtered for mem32. + const uint32_t range = limits->indexType == IndexType::I32 ? 32 : 49; + uint64_t limit = 0; + + bool haveInitial = false; + if (!GetLimit(cx, obj, "initial", noun, "initial size", range, &haveInitial, + &limit)) { + return false; + } + if (haveInitial) { + limits->initial = limit; + } + + bool haveMinimum = false; +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + if (!GetLimit(cx, obj, "minimum", noun, "initial size", range, &haveMinimum, + &limit)) { + return false; + } + if (haveMinimum) { + limits->initial = limit; + } +#endif + + if (!(haveInitial || haveMinimum)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_MISSING_REQUIRED, "initial"); + return false; + } + if (haveInitial && haveMinimum) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_SUPPLY_ONLY_ONE, "minimum", "initial"); + return false; + } + + bool haveMaximum = false; + if (!GetLimit(cx, obj, "maximum", noun, "maximum size", range, &haveMaximum, + &limit)) { + return false; + } + if (haveMaximum) { + limits->maximum = Some(limit); + } + + limits->shared = Shareable::False; + + // Memory limits may be shared. + if (kind == LimitsKind::Memory) { + // Get the shared field + JSAtom* sharedAtom = Atomize(cx, "shared", strlen("shared")); + if (!sharedAtom) { + return false; + } + RootedId sharedId(cx, AtomToId(sharedAtom)); + + RootedValue sharedVal(cx); + if (!GetProperty(cx, obj, obj, sharedId, &sharedVal)) { + return false; + } + + // shared's default value is false, which is already the value set above. + if (!sharedVal.isUndefined()) { + limits->shared = + ToBoolean(sharedVal) ? Shareable::True : Shareable::False; + + if (limits->shared == Shareable::True) { + if (!haveMaximum) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_WASM_MISSING_MAXIMUM, noun); + return false; + } + + if (!cx->realm() + ->creationOptions() + .getSharedMemoryAndAtomicsEnabled()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_WASM_NO_SHMEM_LINK); + return false; + } + } + } + } + + return true; +} + +static bool CheckLimits(JSContext* cx, uint64_t maximumField, LimitsKind kind, + Limits* limits) { + const char* noun = (kind == LimitsKind::Memory ? "Memory" : "Table"); + + if (limits->initial > maximumField) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE, + noun, "initial size"); + return false; + } + + if (limits->maximum.isSome() && + (*limits->maximum > maximumField || limits->initial > *limits->maximum)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE, + noun, "maximum size"); + return false; + } + return true; +} + +template <class Class, const char* name> +static JSObject* CreateWasmConstructor(JSContext* cx, JSProtoKey key) { + Rooted<JSAtom*> className(cx, Atomize(cx, name, strlen(name))); + if (!className) { + return nullptr; + } + + return NewNativeConstructor(cx, Class::construct, 1, className); +} + +static JSObject* GetWasmConstructorPrototype(JSContext* cx, + const CallArgs& callArgs, + JSProtoKey key) { + RootedObject proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, callArgs, key, &proto)) { + return nullptr; + } + if (!proto) { + proto = GlobalObject::getOrCreatePrototype(cx, key); + } + return proto; +} + +[[nodiscard]] static bool ParseValTypes(JSContext* cx, HandleValue src, + ValTypeVector& dest) { + JS::ForOfIterator iterator(cx); + + if (!iterator.init(src, JS::ForOfIterator::ThrowOnNonIterable)) { + return false; + } + + RootedValue nextParam(cx); + while (true) { + bool done; + if (!iterator.next(&nextParam, &done)) { + return false; + } + if (done) { + break; + } + + ValType valType; + if (!ToValType(cx, nextParam, &valType) || !dest.append(valType)) { + return false; + } + } + return true; +} + +#ifdef ENABLE_WASM_TYPE_REFLECTIONS +static JSString* UTF8CharsToString(JSContext* cx, const char* chars) { + return NewStringCopyUTF8Z(cx, JS::ConstUTF8CharsZ(chars, strlen(chars))); +} + +[[nodiscard]] static JSObject* ValTypesToArray(JSContext* cx, + const ValTypeVector& valTypes) { + Rooted<ArrayObject*> arrayObj(cx, NewDenseEmptyArray(cx)); + if (!arrayObj) { + return nullptr; + } + for (ValType valType : valTypes) { + RootedString type(cx, + UTF8CharsToString(cx, ToString(valType, nullptr).get())); + if (!type) { + return nullptr; + } + if (!NewbornArrayPush(cx, arrayObj, StringValue(type))) { + return nullptr; + } + } + return arrayObj; +} + +static JSObject* FuncTypeToObject(JSContext* cx, const FuncType& type) { + Rooted<IdValueVector> props(cx, IdValueVector(cx)); + + RootedObject parametersObj(cx, ValTypesToArray(cx, type.args())); + if (!parametersObj || + !props.append(IdValuePair(NameToId(cx->names().parameters), + ObjectValue(*parametersObj)))) { + ReportOutOfMemory(cx); + return nullptr; + } + + RootedObject resultsObj(cx, ValTypesToArray(cx, type.results())); + if (!resultsObj || !props.append(IdValuePair(NameToId(cx->names().results), + ObjectValue(*resultsObj)))) { + ReportOutOfMemory(cx); + return nullptr; + } + + return NewPlainObjectWithUniqueNames(cx, props.begin(), props.length()); +} + +static JSObject* TableTypeToObject(JSContext* cx, RefType type, + uint32_t initial, Maybe<uint32_t> maximum) { + Rooted<IdValueVector> props(cx, IdValueVector(cx)); + + RootedString elementType( + cx, UTF8CharsToString(cx, ToString(type, nullptr).get())); + if (!elementType || !props.append(IdValuePair(NameToId(cx->names().element), + StringValue(elementType)))) { + ReportOutOfMemory(cx); + return nullptr; + } + + if (maximum.isSome()) { + if (!props.append(IdValuePair(NameToId(cx->names().maximum), + NumberValue(maximum.value())))) { + ReportOutOfMemory(cx); + return nullptr; + } + } + + if (!props.append( + IdValuePair(NameToId(cx->names().minimum), NumberValue(initial)))) { + ReportOutOfMemory(cx); + return nullptr; + } + + return NewPlainObjectWithUniqueNames(cx, props.begin(), props.length()); +} + +static JSObject* MemoryTypeToObject(JSContext* cx, bool shared, + wasm::IndexType indexType, + wasm::Pages minPages, + Maybe<wasm::Pages> maxPages) { + Rooted<IdValueVector> props(cx, IdValueVector(cx)); + if (maxPages) { + double maxPagesNum; + if (indexType == IndexType::I32) { + maxPagesNum = double(mozilla::AssertedCast<uint32_t>(maxPages->value())); + } else { + // The maximum number of pages is 2^48. + maxPagesNum = double(maxPages->value()); + } + if (!props.append(IdValuePair(NameToId(cx->names().maximum), + NumberValue(maxPagesNum)))) { + ReportOutOfMemory(cx); + return nullptr; + } + } + + double minPagesNum; + if (indexType == IndexType::I32) { + minPagesNum = double(mozilla::AssertedCast<uint32_t>(minPages.value())); + } else { + minPagesNum = double(minPages.value()); + } + if (!props.append(IdValuePair(NameToId(cx->names().minimum), + NumberValue(minPagesNum)))) { + ReportOutOfMemory(cx); + return nullptr; + } + +# ifdef ENABLE_WASM_MEMORY64 + RootedString it( + cx, JS_NewStringCopyZ(cx, indexType == IndexType::I32 ? "i32" : "i64")); + if (!it) { + return nullptr; + } + if (!props.append( + IdValuePair(NameToId(cx->names().index), StringValue(it)))) { + ReportOutOfMemory(cx); + return nullptr; + } +# endif + + if (!props.append( + IdValuePair(NameToId(cx->names().shared), BooleanValue(shared)))) { + ReportOutOfMemory(cx); + return nullptr; + } + + return NewPlainObjectWithUniqueNames(cx, props.begin(), props.length()); +} + +static JSObject* GlobalTypeToObject(JSContext* cx, ValType type, + bool isMutable) { + Rooted<IdValueVector> props(cx, IdValueVector(cx)); + + if (!props.append(IdValuePair(NameToId(cx->names().mutable_), + BooleanValue(isMutable)))) { + ReportOutOfMemory(cx); + return nullptr; + } + + RootedString valueType(cx, + UTF8CharsToString(cx, ToString(type, nullptr).get())); + if (!valueType || !props.append(IdValuePair(NameToId(cx->names().value), + StringValue(valueType)))) { + ReportOutOfMemory(cx); + return nullptr; + } + + return NewPlainObjectWithUniqueNames(cx, props.begin(), props.length()); +} + +static JSObject* TagTypeToObject(JSContext* cx, + const wasm::ValTypeVector& params) { + Rooted<IdValueVector> props(cx, IdValueVector(cx)); + + RootedObject parametersObj(cx, ValTypesToArray(cx, params)); + if (!parametersObj || + !props.append(IdValuePair(NameToId(cx->names().parameters), + ObjectValue(*parametersObj)))) { + ReportOutOfMemory(cx); + return nullptr; + } + + return NewPlainObjectWithUniqueNames(cx, props.begin(), props.length()); +} +#endif // ENABLE_WASM_TYPE_REFLECTIONS + +// ============================================================================ +// WebAssembly.Module class and methods + +const JSClassOps WasmModuleObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + WasmModuleObject::finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +const JSClass WasmModuleObject::class_ = { + "WebAssembly.Module", + JSCLASS_DELAY_METADATA_BUILDER | + JSCLASS_HAS_RESERVED_SLOTS(WasmModuleObject::RESERVED_SLOTS) | + JSCLASS_FOREGROUND_FINALIZE, + &WasmModuleObject::classOps_, + &WasmModuleObject::classSpec_, +}; + +const JSClass& WasmModuleObject::protoClass_ = PlainObject::class_; + +static constexpr char WasmModuleName[] = "Module"; + +const ClassSpec WasmModuleObject::classSpec_ = { + CreateWasmConstructor<WasmModuleObject, WasmModuleName>, + GenericCreatePrototype<WasmModuleObject>, + WasmModuleObject::static_methods, + nullptr, + WasmModuleObject::methods, + WasmModuleObject::properties, + nullptr, + ClassSpec::DontDefineConstructor}; + +const JSPropertySpec WasmModuleObject::properties[] = { + JS_STRING_SYM_PS(toStringTag, "WebAssembly.Module", JSPROP_READONLY), + JS_PS_END}; + +const JSFunctionSpec WasmModuleObject::methods[] = {JS_FS_END}; + +const JSFunctionSpec WasmModuleObject::static_methods[] = { + JS_FN("imports", WasmModuleObject::imports, 1, JSPROP_ENUMERATE), + JS_FN("exports", WasmModuleObject::exports, 1, JSPROP_ENUMERATE), + JS_FN("customSections", WasmModuleObject::customSections, 2, + JSPROP_ENUMERATE), + JS_FS_END}; + +/* static */ +void WasmModuleObject::finalize(JS::GCContext* gcx, JSObject* obj) { + const Module& module = obj->as<WasmModuleObject>().module(); + obj->zone()->decJitMemory(module.codeLength(module.code().stableTier())); + gcx->release(obj, &module, module.gcMallocBytesExcludingCode(), + MemoryUse::WasmModule); +} + +static bool IsModuleObject(JSObject* obj, const Module** module) { + WasmModuleObject* mobj = obj->maybeUnwrapIf<WasmModuleObject>(); + if (!mobj) { + return false; + } + + *module = &mobj->module(); + return true; +} + +static bool GetModuleArg(JSContext* cx, CallArgs args, uint32_t numRequired, + const char* name, const Module** module) { + if (!args.requireAtLeast(cx, name, numRequired)) { + return false; + } + + if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), module)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_MOD_ARG); + return false; + } + + return true; +} + +struct KindNames { + Rooted<PropertyName*> kind; + Rooted<PropertyName*> table; + Rooted<PropertyName*> memory; + Rooted<PropertyName*> tag; + Rooted<PropertyName*> type; + + explicit KindNames(JSContext* cx) + : kind(cx), table(cx), memory(cx), tag(cx), type(cx) {} +}; + +static bool InitKindNames(JSContext* cx, KindNames* names) { + JSAtom* kind = Atomize(cx, "kind", strlen("kind")); + if (!kind) { + return false; + } + names->kind = kind->asPropertyName(); + + JSAtom* table = Atomize(cx, "table", strlen("table")); + if (!table) { + return false; + } + names->table = table->asPropertyName(); + + JSAtom* memory = Atomize(cx, "memory", strlen("memory")); + if (!memory) { + return false; + } + names->memory = memory->asPropertyName(); + + JSAtom* tag = Atomize(cx, "tag", strlen("tag")); + if (!tag) { + return false; + } + names->tag = tag->asPropertyName(); + + JSAtom* type = Atomize(cx, "type", strlen("type")); + if (!type) { + return false; + } + names->type = type->asPropertyName(); + + return true; +} + +static JSString* KindToString(JSContext* cx, const KindNames& names, + DefinitionKind kind) { + switch (kind) { + case DefinitionKind::Function: + return cx->names().function; + case DefinitionKind::Table: + return names.table; + case DefinitionKind::Memory: + return names.memory; + case DefinitionKind::Global: + return cx->names().global; + case DefinitionKind::Tag: + return names.tag; + } + + MOZ_CRASH("invalid kind"); +} + +/* static */ +bool WasmModuleObject::imports(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + const Module* module; + if (!GetModuleArg(cx, args, 1, "WebAssembly.Module.imports", &module)) { + return false; + } + + KindNames names(cx); + if (!InitKindNames(cx, &names)) { + return false; + } + + RootedValueVector elems(cx); + if (!elems.reserve(module->imports().length())) { + return false; + } + +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + const Metadata& metadata = module->metadata(); + const MetadataTier& metadataTier = + module->metadata(module->code().stableTier()); + + size_t numFuncImport = 0; + size_t numMemoryImport = 0; + size_t numGlobalImport = 0; + size_t numTableImport = 0; + size_t numTagImport = 0; +#endif // ENABLE_WASM_TYPE_REFLECTIONS + + for (const Import& import : module->imports()) { + Rooted<IdValueVector> props(cx, IdValueVector(cx)); + if (!props.reserve(3)) { + return false; + } + + JSString* moduleStr = import.module.toAtom(cx); + if (!moduleStr) { + return false; + } + props.infallibleAppend( + IdValuePair(NameToId(cx->names().module), StringValue(moduleStr))); + + JSString* nameStr = import.field.toAtom(cx); + if (!nameStr) { + return false; + } + props.infallibleAppend( + IdValuePair(NameToId(cx->names().name), StringValue(nameStr))); + + JSString* kindStr = KindToString(cx, names, import.kind); + if (!kindStr) { + return false; + } + props.infallibleAppend( + IdValuePair(NameToId(names.kind), StringValue(kindStr))); + +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + RootedObject typeObj(cx); + switch (import.kind) { + case DefinitionKind::Function: { + size_t funcIndex = numFuncImport++; + const FuncType& funcType = + metadata.getFuncImportType(metadataTier.funcImports[funcIndex]); + typeObj = FuncTypeToObject(cx, funcType); + break; + } + case DefinitionKind::Table: { + size_t tableIndex = numTableImport++; + const TableDesc& table = metadata.tables[tableIndex]; + typeObj = TableTypeToObject(cx, table.elemType, table.initialLength, + table.maximumLength); + break; + } + case DefinitionKind::Memory: { + DebugOnly<size_t> memoryIndex = numMemoryImport++; + MOZ_ASSERT(memoryIndex == 0); + const MemoryDesc& memory = *metadata.memory; + typeObj = + MemoryTypeToObject(cx, memory.isShared(), memory.indexType(), + memory.initialPages(), memory.maximumPages()); + break; + } + case DefinitionKind::Global: { + size_t globalIndex = numGlobalImport++; + const GlobalDesc& global = metadata.globals[globalIndex]; + typeObj = GlobalTypeToObject(cx, global.type(), global.isMutable()); + break; + } + case DefinitionKind::Tag: { + size_t tagIndex = numTagImport++; + const TagDesc& tag = metadata.tags[tagIndex]; + typeObj = TagTypeToObject(cx, tag.type->argTypes_); + break; + } + } + + if (!typeObj || !props.append(IdValuePair(NameToId(names.type), + ObjectValue(*typeObj)))) { + ReportOutOfMemory(cx); + return false; + } +#endif // ENABLE_WASM_TYPE_REFLECTIONS + + JSObject* obj = + NewPlainObjectWithUniqueNames(cx, props.begin(), props.length()); + if (!obj) { + return false; + } + + elems.infallibleAppend(ObjectValue(*obj)); + } + + JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin()); + if (!arr) { + return false; + } + + args.rval().setObject(*arr); + return true; +} + +/* static */ +bool WasmModuleObject::exports(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + const Module* module; + if (!GetModuleArg(cx, args, 1, "WebAssembly.Module.exports", &module)) { + return false; + } + + KindNames names(cx); + if (!InitKindNames(cx, &names)) { + return false; + } + + RootedValueVector elems(cx); + if (!elems.reserve(module->exports().length())) { + return false; + } + +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + const Metadata& metadata = module->metadata(); + const MetadataTier& metadataTier = + module->metadata(module->code().stableTier()); +#endif // ENABLE_WASM_TYPE_REFLECTIONS + + for (const Export& exp : module->exports()) { + Rooted<IdValueVector> props(cx, IdValueVector(cx)); + if (!props.reserve(2)) { + return false; + } + + JSString* nameStr = exp.fieldName().toAtom(cx); + if (!nameStr) { + return false; + } + props.infallibleAppend( + IdValuePair(NameToId(cx->names().name), StringValue(nameStr))); + + JSString* kindStr = KindToString(cx, names, exp.kind()); + if (!kindStr) { + return false; + } + props.infallibleAppend( + IdValuePair(NameToId(names.kind), StringValue(kindStr))); + +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + RootedObject typeObj(cx); + switch (exp.kind()) { + case DefinitionKind::Function: { + const FuncExport& fe = metadataTier.lookupFuncExport(exp.funcIndex()); + const FuncType& funcType = metadata.getFuncExportType(fe); + typeObj = FuncTypeToObject(cx, funcType); + break; + } + case DefinitionKind::Table: { + const TableDesc& table = metadata.tables[exp.tableIndex()]; + typeObj = TableTypeToObject(cx, table.elemType, table.initialLength, + table.maximumLength); + break; + } + case DefinitionKind::Memory: { + const MemoryDesc& memory = *metadata.memory; + typeObj = + MemoryTypeToObject(cx, memory.isShared(), memory.indexType(), + memory.initialPages(), memory.maximumPages()); + break; + } + case DefinitionKind::Global: { + const GlobalDesc& global = metadata.globals[exp.globalIndex()]; + typeObj = GlobalTypeToObject(cx, global.type(), global.isMutable()); + break; + } + case DefinitionKind::Tag: { + const TagDesc& tag = metadata.tags[exp.tagIndex()]; + typeObj = TagTypeToObject(cx, tag.type->argTypes_); + break; + } + } + + if (!typeObj || !props.append(IdValuePair(NameToId(names.type), + ObjectValue(*typeObj)))) { + ReportOutOfMemory(cx); + return false; + } +#endif // ENABLE_WASM_TYPE_REFLECTIONS + + JSObject* obj = + NewPlainObjectWithUniqueNames(cx, props.begin(), props.length()); + if (!obj) { + return false; + } + + elems.infallibleAppend(ObjectValue(*obj)); + } + + JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin()); + if (!arr) { + return false; + } + + args.rval().setObject(*arr); + return true; +} + +/* static */ +bool WasmModuleObject::customSections(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + const Module* module; + if (!GetModuleArg(cx, args, 2, "WebAssembly.Module.customSections", + &module)) { + return false; + } + + Vector<char, 8> name(cx); + { + RootedString str(cx, ToString(cx, args.get(1))); + if (!str) { + return false; + } + + Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx)); + if (!linear) { + return false; + } + + if (!name.initLengthUninitialized( + JS::GetDeflatedUTF8StringLength(linear))) { + return false; + } + + (void)JS::DeflateStringToUTF8Buffer(linear, + Span(name.begin(), name.length())); + } + + RootedValueVector elems(cx); + RootedArrayBufferObject buf(cx); + for (const CustomSection& cs : module->customSections()) { + if (name.length() != cs.name.length()) { + continue; + } + if (memcmp(name.begin(), cs.name.begin(), name.length()) != 0) { + continue; + } + + buf = ArrayBufferObject::createZeroed(cx, cs.payload->length()); + if (!buf) { + return false; + } + + memcpy(buf->dataPointer(), cs.payload->begin(), cs.payload->length()); + if (!elems.append(ObjectValue(*buf))) { + return false; + } + } + + JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin()); + if (!arr) { + return false; + } + + args.rval().setObject(*arr); + return true; +} + +/* static */ +WasmModuleObject* WasmModuleObject::create(JSContext* cx, const Module& module, + HandleObject proto) { + AutoSetNewObjectMetadata metadata(cx); + auto* obj = NewObjectWithGivenProto<WasmModuleObject>(cx, proto); + if (!obj) { + return nullptr; + } + + // The pipeline state on some architectures may retain stale instructions + // even after we invalidate the instruction cache. There is no generally + // available method to broadcast this pipeline flush to all threads after + // we've compiled new code, so conservatively perform one here when we're + // receiving a module that may have been compiled from another thread. + // + // The cost of this flush is expected to minimal enough to not be worth + // optimizing away in the case the module was compiled on this thread. + jit::FlushExecutionContext(); + + // This accounts for module allocation size (excluding code which is handled + // separately - see below). This assumes that the size of associated data + // doesn't change for the life of the WasmModuleObject. The size is counted + // once per WasmModuleObject referencing a Module. + InitReservedSlot(obj, MODULE_SLOT, const_cast<Module*>(&module), + module.gcMallocBytesExcludingCode(), MemoryUse::WasmModule); + module.AddRef(); + + // Bug 1569888: We account for the first tier here; the second tier, if + // different, also needs to be accounted for. + cx->zone()->incJitMemory(module.codeLength(module.code().stableTier())); + return obj; +} + +static bool GetBufferSource(JSContext* cx, JSObject* obj, unsigned errorNumber, + MutableBytes* bytecode) { + *bytecode = cx->new_<ShareableBytes>(); + if (!*bytecode) { + return false; + } + + JSObject* unwrapped = CheckedUnwrapStatic(obj); + + SharedMem<uint8_t*> dataPointer; + size_t byteLength; + if (!unwrapped || !IsBufferSource(unwrapped, &dataPointer, &byteLength)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber); + return false; + } + + if (!(*bytecode)->append(dataPointer.unwrap(), byteLength)) { + ReportOutOfMemory(cx); + return false; + } + + return true; +} + +static bool ReportCompileWarnings(JSContext* cx, + const UniqueCharsVector& warnings) { + // Avoid spamming the console. + size_t numWarnings = std::min<size_t>(warnings.length(), 3); + + for (size_t i = 0; i < numWarnings; i++) { + if (!WarnNumberASCII(cx, JSMSG_WASM_COMPILE_WARNING, warnings[i].get())) { + return false; + } + } + + if (warnings.length() > numWarnings) { + if (!WarnNumberASCII(cx, JSMSG_WASM_COMPILE_WARNING, + "other warnings suppressed")) { + return false; + } + } + + return true; +} + +/* static */ +bool WasmModuleObject::construct(JSContext* cx, unsigned argc, Value* vp) { + CallArgs callArgs = CallArgsFromVp(argc, vp); + + Log(cx, "sync new Module() started"); + + if (!ThrowIfNotConstructing(cx, callArgs, "Module")) { + return false; + } + + if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_CSP_BLOCKED_WASM, "WebAssembly.Module"); + return false; + } + + if (!callArgs.requireAtLeast(cx, "WebAssembly.Module", 1)) { + return false; + } + + if (!callArgs[0].isObject()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_BUF_ARG); + return false; + } + + MutableBytes bytecode; + if (!GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG, + &bytecode)) { + return false; + } + + SharedCompileArgs compileArgs = InitCompileArgs(cx, "WebAssembly.Module"); + if (!compileArgs) { + return false; + } + + UniqueChars error; + UniqueCharsVector warnings; + SharedModule module = + CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr); + + if (!ReportCompileWarnings(cx, warnings)) { + return false; + } + if (!module) { + if (error) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_COMPILE_ERROR, error.get()); + return false; + } + ReportOutOfMemory(cx); + return false; + } + + RootedObject proto( + cx, GetWasmConstructorPrototype(cx, callArgs, JSProto_WasmModule)); + if (!proto) { + ReportOutOfMemory(cx); + return false; + } + + RootedObject moduleObj(cx, WasmModuleObject::create(cx, *module, proto)); + if (!moduleObj) { + return false; + } + + Log(cx, "sync new Module() succeded"); + + callArgs.rval().setObject(*moduleObj); + return true; +} + +const Module& WasmModuleObject::module() const { + MOZ_ASSERT(is<WasmModuleObject>()); + return *(const Module*)getReservedSlot(MODULE_SLOT).toPrivate(); +} + +// ============================================================================ +// WebAssembly.Instance class and methods + +const JSClassOps WasmInstanceObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + WasmInstanceObject::finalize, // finalize + nullptr, // call + nullptr, // construct + WasmInstanceObject::trace, // trace +}; + +const JSClass WasmInstanceObject::class_ = { + "WebAssembly.Instance", + JSCLASS_DELAY_METADATA_BUILDER | + JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceObject::RESERVED_SLOTS) | + JSCLASS_FOREGROUND_FINALIZE, + &WasmInstanceObject::classOps_, + &WasmInstanceObject::classSpec_, +}; + +const JSClass& WasmInstanceObject::protoClass_ = PlainObject::class_; + +static constexpr char WasmInstanceName[] = "Instance"; + +const ClassSpec WasmInstanceObject::classSpec_ = { + CreateWasmConstructor<WasmInstanceObject, WasmInstanceName>, + GenericCreatePrototype<WasmInstanceObject>, + WasmInstanceObject::static_methods, + nullptr, + WasmInstanceObject::methods, + WasmInstanceObject::properties, + nullptr, + ClassSpec::DontDefineConstructor}; + +static bool IsInstance(HandleValue v) { + return v.isObject() && v.toObject().is<WasmInstanceObject>(); +} + +/* static */ +bool WasmInstanceObject::exportsGetterImpl(JSContext* cx, + const CallArgs& args) { + args.rval().setObject( + args.thisv().toObject().as<WasmInstanceObject>().exportsObj()); + return true; +} + +/* static */ +bool WasmInstanceObject::exportsGetter(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsInstance, exportsGetterImpl>(cx, args); +} + +const JSPropertySpec WasmInstanceObject::properties[] = { + JS_PSG("exports", WasmInstanceObject::exportsGetter, JSPROP_ENUMERATE), + JS_STRING_SYM_PS(toStringTag, "WebAssembly.Instance", JSPROP_READONLY), + JS_PS_END}; + +const JSFunctionSpec WasmInstanceObject::methods[] = {JS_FS_END}; + +const JSFunctionSpec WasmInstanceObject::static_methods[] = {JS_FS_END}; + +bool WasmInstanceObject::isNewborn() const { + MOZ_ASSERT(is<WasmInstanceObject>()); + return getReservedSlot(INSTANCE_SLOT).isUndefined(); +} + +// WeakScopeMap maps from function index to js::Scope. This maps is weak +// to avoid holding scope objects alive. The scopes are normally created +// during debugging. +// +// This is defined here in order to avoid recursive dependency between +// WasmJS.h and Scope.h. +using WasmFunctionScopeMap = + JS::WeakCache<GCHashMap<uint32_t, WeakHeapPtr<WasmFunctionScope*>, + DefaultHasher<uint32_t>, CellAllocPolicy>>; +class WasmInstanceObject::UnspecifiedScopeMap { + public: + WasmFunctionScopeMap& asWasmFunctionScopeMap() { + return *(WasmFunctionScopeMap*)this; + } +}; + +/* static */ +void WasmInstanceObject::finalize(JS::GCContext* gcx, JSObject* obj) { + WasmInstanceObject& instance = obj->as<WasmInstanceObject>(); + gcx->delete_(obj, &instance.exports(), MemoryUse::WasmInstanceExports); + gcx->delete_(obj, &instance.scopes().asWasmFunctionScopeMap(), + MemoryUse::WasmInstanceScopes); + gcx->delete_(obj, &instance.indirectGlobals(), + MemoryUse::WasmInstanceGlobals); + if (!instance.isNewborn()) { + if (instance.instance().debugEnabled()) { + instance.instance().debug().finalize(gcx); + } + Instance::destroy(&instance.instance()); + gcx->removeCellMemory(obj, sizeof(Instance), + MemoryUse::WasmInstanceInstance); + } +} + +/* static */ +void WasmInstanceObject::trace(JSTracer* trc, JSObject* obj) { + WasmInstanceObject& instanceObj = obj->as<WasmInstanceObject>(); + instanceObj.exports().trace(trc); + instanceObj.indirectGlobals().trace(trc); + if (!instanceObj.isNewborn()) { + instanceObj.instance().tracePrivate(trc); + } +} + +/* static */ +WasmInstanceObject* WasmInstanceObject::create( + JSContext* cx, const SharedCode& code, + const DataSegmentVector& dataSegments, + const ElemSegmentVector& elemSegments, uint32_t instanceDataLength, + Handle<WasmMemoryObject*> memory, SharedTableVector&& tables, + const JSObjectVector& funcImports, const GlobalDescVector& globals, + const ValVector& globalImportValues, + const WasmGlobalObjectVector& globalObjs, + const WasmTagObjectVector& tagObjs, HandleObject proto, + UniqueDebugState maybeDebug) { + Rooted<UniquePtr<ExportMap>> exports(cx, + js::MakeUnique<ExportMap>(cx->zone())); + if (!exports) { + ReportOutOfMemory(cx); + return nullptr; + } + + UniquePtr<WasmFunctionScopeMap> scopes = + js::MakeUnique<WasmFunctionScopeMap>(cx->zone(), cx->zone()); + if (!scopes) { + ReportOutOfMemory(cx); + return nullptr; + } + // Note that `scopes` is a WeakCache, auto-linked into a sweep list on the + // Zone, and so does not require rooting. + + uint32_t indirectGlobals = 0; + + for (uint32_t i = 0; i < globalObjs.length(); i++) { + if (globalObjs[i] && globals[i].isIndirect()) { + indirectGlobals++; + } + } + + Rooted<UniquePtr<GlobalObjectVector>> indirectGlobalObjs( + cx, js::MakeUnique<GlobalObjectVector>(cx->zone())); + if (!indirectGlobalObjs || !indirectGlobalObjs->resize(indirectGlobals)) { + ReportOutOfMemory(cx); + return nullptr; + } + + { + uint32_t next = 0; + for (uint32_t i = 0; i < globalObjs.length(); i++) { + if (globalObjs[i] && globals[i].isIndirect()) { + (*indirectGlobalObjs)[next++] = globalObjs[i]; + } + } + } + + Instance* instance = nullptr; + Rooted<WasmInstanceObject*> obj(cx); + + { + // We must delay creating metadata for this object until after all its + // slots have been initialized. We must also create the metadata before + // calling Instance::init as that may allocate new objects. + AutoSetNewObjectMetadata metadata(cx); + obj = NewObjectWithGivenProto<WasmInstanceObject>(cx, proto); + if (!obj) { + return nullptr; + } + + MOZ_ASSERT(obj->isTenured(), "assumed by WasmTableObject write barriers"); + + // Finalization assumes these slots are always initialized: + InitReservedSlot(obj, EXPORTS_SLOT, exports.release(), + MemoryUse::WasmInstanceExports); + + InitReservedSlot(obj, SCOPES_SLOT, scopes.release(), + MemoryUse::WasmInstanceScopes); + + InitReservedSlot(obj, GLOBALS_SLOT, indirectGlobalObjs.release(), + MemoryUse::WasmInstanceGlobals); + + obj->initReservedSlot(INSTANCE_SCOPE_SLOT, UndefinedValue()); + + // The INSTANCE_SLOT may not be initialized if Instance allocation fails, + // leading to an observable "newborn" state in tracing/finalization. + MOZ_ASSERT(obj->isNewborn()); + + // Create this just before constructing Instance to avoid rooting hazards. + instance = Instance::create(cx, obj, code, instanceDataLength, memory, + std::move(tables), std::move(maybeDebug)); + if (!instance) { + return nullptr; + } + + InitReservedSlot(obj, INSTANCE_SLOT, instance, + MemoryUse::WasmInstanceInstance); + MOZ_ASSERT(!obj->isNewborn()); + } + + if (!instance->init(cx, funcImports, globalImportValues, globalObjs, tagObjs, + dataSegments, elemSegments)) { + return nullptr; + } + + return obj; +} + +void WasmInstanceObject::initExportsObj(JSObject& exportsObj) { + MOZ_ASSERT(getReservedSlot(EXPORTS_OBJ_SLOT).isUndefined()); + setReservedSlot(EXPORTS_OBJ_SLOT, ObjectValue(exportsObj)); +} + +static bool GetImportArg(JSContext* cx, CallArgs callArgs, + MutableHandleObject importObj) { + if (!callArgs.get(1).isUndefined()) { + if (!callArgs[1].isObject()) { + return ThrowBadImportArg(cx); + } + importObj.set(&callArgs[1].toObject()); + } + return true; +} + +/* static */ +bool WasmInstanceObject::construct(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + Log(cx, "sync new Instance() started"); + + if (!ThrowIfNotConstructing(cx, args, "Instance")) { + return false; + } + + if (!args.requireAtLeast(cx, "WebAssembly.Instance", 1)) { + return false; + } + + const Module* module; + if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), &module)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_MOD_ARG); + return false; + } + + RootedObject importObj(cx); + if (!GetImportArg(cx, args, &importObj)) { + return false; + } + + RootedObject proto( + cx, GetWasmConstructorPrototype(cx, args, JSProto_WasmInstance)); + if (!proto) { + ReportOutOfMemory(cx); + return false; + } + + Rooted<ImportValues> imports(cx); + if (!GetImports(cx, *module, importObj, imports.address())) { + return false; + } + + Rooted<WasmInstanceObject*> instanceObj(cx); + if (!module->instantiate(cx, imports.get(), proto, &instanceObj)) { + return false; + } + + Log(cx, "sync new Instance() succeeded"); + + args.rval().setObject(*instanceObj); + return true; +} + +Instance& WasmInstanceObject::instance() const { + MOZ_ASSERT(!isNewborn()); + return *(Instance*)getReservedSlot(INSTANCE_SLOT).toPrivate(); +} + +JSObject& WasmInstanceObject::exportsObj() const { + return getReservedSlot(EXPORTS_OBJ_SLOT).toObject(); +} + +WasmInstanceObject::ExportMap& WasmInstanceObject::exports() const { + return *(ExportMap*)getReservedSlot(EXPORTS_SLOT).toPrivate(); +} + +WasmInstanceObject::UnspecifiedScopeMap& WasmInstanceObject::scopes() const { + return *(UnspecifiedScopeMap*)(getReservedSlot(SCOPES_SLOT).toPrivate()); +} + +WasmInstanceObject::GlobalObjectVector& WasmInstanceObject::indirectGlobals() + const { + return *(GlobalObjectVector*)getReservedSlot(GLOBALS_SLOT).toPrivate(); +} + +static bool WasmCall(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + RootedFunction callee(cx, &args.callee().as<JSFunction>()); + + Instance& instance = ExportedFunctionToInstance(callee); + uint32_t funcIndex = ExportedFunctionToFuncIndex(callee); + return instance.callExport(cx, funcIndex, args); +} + +/* + * [SMDOC] Exported wasm functions and the jit-entry stubs + * + * ## The kinds of exported functions + * + * There are several kinds of exported wasm functions. /Explicitly/ exported + * functions are: + * + * - any wasm function exported via the export section + * - any asm.js export + * - the module start function + * + * There are also /implicitly/ exported functions, these are the functions whose + * indices in the module are referenced outside the code segment, eg, in element + * segments and in global initializers. + * + * ## Wasm functions as JSFunctions + * + * Any exported function can be manipulated by JS and wasm code, and to both the + * exported function is represented as a JSFunction. To JS, that means that the + * function can be called in the same way as any other JSFunction. To Wasm, it + * means that the function is a reference with the same representation as + * externref. + * + * However, the JSFunction object is created only when the function value is + * actually exposed to JS the first time. The creation is performed by + * getExportedFunction(), below, as follows: + * + * - a function exported via the export section (or from asm.js) is created + * when the export object is created, which happens at instantiation time. + * + * - a function implicitly exported via a table is created when the table + * element is read (by JS or wasm) and a function value is needed to + * represent that value. Functions stored in tables by initializers have a + * special representation that does not require the function object to be + * created. + * + * - a function implicitly exported via a global initializer is created when + * the global is initialized. + * + * - a function referenced from a ref.func instruction in code is created when + * that instruction is executed the first time. + * + * The JSFunction representing a wasm function never changes: every reference to + * the wasm function that exposes the JSFunction gets the same JSFunction. In + * particular, imported functions already have a JSFunction representation (from + * JS or from their home module), and will be exposed using that representation. + * + * The mapping from a wasm function to its JSFunction is instance-specific, and + * held in a hashmap in the instance. If a module is shared across multiple + * instances, possibly in multiple threads, each instance will have its own + * JSFunction representing the wasm function. + * + * ## Stubs -- interpreter, eager, lazy, provisional, and absent + * + * While a Wasm exported function is just a JSFunction, the internal wasm ABI is + * neither the C++ ABI nor the JS JIT ABI, so there needs to be an extra step + * when C++ or JS JIT code calls wasm code. For this, execution passes through + * a stub that is adapted to both the JS caller and the wasm callee. + * + * ### Interpreter stubs and jit-entry stubs + * + * When JS interpreted code calls a wasm function, we end up in + * Instance::callExport() to execute the call. This function must enter wasm, + * and to do this it uses a stub that is specific to the wasm function (see + * GenerateInterpEntry) that is callable with the C++ interpreter ABI and which + * will convert arguments as necessary and enter compiled wasm code. + * + * The interpreter stub is created eagerly, when the module is compiled. + * + * However, the interpreter call path is slow, and when JS jitted code calls + * wasm we want to do better. In this case, there is a different, optimized + * stub that is to be invoked, and it uses the JIT ABI. This is the jit-entry + * stub for the function. Jitted code will call a wasm function's jit-entry + * stub to invoke the function with the JIT ABI. The stub will adapt the call + * to the wasm ABI. + * + * Some jit-entry stubs are created eagerly and some are created lazily. + * + * ### Eager jit-entry stubs + * + * The explicitly exported functions have stubs created for them eagerly. Eager + * stubs are created with their tier when the module is compiled, see + * ModuleGenerator::finishCodeTier(), which calls wasm::GenerateStubs(), which + * generates stubs for functions with eager stubs. + * + * An eager stub for tier-1 is upgraded to tier-2 if the module tiers up, see + * below. + * + * ### Lazy jit-entry stubs + * + * Stubs are created lazily for all implicitly exported functions. These + * functions may flow out to JS, but will only need a stub if they are ever + * called from jitted code. (That's true for explicitly exported functions too, + * but for them the presumption is that they will be called.) + * + * Lazy stubs are created only when they are needed, and they are /doubly/ lazy, + * see getExportedFunction(), below: A function implicitly exported via a table + * or global may be manipulated eagerly by host code without actually being + * called (maybe ever), so we do not generate a lazy stub when the function + * object escapes to JS, but instead delay stub generation until the function is + * actually called. + * + * ### The provisional lazy jit-entry stub + * + * However, JS baseline compilation needs to have a stub to start with in order + * to allow it to attach CacheIR data to the call (or it deoptimizes the call as + * a C++ call). Thus when the JSFunction for the wasm export is retrieved by JS + * code, a /provisional/ lazy jit-entry stub is associated with the function. + * The stub will invoke the wasm function on the slow interpreter path via + * callExport - if the function is ever called - and will cause a fast jit-entry + * stub to be created at the time of the call. The provisional lazy stub is + * shared globally, it contains no function-specific or context-specific data. + * + * Thus, the final lazy jit-entry stubs are eventually created by + * Instance::callExport, when a call is routed through it on the slow path for + * any of the reasons given above. + * + * ### Absent jit-entry stubs + * + * Some functions never get jit-entry stubs. The predicate canHaveJitEntry() + * determines if a wasm function gets a stub, and it will deny this if the + * function's signature exposes non-JS-compatible types (such as v128) or if + * stub optimization has been disabled by a jit option. Calls to these + * functions will continue to go via callExport and use the slow interpreter + * stub. + * + * ## The jit-entry jump table + * + * The mapping from the exported function to its jit-entry stub is implemented + * by the jit-entry jump table in the JumpTables object (see WasmCode.h). The + * jit-entry jump table entry for a function holds a stub that the jit can call + * to perform fast calls. + * + * While there is a single contiguous jump table, it has two logical sections: + * one for eager stubs, and one for lazy stubs. These sections are initialized + * and updated separately, using logic that is specific to each section. + * + * The value of the table element for an eager stub is a pointer to the stub + * code in the current tier. The pointer is installed just after the creation + * of the stub, before any code in the module is executed. If the module later + * tiers up, the eager jit-entry stub for tier-1 code is replaced by one for + * tier-2 code, see the next section. + * + * Initially the value of the jump table element for a lazy stub is null. + * + * If the function is retrieved by JS (by getExportedFunction()) and is not + * barred from having a jit-entry, then the stub is upgraded to the shared + * provisional lazy jit-entry stub. This upgrade happens to be racy if the + * module is shared, and so the update is atomic and only happens if the entry + * is already null. Since the provisional lazy stub is shared, this is fine; if + * several threads try to upgrade at the same time, it is to the same shared + * value. + * + * If the retrieved function is later invoked (via callExport()), the stub is + * upgraded to an actual jit-entry stub for the current code tier, again if the + * function is allowed to have a jit-entry. This is not racy -- though multiple + * threads can be trying to create a jit-entry stub at the same time, they do so + * under a lock and only the first to take the lock will be allowed to create a + * stub, the others will reuse the first-installed stub. + * + * If the module later tiers up, the lazy jit-entry stub for tier-1 code (if it + * exists) is replaced by one for tier-2 code, see the next section. + * + * (Note, the InterpEntry stub is never stored in the jit-entry table, as it + * uses the C++ ABI, not the JIT ABI. It is accessible through the + * FunctionEntry.) + * + * ### Interaction of the jit-entry jump table and tiering + * + * (For general info about tiering, see the comment in WasmCompile.cpp.) + * + * The jit-entry stub, whether eager or lazy, is specific to a code tier - a + * stub will invoke the code for its function for the tier. When we tier up, + * new jit-entry stubs must be created that reference tier-2 code, and must then + * be patched into the jit-entry table. The complication here is that, since + * the jump table is shared with its code between instances on multiple threads, + * tier-1 code is running on other threads and new tier-1 specific jit-entry + * stubs may be created concurrently with trying to create the tier-2 stubs on + * the thread that performs the tiering-up. Indeed, there may also be + * concurrent attempts to upgrade null jit-entries to the provisional lazy stub. + * + * Eager stubs: + * + * - Eager stubs for tier-2 code are patched in racily by Module::finishTier2() + * along with code pointers for tiering; nothing conflicts with these writes. + * + * Lazy stubs: + * + * - An upgrade from a null entry to a lazy provisional stub is atomic and can + * only happen if the entry is null, and it only happens in + * getExportedFunction(). No lazy provisional stub will be installed if + * there's another stub present. + * + * - The lazy tier-appropriate stub is installed by callExport() (really by + * EnsureEntryStubs()) during the first invocation of the exported function + * that reaches callExport(). That invocation must be from within JS, and so + * the jit-entry element can't be null, because a prior getExportedFunction() + * will have ensured that it is not: the lazy provisional stub will have been + * installed. Hence the installing of the lazy tier-appropriate stub does + * not race with the installing of the lazy provisional stub. + * + * - A lazy tier-1 stub is upgraded to a lazy tier-2 stub by + * Module::finishTier2(). The upgrade needs to ensure that all tier-1 stubs + * are upgraded, and that once the upgrade is finished, callExport() will + * only create tier-2 lazy stubs. (This upgrading does not upgrade lazy + * provisional stubs or absent stubs.) + * + * The locking protocol ensuring that all stubs are upgraded properly and + * that the system switches to creating tier-2 stubs is implemented in + * Module::finishTier2() and EnsureEntryStubs(): + * + * There are two locks, one per code tier. + * + * EnsureEntryStubs() is attempting to create a tier-appropriate lazy stub, + * so it takes the lock for the current best tier, checks to see if there is + * a stub, and exits if there is. If the tier changed racily it takes the + * other lock too, since that is now the lock for the best tier. Then it + * creates the stub, installs it, and releases the locks. Thus at most one + * stub per tier can be created at a time. + * + * Module::finishTier2() takes both locks (tier-1 before tier-2), thus + * preventing EnsureEntryStubs() from creating stubs while stub upgrading is + * going on, and itself waiting until EnsureEntryStubs() is not active. Once + * it has both locks, it upgrades all lazy stubs and makes tier-2 the new + * best tier. Should EnsureEntryStubs subsequently enter, it will find that + * a stub already exists at tier-2 and will exit early. + * + * (It would seem that the locking protocol could be simplified a little by + * having only one lock, hanging off the Code object, or by unconditionally + * taking both locks in EnsureEntryStubs(). However, in some cases where we + * acquire a lock the Code object is not readily available, so plumbing would + * have to be added, and in EnsureEntryStubs(), there are sometimes not two code + * tiers.) + * + * ## Stub lifetimes and serialization + * + * Eager jit-entry stub code, along with stub code for import functions, is + * serialized along with the tier-2 code for the module. + * + * Lazy stub code and thunks for builtin functions (including the provisional + * lazy jit-entry stub) are never serialized. + */ + +/* static */ +bool WasmInstanceObject::getExportedFunction( + JSContext* cx, Handle<WasmInstanceObject*> instanceObj, uint32_t funcIndex, + MutableHandleFunction fun) { + if (ExportMap::Ptr p = instanceObj->exports().lookup(funcIndex)) { + fun.set(p->value()); + return true; + } + + const Instance& instance = instanceObj->instance(); + const FuncExport& funcExport = + instance.metadata(instance.code().bestTier()).lookupFuncExport(funcIndex); + const FuncType& funcType = instance.metadata().getFuncExportType(funcExport); + unsigned numArgs = funcType.args().length(); + + if (instance.isAsmJS()) { + // asm.js needs to act like a normal JS function which means having the + // name from the original source and being callable as a constructor. + Rooted<JSAtom*> name(cx, instance.getFuncDisplayAtom(cx, funcIndex)); + if (!name) { + return false; + } + fun.set(NewNativeConstructor(cx, WasmCall, numArgs, name, + gc::AllocKind::FUNCTION_EXTENDED, + TenuredObject, FunctionFlags::ASMJS_CTOR)); + if (!fun) { + return false; + } + + // asm.js does not support jit entries. + fun->setWasmFuncIndex(funcIndex); + } else { + Rooted<JSAtom*> name(cx, NumberToAtom(cx, funcIndex)); + if (!name) { + return false; + } + RootedObject proto(cx); +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmFunction); + if (!proto) { + return false; + } +#endif + fun.set(NewFunctionWithProto( + cx, WasmCall, numArgs, FunctionFlags::WASM, nullptr, name, proto, + gc::AllocKind::FUNCTION_EXTENDED, TenuredObject)); + if (!fun) { + return false; + } + + // Some applications eagerly access all table elements which currently + // triggers worst-case behavior for lazy stubs, since each will allocate a + // separate 4kb code page. Most eagerly-accessed functions are not called, + // so use a shared, provisional (and slow) lazy stub as JitEntry and wait + // until Instance::callExport() to create the fast entry stubs. + if (funcType.canHaveJitEntry()) { + if (!funcExport.hasEagerStubs()) { + if (!EnsureBuiltinThunksInitialized()) { + return false; + } + void* provisionalLazyJitEntryStub = ProvisionalLazyJitEntryStub(); + MOZ_ASSERT(provisionalLazyJitEntryStub); + instance.code().setJitEntryIfNull(funcIndex, + provisionalLazyJitEntryStub); + } + fun->setWasmJitEntry(instance.code().getAddressOfJitEntry(funcIndex)); + } else { + fun->setWasmFuncIndex(funcIndex); + } + } + + fun->setExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT, + PrivateValue(const_cast<Instance*>(&instance))); + + const CodeTier& codeTier = + instance.code().codeTier(instance.code().bestTier()); + const CodeRange& codeRange = codeTier.metadata().codeRange(funcExport); + + fun->setExtendedSlot(FunctionExtended::WASM_FUNC_UNCHECKED_ENTRY_SLOT, + PrivateValue(codeTier.segment().base() + + codeRange.funcUncheckedCallEntry())); + + if (!instanceObj->exports().putNew(funcIndex, fun)) { + ReportOutOfMemory(cx); + return false; + } + + return true; +} + +const CodeRange& WasmInstanceObject::getExportedFunctionCodeRange( + JSFunction* fun, Tier tier) { + uint32_t funcIndex = ExportedFunctionToFuncIndex(fun); + MOZ_ASSERT(exports().lookup(funcIndex)->value() == fun); + const MetadataTier& metadata = instance().metadata(tier); + return metadata.codeRange(metadata.lookupFuncExport(funcIndex)); +} + +/* static */ +WasmInstanceScope* WasmInstanceObject::getScope( + JSContext* cx, Handle<WasmInstanceObject*> instanceObj) { + if (!instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT).isUndefined()) { + return (WasmInstanceScope*)instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT) + .toGCThing(); + } + + Rooted<WasmInstanceScope*> instanceScope( + cx, WasmInstanceScope::create(cx, instanceObj)); + if (!instanceScope) { + return nullptr; + } + + instanceObj->setReservedSlot(INSTANCE_SCOPE_SLOT, + PrivateGCThingValue(instanceScope)); + + return instanceScope; +} + +/* static */ +WasmFunctionScope* WasmInstanceObject::getFunctionScope( + JSContext* cx, Handle<WasmInstanceObject*> instanceObj, + uint32_t funcIndex) { + if (auto p = + instanceObj->scopes().asWasmFunctionScopeMap().lookup(funcIndex)) { + return p->value(); + } + + Rooted<WasmInstanceScope*> instanceScope( + cx, WasmInstanceObject::getScope(cx, instanceObj)); + if (!instanceScope) { + return nullptr; + } + + Rooted<WasmFunctionScope*> funcScope( + cx, WasmFunctionScope::create(cx, instanceScope, funcIndex)); + if (!funcScope) { + return nullptr; + } + + if (!instanceObj->scopes().asWasmFunctionScopeMap().putNew(funcIndex, + funcScope)) { + ReportOutOfMemory(cx); + return nullptr; + } + + return funcScope; +} + +bool wasm::IsWasmExportedFunction(JSFunction* fun) { + return fun->kind() == FunctionFlags::Wasm; +} + +Instance& wasm::ExportedFunctionToInstance(JSFunction* fun) { + return fun->wasmInstance(); +} + +WasmInstanceObject* wasm::ExportedFunctionToInstanceObject(JSFunction* fun) { + return fun->wasmInstance().object(); +} + +uint32_t wasm::ExportedFunctionToFuncIndex(JSFunction* fun) { + return fun->wasmInstance().code().getFuncIndex(fun); +} + +// ============================================================================ +// WebAssembly.Memory class and methods + +const JSClassOps WasmMemoryObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + WasmMemoryObject::finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +const JSClass WasmMemoryObject::class_ = { + "WebAssembly.Memory", + JSCLASS_DELAY_METADATA_BUILDER | + JSCLASS_HAS_RESERVED_SLOTS(WasmMemoryObject::RESERVED_SLOTS) | + JSCLASS_FOREGROUND_FINALIZE, + &WasmMemoryObject::classOps_, &WasmMemoryObject::classSpec_}; + +const JSClass& WasmMemoryObject::protoClass_ = PlainObject::class_; + +static constexpr char WasmMemoryName[] = "Memory"; + +static JSObject* CreateWasmMemoryPrototype(JSContext* cx, JSProtoKey key) { + RootedObject proto(cx, GlobalObject::createBlankPrototype( + cx, cx->global(), &WasmMemoryObject::protoClass_)); + if (!proto) { + return nullptr; + } + if (MemoryControlAvailable(cx)) { + if (!JS_DefineFunctions(cx, proto, + WasmMemoryObject::memoryControlMethods)) { + return nullptr; + } + } + return proto; +} + +const ClassSpec WasmMemoryObject::classSpec_ = { + CreateWasmConstructor<WasmMemoryObject, WasmMemoryName>, + CreateWasmMemoryPrototype, + WasmMemoryObject::static_methods, + nullptr, + WasmMemoryObject::methods, + WasmMemoryObject::properties, + nullptr, + ClassSpec::DontDefineConstructor}; + +/* static */ +void WasmMemoryObject::finalize(JS::GCContext* gcx, JSObject* obj) { + WasmMemoryObject& memory = obj->as<WasmMemoryObject>(); + if (memory.hasObservers()) { + gcx->delete_(obj, &memory.observers(), MemoryUse::WasmMemoryObservers); + } +} + +/* static */ +WasmMemoryObject* WasmMemoryObject::create( + JSContext* cx, HandleArrayBufferObjectMaybeShared buffer, bool isHuge, + HandleObject proto) { + AutoSetNewObjectMetadata metadata(cx); + auto* obj = NewObjectWithGivenProto<WasmMemoryObject>(cx, proto); + if (!obj) { + return nullptr; + } + + obj->initReservedSlot(BUFFER_SLOT, ObjectValue(*buffer)); + obj->initReservedSlot(ISHUGE_SLOT, BooleanValue(isHuge)); + MOZ_ASSERT(!obj->hasObservers()); + + return obj; +} + +/* static */ +bool WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "Memory")) { + return false; + } + + if (!args.requireAtLeast(cx, "WebAssembly.Memory", 1)) { + return false; + } + + if (!args.get(0).isObject()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_DESC_ARG, "memory"); + return false; + } + + RootedObject obj(cx, &args[0].toObject()); + Limits limits; + if (!GetLimits(cx, obj, LimitsKind::Memory, &limits) || + !CheckLimits(cx, MaxMemoryLimitField(limits.indexType), + LimitsKind::Memory, &limits)) { + return false; + } + + if (Pages(limits.initial) > MaxMemoryPages(limits.indexType)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_MEM_IMP_LIMIT); + return false; + } + MemoryDesc memory(limits); + + RootedArrayBufferObjectMaybeShared buffer(cx); + if (!CreateWasmBuffer(cx, memory, &buffer)) { + return false; + } + + RootedObject proto(cx, + GetWasmConstructorPrototype(cx, args, JSProto_WasmMemory)); + if (!proto) { + ReportOutOfMemory(cx); + return false; + } + + Rooted<WasmMemoryObject*> memoryObj( + cx, WasmMemoryObject::create( + cx, buffer, IsHugeMemoryEnabled(limits.indexType), proto)); + if (!memoryObj) { + return false; + } + + args.rval().setObject(*memoryObj); + return true; +} + +static bool IsMemory(HandleValue v) { + return v.isObject() && v.toObject().is<WasmMemoryObject>(); +} + +/* static */ +bool WasmMemoryObject::bufferGetterImpl(JSContext* cx, const CallArgs& args) { + Rooted<WasmMemoryObject*> memoryObj( + cx, &args.thisv().toObject().as<WasmMemoryObject>()); + RootedArrayBufferObjectMaybeShared buffer(cx, &memoryObj->buffer()); + + if (memoryObj->isShared()) { + size_t memoryLength = memoryObj->volatileMemoryLength(); + MOZ_ASSERT(memoryLength >= buffer->byteLength()); + + if (memoryLength > buffer->byteLength()) { + RootedSharedArrayBufferObject newBuffer( + cx, SharedArrayBufferObject::New( + cx, memoryObj->sharedArrayRawBuffer(), memoryLength)); + if (!newBuffer) { + return false; + } + // OK to addReference after we try to allocate because the memoryObj + // keeps the rawBuffer alive. + if (!memoryObj->sharedArrayRawBuffer()->addReference()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_SC_SAB_REFCNT_OFLO); + return false; + } + buffer = newBuffer; + memoryObj->setReservedSlot(BUFFER_SLOT, ObjectValue(*newBuffer)); + } + } + + args.rval().setObject(*buffer); + return true; +} + +/* static */ +bool WasmMemoryObject::bufferGetter(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsMemory, bufferGetterImpl>(cx, args); +} + +const JSPropertySpec WasmMemoryObject::properties[] = { + JS_PSG("buffer", WasmMemoryObject::bufferGetter, JSPROP_ENUMERATE), + JS_STRING_SYM_PS(toStringTag, "WebAssembly.Memory", JSPROP_READONLY), + JS_PS_END}; + +/* static */ +bool WasmMemoryObject::growImpl(JSContext* cx, const CallArgs& args) { + Rooted<WasmMemoryObject*> memory( + cx, &args.thisv().toObject().as<WasmMemoryObject>()); + + if (!args.requireAtLeast(cx, "WebAssembly.Memory.grow", 1)) { + return false; + } + + uint32_t delta; + if (!EnforceRangeU32(cx, args.get(0), "Memory", "grow delta", &delta)) { + return false; + } + + uint32_t ret = grow(memory, delta, cx); + + if (ret == uint32_t(-1)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW, + "memory"); + return false; + } + + args.rval().setInt32(int32_t(ret)); + return true; +} + +/* static */ +bool WasmMemoryObject::grow(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsMemory, growImpl>(cx, args); +} + +/* static */ +bool WasmMemoryObject::discardImpl(JSContext* cx, const CallArgs& args) { + Rooted<WasmMemoryObject*> memory( + cx, &args.thisv().toObject().as<WasmMemoryObject>()); + + if (!args.requireAtLeast(cx, "WebAssembly.Memory.discard", 2)) { + return false; + } + + uint64_t byteOffset; + if (!EnforceRangeU64(cx, args.get(0), "Memory", "byte offset", &byteOffset)) { + return false; + } + + uint64_t byteLen; + if (!EnforceRangeU64(cx, args.get(1), "Memory", "length", &byteLen)) { + return false; + } + + if (byteOffset % wasm::PageSize != 0 || byteLen % wasm::PageSize != 0) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_UNALIGNED_ACCESS); + return false; + } + + if (!wasm::MemoryBoundsCheck(byteOffset, byteLen, + memory->volatileMemoryLength())) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_OUT_OF_BOUNDS); + return false; + } + + discard(memory, byteOffset, byteLen, cx); + + args.rval().setUndefined(); + return true; +} + +/* static */ +bool WasmMemoryObject::discard(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsMemory, discardImpl>(cx, args); +} + +const JSFunctionSpec WasmMemoryObject::methods[] = { +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + JS_FN("type", WasmMemoryObject::type, 0, JSPROP_ENUMERATE), +#endif + JS_FN("grow", WasmMemoryObject::grow, 1, JSPROP_ENUMERATE), JS_FS_END}; + +const JSFunctionSpec WasmMemoryObject::memoryControlMethods[] = { + JS_FN("discard", WasmMemoryObject::discard, 2, JSPROP_ENUMERATE), + JS_FS_END}; + +const JSFunctionSpec WasmMemoryObject::static_methods[] = {JS_FS_END}; + +ArrayBufferObjectMaybeShared& WasmMemoryObject::buffer() const { + return getReservedSlot(BUFFER_SLOT) + .toObject() + .as<ArrayBufferObjectMaybeShared>(); +} + +WasmSharedArrayRawBuffer* WasmMemoryObject::sharedArrayRawBuffer() const { + MOZ_ASSERT(isShared()); + return buffer().as<SharedArrayBufferObject>().rawWasmBufferObject(); +} + +#ifdef ENABLE_WASM_TYPE_REFLECTIONS +bool WasmMemoryObject::typeImpl(JSContext* cx, const CallArgs& args) { + Rooted<WasmMemoryObject*> memoryObj( + cx, &args.thisv().toObject().as<WasmMemoryObject>()); + RootedObject typeObj( + cx, MemoryTypeToObject(cx, memoryObj->isShared(), memoryObj->indexType(), + memoryObj->volatilePages(), + memoryObj->sourceMaxPages())); + if (!typeObj) { + return false; + } + args.rval().setObject(*typeObj); + return true; +} + +bool WasmMemoryObject::type(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsMemory, typeImpl>(cx, args); +} +#endif + +size_t WasmMemoryObject::volatileMemoryLength() const { + if (isShared()) { + return sharedArrayRawBuffer()->volatileByteLength(); + } + return buffer().byteLength(); +} + +wasm::Pages WasmMemoryObject::volatilePages() const { + if (isShared()) { + return sharedArrayRawBuffer()->volatileWasmPages(); + } + return buffer().wasmPages(); +} + +wasm::Pages WasmMemoryObject::clampedMaxPages() const { + if (isShared()) { + return sharedArrayRawBuffer()->wasmClampedMaxPages(); + } + return buffer().wasmClampedMaxPages(); +} + +Maybe<wasm::Pages> WasmMemoryObject::sourceMaxPages() const { + if (isShared()) { + return Some(sharedArrayRawBuffer()->wasmSourceMaxPages()); + } + return buffer().wasmSourceMaxPages(); +} + +wasm::IndexType WasmMemoryObject::indexType() const { + if (isShared()) { + return sharedArrayRawBuffer()->wasmIndexType(); + } + return buffer().wasmIndexType(); +} + +bool WasmMemoryObject::isShared() const { + return buffer().is<SharedArrayBufferObject>(); +} + +bool WasmMemoryObject::hasObservers() const { + return !getReservedSlot(OBSERVERS_SLOT).isUndefined(); +} + +WasmMemoryObject::InstanceSet& WasmMemoryObject::observers() const { + MOZ_ASSERT(hasObservers()); + return *reinterpret_cast<InstanceSet*>( + getReservedSlot(OBSERVERS_SLOT).toPrivate()); +} + +WasmMemoryObject::InstanceSet* WasmMemoryObject::getOrCreateObservers( + JSContext* cx) { + if (!hasObservers()) { + auto observers = MakeUnique<InstanceSet>(cx->zone(), cx->zone()); + if (!observers) { + ReportOutOfMemory(cx); + return nullptr; + } + + InitReservedSlot(this, OBSERVERS_SLOT, observers.release(), + MemoryUse::WasmMemoryObservers); + } + + return &observers(); +} + +bool WasmMemoryObject::isHuge() const { + return getReservedSlot(ISHUGE_SLOT).toBoolean(); +} + +bool WasmMemoryObject::movingGrowable() const { + return !isHuge() && !buffer().wasmSourceMaxPages(); +} + +size_t WasmMemoryObject::boundsCheckLimit() const { + if (!buffer().isWasm() || isHuge()) { + return buffer().byteLength(); + } + size_t mappedSize = buffer().wasmMappedSize(); +#if !defined(JS_64BIT) + // See clamping performed in CreateSpecificWasmBuffer(). On 32-bit systems + // we do not want to overflow a uint32_t. For the other 64-bit compilers, + // all constraints are implied by the largest accepted value for a memory's + // max field. + MOZ_ASSERT(mappedSize < UINT32_MAX); +#endif + MOZ_ASSERT(mappedSize % wasm::PageSize == 0); + MOZ_ASSERT(mappedSize >= wasm::GuardSize); + MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize - wasm::GuardSize)); + size_t limit = mappedSize - wasm::GuardSize; + MOZ_ASSERT(limit <= MaxMemoryBoundsCheckLimit(indexType())); + return limit; +} + +bool WasmMemoryObject::addMovingGrowObserver(JSContext* cx, + WasmInstanceObject* instance) { + MOZ_ASSERT(movingGrowable()); + + InstanceSet* observers = getOrCreateObservers(cx); + if (!observers) { + return false; + } + + if (!observers->putNew(instance)) { + ReportOutOfMemory(cx); + return false; + } + + return true; +} + +/* static */ +uint64_t WasmMemoryObject::growShared(Handle<WasmMemoryObject*> memory, + uint64_t delta) { + WasmSharedArrayRawBuffer* rawBuf = memory->sharedArrayRawBuffer(); + WasmSharedArrayRawBuffer::Lock lock(rawBuf); + + Pages oldNumPages = rawBuf->volatileWasmPages(); + Pages newPages = oldNumPages; + if (!newPages.checkedIncrement(Pages(delta))) { + return uint64_t(int64_t(-1)); + } + + if (!rawBuf->wasmGrowToPagesInPlace(lock, memory->indexType(), newPages)) { + return uint64_t(int64_t(-1)); + } + // New buffer objects will be created lazily in all agents (including in + // this agent) by bufferGetterImpl, above, so no more work to do here. + + return oldNumPages.value(); +} + +/* static */ +uint64_t WasmMemoryObject::grow(Handle<WasmMemoryObject*> memory, + uint64_t delta, JSContext* cx) { + if (memory->isShared()) { + return growShared(memory, delta); + } + + RootedArrayBufferObject oldBuf(cx, &memory->buffer().as<ArrayBufferObject>()); + +#if !defined(JS_64BIT) + // TODO (large ArrayBuffer): See more information at the definition of + // MaxMemoryBytes(). + MOZ_ASSERT(MaxMemoryBytes(memory->indexType()) <= UINT32_MAX, + "Avoid 32-bit overflows"); +#endif + + Pages oldNumPages = oldBuf->wasmPages(); + Pages newPages = oldNumPages; + if (!newPages.checkedIncrement(Pages(delta))) { + return uint64_t(int64_t(-1)); + } + + RootedArrayBufferObject newBuf(cx); + + if (memory->movingGrowable()) { + MOZ_ASSERT(!memory->isHuge()); + if (!ArrayBufferObject::wasmMovingGrowToPages(memory->indexType(), newPages, + oldBuf, &newBuf, cx)) { + return uint64_t(int64_t(-1)); + } + } else if (!ArrayBufferObject::wasmGrowToPagesInPlace( + memory->indexType(), newPages, oldBuf, &newBuf, cx)) { + return uint64_t(int64_t(-1)); + } + + memory->setReservedSlot(BUFFER_SLOT, ObjectValue(*newBuf)); + + // Only notify moving-grow-observers after the BUFFER_SLOT has been updated + // since observers will call buffer(). + if (memory->hasObservers()) { + for (InstanceSet::Range r = memory->observers().all(); !r.empty(); + r.popFront()) { + r.front()->instance().onMovingGrowMemory(); + } + } + + return oldNumPages.value(); +} + +/* static */ +void WasmMemoryObject::discard(Handle<WasmMemoryObject*> memory, + uint64_t byteOffset, uint64_t byteLen, + JSContext* cx) { + if (memory->isShared()) { + RootedSharedArrayBufferObject buf( + cx, &memory->buffer().as<SharedArrayBufferObject>()); + SharedArrayBufferObject::wasmDiscard(buf, byteOffset, byteLen); + } else { + RootedArrayBufferObject buf(cx, &memory->buffer().as<ArrayBufferObject>()); + ArrayBufferObject::wasmDiscard(buf, byteOffset, byteLen); + } +} + +bool js::wasm::IsSharedWasmMemoryObject(JSObject* obj) { + WasmMemoryObject* mobj = obj->maybeUnwrapIf<WasmMemoryObject>(); + return mobj && mobj->isShared(); +} + +// ============================================================================ +// WebAssembly.Table class and methods + +const JSClassOps WasmTableObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + WasmTableObject::finalize, // finalize + nullptr, // call + nullptr, // construct + WasmTableObject::trace, // trace +}; + +const JSClass WasmTableObject::class_ = { + "WebAssembly.Table", + JSCLASS_DELAY_METADATA_BUILDER | + JSCLASS_HAS_RESERVED_SLOTS(WasmTableObject::RESERVED_SLOTS) | + JSCLASS_FOREGROUND_FINALIZE, + &WasmTableObject::classOps_, &WasmTableObject::classSpec_}; + +const JSClass& WasmTableObject::protoClass_ = PlainObject::class_; + +static constexpr char WasmTableName[] = "Table"; + +const ClassSpec WasmTableObject::classSpec_ = { + CreateWasmConstructor<WasmTableObject, WasmTableName>, + GenericCreatePrototype<WasmTableObject>, + WasmTableObject::static_methods, + nullptr, + WasmTableObject::methods, + WasmTableObject::properties, + nullptr, + ClassSpec::DontDefineConstructor}; + +bool WasmTableObject::isNewborn() const { + MOZ_ASSERT(is<WasmTableObject>()); + return getReservedSlot(TABLE_SLOT).isUndefined(); +} + +/* static */ +void WasmTableObject::finalize(JS::GCContext* gcx, JSObject* obj) { + WasmTableObject& tableObj = obj->as<WasmTableObject>(); + if (!tableObj.isNewborn()) { + auto& table = tableObj.table(); + gcx->release(obj, &table, table.gcMallocBytes(), MemoryUse::WasmTableTable); + } +} + +/* static */ +void WasmTableObject::trace(JSTracer* trc, JSObject* obj) { + WasmTableObject& tableObj = obj->as<WasmTableObject>(); + if (!tableObj.isNewborn()) { + tableObj.table().tracePrivate(trc); + } +} + +// Return the JS value to use when a parameter to a function requiring a table +// value is omitted. An implementation of [1]. +// +// [1] +// https://webassembly.github.io/reference-types/js-api/index.html#defaultvalue +static Value RefTypeDefautValue(wasm::RefType tableType) { + return tableType.isExtern() ? UndefinedValue() : NullValue(); +} + +static bool CheckRefTypeValue(JSContext* cx, wasm::RefType type, + HandleValue value) { + RootedFunction fun(cx); + RootedAnyRef any(cx, AnyRef::null()); + + return CheckRefType(cx, type, value, &fun, &any); +} + +/* static */ +WasmTableObject* WasmTableObject::create(JSContext* cx, uint32_t initialLength, + Maybe<uint32_t> maximumLength, + wasm::RefType tableType, + HandleObject proto) { + AutoSetNewObjectMetadata metadata(cx); + Rooted<WasmTableObject*> obj( + cx, NewObjectWithGivenProto<WasmTableObject>(cx, proto)); + if (!obj) { + return nullptr; + } + + MOZ_ASSERT(obj->isNewborn()); + + TableDesc td(tableType, initialLength, maximumLength, Nothing(), + /*isAsmJS*/ false, + /*isImported=*/true, /*isExported=*/true); + + SharedTable table = Table::create(cx, td, obj); + if (!table) { + return nullptr; + } + + size_t size = table->gcMallocBytes(); + InitReservedSlot(obj, TABLE_SLOT, table.forget().take(), size, + MemoryUse::WasmTableTable); + + MOZ_ASSERT(!obj->isNewborn()); + return obj; +} + +/* static */ +bool WasmTableObject::construct(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "Table")) { + return false; + } + + if (!args.requireAtLeast(cx, "WebAssembly.Table", 1)) { + return false; + } + + if (!args.get(0).isObject()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_DESC_ARG, "table"); + return false; + } + + RootedObject obj(cx, &args[0].toObject()); + + JSAtom* elementAtom = Atomize(cx, "element", strlen("element")); + if (!elementAtom) { + return false; + } + RootedId elementId(cx, AtomToId(elementAtom)); + + RootedValue elementVal(cx); + if (!GetProperty(cx, obj, obj, elementId, &elementVal)) { + return false; + } + + RefType tableType; + if (!ToRefType(cx, elementVal, &tableType)) { + return false; + } + + Limits limits; + if (!GetLimits(cx, obj, LimitsKind::Table, &limits) || + !CheckLimits(cx, MaxTableLimitField, LimitsKind::Table, &limits)) { + return false; + } + + // Converting limits for a table only supports i32 + MOZ_ASSERT(limits.indexType == IndexType::I32); + + if (limits.initial > MaxTableLength) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_TABLE_IMP_LIMIT); + return false; + } + + RootedObject proto(cx, + GetWasmConstructorPrototype(cx, args, JSProto_WasmTable)); + if (!proto) { + ReportOutOfMemory(cx); + return false; + } + + // The rest of the runtime expects table limits to be within a 32-bit range. + static_assert(MaxTableLimitField <= UINT32_MAX, "invariant"); + uint32_t initialLength = uint32_t(limits.initial); + Maybe<uint32_t> maximumLength; + if (limits.maximum) { + maximumLength = Some(uint32_t(*limits.maximum)); + } + + Rooted<WasmTableObject*> table( + cx, WasmTableObject::create(cx, initialLength, maximumLength, tableType, + proto)); + if (!table) { + return false; + } + + // Initialize the table to a default value + RootedValue initValue( + cx, args.length() < 2 ? RefTypeDefautValue(tableType) : args[1]); + if (!CheckRefTypeValue(cx, tableType, initValue)) { + return false; + } + + // Skip initializing the table if the fill value is null, as that is the + // default value. + if (!initValue.isNull() && + !table->fillRange(cx, 0, initialLength, initValue)) { + return false; + } +#ifdef DEBUG + // Assert that null is the default value of a new table. + if (initValue.isNull()) { + table->table().assertRangeNull(0, initialLength); + } + if (!tableType.isNullable()) { + table->table().assertRangeNotNull(0, initialLength); + } +#endif + + args.rval().setObject(*table); + return true; +} + +static bool IsTable(HandleValue v) { + return v.isObject() && v.toObject().is<WasmTableObject>(); +} + +/* static */ +bool WasmTableObject::lengthGetterImpl(JSContext* cx, const CallArgs& args) { + args.rval().setNumber( + args.thisv().toObject().as<WasmTableObject>().table().length()); + return true; +} + +/* static */ +bool WasmTableObject::lengthGetter(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsTable, lengthGetterImpl>(cx, args); +} + +const JSPropertySpec WasmTableObject::properties[] = { + JS_PSG("length", WasmTableObject::lengthGetter, JSPROP_ENUMERATE), + JS_STRING_SYM_PS(toStringTag, "WebAssembly.Table", JSPROP_READONLY), + JS_PS_END}; + +static bool ToTableIndex(JSContext* cx, HandleValue v, const Table& table, + const char* noun, uint32_t* index) { + if (!EnforceRangeU32(cx, v, "Table", noun, index)) { + return false; + } + + if (*index >= table.length()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_RANGE, "Table", noun); + return false; + } + + return true; +} + +#ifdef ENABLE_WASM_TYPE_REFLECTIONS +/* static */ +bool WasmTableObject::typeImpl(JSContext* cx, const CallArgs& args) { + Table& table = args.thisv().toObject().as<WasmTableObject>().table(); + RootedObject typeObj(cx, TableTypeToObject(cx, table.elemType(), + table.length(), table.maximum())); + if (!typeObj) { + return false; + } + args.rval().setObject(*typeObj); + return true; +} + +/* static */ +bool WasmTableObject::type(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsTable, typeImpl>(cx, args); +} +#endif + +/* static */ +bool WasmTableObject::getImpl(JSContext* cx, const CallArgs& args) { + Rooted<WasmTableObject*> tableObj( + cx, &args.thisv().toObject().as<WasmTableObject>()); + const Table& table = tableObj->table(); + + if (!args.requireAtLeast(cx, "WebAssembly.Table.get", 1)) { + return false; + } + + uint32_t index; + if (!ToTableIndex(cx, args.get(0), table, "get index", &index)) { + return false; + } + + return table.getValue(cx, index, args.rval()); +} + +/* static */ +bool WasmTableObject::get(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsTable, getImpl>(cx, args); +} + +/* static */ +bool WasmTableObject::setImpl(JSContext* cx, const CallArgs& args) { + Rooted<WasmTableObject*> tableObj( + cx, &args.thisv().toObject().as<WasmTableObject>()); + Table& table = tableObj->table(); + + if (!args.requireAtLeast(cx, "WebAssembly.Table.set", 1)) { + return false; + } + + uint32_t index; + if (!ToTableIndex(cx, args.get(0), table, "set index", &index)) { + return false; + } + + RootedValue fillValue( + cx, args.length() < 2 ? RefTypeDefautValue(table.elemType()) : args[1]); + if (!tableObj->fillRange(cx, index, 1, fillValue)) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +/* static */ +bool WasmTableObject::set(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsTable, setImpl>(cx, args); +} + +/* static */ +bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) { + Rooted<WasmTableObject*> tableObj( + cx, &args.thisv().toObject().as<WasmTableObject>()); + Table& table = tableObj->table(); + + if (!args.requireAtLeast(cx, "WebAssembly.Table.grow", 1)) { + return false; + } + + uint32_t delta; + if (!EnforceRangeU32(cx, args.get(0), "Table", "grow delta", &delta)) { + return false; + } + + RootedValue fillValue( + cx, args.length() < 2 ? RefTypeDefautValue(table.elemType()) : args[1]); + if (!CheckRefTypeValue(cx, table.elemType(), fillValue)) { + return false; + } + + uint32_t oldLength = table.grow(delta); + + if (oldLength == uint32_t(-1)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW, + "table"); + return false; + } + + // Skip filling the grown range of the table if the fill value is null, as + // that is the default value. + if (!fillValue.isNull() && + !tableObj->fillRange(cx, oldLength, delta, fillValue)) { + return false; + } +#ifdef DEBUG + // Assert that null is the default value of the grown range. + if (fillValue.isNull()) { + table.assertRangeNull(oldLength, delta); + } + if (!table.elemType().isNullable()) { + table.assertRangeNotNull(oldLength, delta); + } +#endif + + args.rval().setInt32(int32_t(oldLength)); + return true; +} + +/* static */ +bool WasmTableObject::grow(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsTable, growImpl>(cx, args); +} + +const JSFunctionSpec WasmTableObject::methods[] = { +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + JS_FN("type", WasmTableObject::type, 0, JSPROP_ENUMERATE), +#endif + JS_FN("get", WasmTableObject::get, 1, JSPROP_ENUMERATE), + JS_FN("set", WasmTableObject::set, 2, JSPROP_ENUMERATE), + JS_FN("grow", WasmTableObject::grow, 1, JSPROP_ENUMERATE), JS_FS_END}; + +const JSFunctionSpec WasmTableObject::static_methods[] = {JS_FS_END}; + +Table& WasmTableObject::table() const { + return *(Table*)getReservedSlot(TABLE_SLOT).toPrivate(); +} + +bool WasmTableObject::fillRange(JSContext* cx, uint32_t index, uint32_t length, + HandleValue value) const { + Table& tab = table(); + + // All consumers are required to either bounds check or statically be in + // bounds + MOZ_ASSERT(uint64_t(index) + uint64_t(length) <= tab.length()); + + RootedFunction fun(cx); + RootedAnyRef any(cx, AnyRef::null()); + if (!CheckRefType(cx, tab.elemType(), value, &fun, &any)) { + return false; + } + switch (tab.repr()) { + case TableRepr::Func: + MOZ_RELEASE_ASSERT(!tab.isAsmJS()); + tab.fillFuncRef(index, length, FuncRef::fromJSFunction(fun), cx); + break; + case TableRepr::Ref: + tab.fillAnyRef(index, length, any); + break; + } + return true; +} + +// ============================================================================ +// WebAssembly.global class and methods + +const JSClassOps WasmGlobalObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + WasmGlobalObject::finalize, // finalize + nullptr, // call + nullptr, // construct + WasmGlobalObject::trace, // trace +}; + +const JSClass WasmGlobalObject::class_ = { + "WebAssembly.Global", + JSCLASS_HAS_RESERVED_SLOTS(WasmGlobalObject::RESERVED_SLOTS) | + JSCLASS_BACKGROUND_FINALIZE, + &WasmGlobalObject::classOps_, &WasmGlobalObject::classSpec_}; + +const JSClass& WasmGlobalObject::protoClass_ = PlainObject::class_; + +static constexpr char WasmGlobalName[] = "Global"; + +const ClassSpec WasmGlobalObject::classSpec_ = { + CreateWasmConstructor<WasmGlobalObject, WasmGlobalName>, + GenericCreatePrototype<WasmGlobalObject>, + WasmGlobalObject::static_methods, + nullptr, + WasmGlobalObject::methods, + WasmGlobalObject::properties, + nullptr, + ClassSpec::DontDefineConstructor}; + +/* static */ +void WasmGlobalObject::trace(JSTracer* trc, JSObject* obj) { + WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj); + if (global->isNewborn()) { + // This can happen while we're allocating the object, in which case + // every single slot of the object is not defined yet. In particular, + // there's nothing to trace yet. + return; + } + global->val().get().trace(trc); +} + +/* static */ +void WasmGlobalObject::finalize(JS::GCContext* gcx, JSObject* obj) { + WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj); + if (!global->isNewborn()) { + gcx->delete_(obj, &global->val(), MemoryUse::WasmGlobalCell); + } +} + +/* static */ +WasmGlobalObject* WasmGlobalObject::create(JSContext* cx, HandleVal value, + bool isMutable, HandleObject proto) { + Rooted<WasmGlobalObject*> obj( + cx, NewObjectWithGivenProto<WasmGlobalObject>(cx, proto)); + if (!obj) { + return nullptr; + } + + MOZ_ASSERT(obj->isNewborn()); + MOZ_ASSERT(obj->isTenured(), "assumed by global.set post barriers"); + + GCPtrVal* val = js_new<GCPtrVal>(Val()); + if (!val) { + ReportOutOfMemory(cx); + return nullptr; + } + obj->initReservedSlot(MUTABLE_SLOT, JS::BooleanValue(isMutable)); + InitReservedSlot(obj, VAL_SLOT, val, MemoryUse::WasmGlobalCell); + + // It's simpler to initialize the cell after the object has been created, + // to avoid needing to root the cell before the object creation. + obj->val() = value.get(); + + MOZ_ASSERT(!obj->isNewborn()); + + return obj; +} + +/* static */ +bool WasmGlobalObject::construct(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "Global")) { + return false; + } + + if (!args.requireAtLeast(cx, "WebAssembly.Global", 1)) { + return false; + } + + if (!args.get(0).isObject()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_DESC_ARG, "global"); + return false; + } + + RootedObject obj(cx, &args[0].toObject()); + + // Extract properties in lexicographic order per spec. + + RootedValue mutableVal(cx); + if (!JS_GetProperty(cx, obj, "mutable", &mutableVal)) { + return false; + } + + RootedValue typeVal(cx); + if (!JS_GetProperty(cx, obj, "value", &typeVal)) { + return false; + } + + ValType globalType; + if (!ToValType(cx, typeVal, &globalType)) { + return false; + } + + if (!globalType.isExposable()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_VAL_TYPE); + return false; + } + + bool isMutable = ToBoolean(mutableVal); + + // Extract the initial value, or provide a suitable default. + RootedVal globalVal(cx, globalType); + + // Override with non-undefined value, if provided. + RootedValue valueVal(cx); + if (globalType.isRefType()) { + valueVal.set(args.length() < 2 ? RefTypeDefautValue(globalType.refType()) + : args[1]); + if (!Val::fromJSValue(cx, globalType, valueVal, &globalVal)) { + return false; + } + } else { + valueVal.set(args.get(1)); + if (!valueVal.isUndefined() && + !Val::fromJSValue(cx, globalType, valueVal, &globalVal)) { + return false; + } + } + + RootedObject proto(cx, + GetWasmConstructorPrototype(cx, args, JSProto_WasmGlobal)); + if (!proto) { + ReportOutOfMemory(cx); + return false; + } + + WasmGlobalObject* global = + WasmGlobalObject::create(cx, globalVal, isMutable, proto); + if (!global) { + return false; + } + + args.rval().setObject(*global); + return true; +} + +static bool IsGlobal(HandleValue v) { + return v.isObject() && v.toObject().is<WasmGlobalObject>(); +} + +/* static */ +bool WasmGlobalObject::valueGetterImpl(JSContext* cx, const CallArgs& args) { + const WasmGlobalObject& globalObj = + args.thisv().toObject().as<WasmGlobalObject>(); + if (!globalObj.type().isExposable()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_VAL_TYPE); + return false; + } + return globalObj.val().get().toJSValue(cx, args.rval()); +} + +/* static */ +bool WasmGlobalObject::valueGetter(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsGlobal, valueGetterImpl>(cx, args); +} + +/* static */ +bool WasmGlobalObject::valueSetterImpl(JSContext* cx, const CallArgs& args) { + if (!args.requireAtLeast(cx, "WebAssembly.Global setter", 1)) { + return false; + } + + Rooted<WasmGlobalObject*> global( + cx, &args.thisv().toObject().as<WasmGlobalObject>()); + if (!global->isMutable()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_GLOBAL_IMMUTABLE); + return false; + } + + RootedVal val(cx); + if (!Val::fromJSValue(cx, global->type(), args.get(0), &val)) { + return false; + } + global->val() = val.get(); + + args.rval().setUndefined(); + return true; +} + +/* static */ +bool WasmGlobalObject::valueSetter(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsGlobal, valueSetterImpl>(cx, args); +} + +const JSPropertySpec WasmGlobalObject::properties[] = { + JS_PSGS("value", WasmGlobalObject::valueGetter, + WasmGlobalObject::valueSetter, JSPROP_ENUMERATE), + JS_STRING_SYM_PS(toStringTag, "WebAssembly.Global", JSPROP_READONLY), + JS_PS_END}; + +const JSFunctionSpec WasmGlobalObject::methods[] = { +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + JS_FN("type", WasmGlobalObject::type, 0, JSPROP_ENUMERATE), +#endif + JS_FN(js_valueOf_str, WasmGlobalObject::valueGetter, 0, JSPROP_ENUMERATE), + JS_FS_END}; + +const JSFunctionSpec WasmGlobalObject::static_methods[] = {JS_FS_END}; + +bool WasmGlobalObject::isMutable() const { + return getReservedSlot(MUTABLE_SLOT).toBoolean(); +} + +ValType WasmGlobalObject::type() const { return val().get().type(); } + +GCPtrVal& WasmGlobalObject::val() const { + return *reinterpret_cast<GCPtrVal*>(getReservedSlot(VAL_SLOT).toPrivate()); +} + +#ifdef ENABLE_WASM_TYPE_REFLECTIONS +/* static */ +bool WasmGlobalObject::typeImpl(JSContext* cx, const CallArgs& args) { + Rooted<WasmGlobalObject*> global( + cx, &args.thisv().toObject().as<WasmGlobalObject>()); + RootedObject typeObj( + cx, GlobalTypeToObject(cx, global->type(), global->isMutable())); + if (!typeObj) { + return false; + } + args.rval().setObject(*typeObj); + return true; +} + +/* static */ +bool WasmGlobalObject::type(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsGlobal, typeImpl>(cx, args); +} +#endif + +// ============================================================================ +// WebAssembly.Tag class and methods + +const JSClassOps WasmTagObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + WasmTagObject::finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +const JSClass WasmTagObject::class_ = { + "WebAssembly.Tag", + JSCLASS_HAS_RESERVED_SLOTS(WasmTagObject::RESERVED_SLOTS) | + JSCLASS_FOREGROUND_FINALIZE, + &WasmTagObject::classOps_, &WasmTagObject::classSpec_}; + +const JSClass& WasmTagObject::protoClass_ = PlainObject::class_; + +static constexpr char WasmTagName[] = "Tag"; + +const ClassSpec WasmTagObject::classSpec_ = { + CreateWasmConstructor<WasmTagObject, WasmTagName>, + GenericCreatePrototype<WasmTagObject>, + WasmTagObject::static_methods, + nullptr, + WasmTagObject::methods, + WasmTagObject::properties, + nullptr, + ClassSpec::DontDefineConstructor}; + +/* static */ +void WasmTagObject::finalize(JS::GCContext* gcx, JSObject* obj) { + WasmTagObject& tagObj = obj->as<WasmTagObject>(); + tagObj.tagType()->Release(); +} + +static bool IsTag(HandleValue v) { + return v.isObject() && v.toObject().is<WasmTagObject>(); +} + +bool WasmTagObject::construct(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "Tag")) { + return false; + } + + if (!args.requireAtLeast(cx, "WebAssembly.Tag", 1)) { + return false; + } + + if (!args.get(0).isObject()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_DESC_ARG, "tag"); + return false; + } + + RootedObject obj(cx, &args[0].toObject()); + RootedValue paramsVal(cx); + if (!JS_GetProperty(cx, obj, "parameters", ¶msVal)) { + return false; + } + + ValTypeVector params; + if (!ParseValTypes(cx, paramsVal, params)) { + return false; + } + wasm::MutableTagType tagType = js_new<wasm::TagType>(); + if (!tagType || !tagType->initialize(std::move(params))) { + return false; + } + + RootedObject proto(cx, + GetWasmConstructorPrototype(cx, args, JSProto_WasmTag)); + if (!proto) { + ReportOutOfMemory(cx); + return false; + } + + Rooted<WasmTagObject*> tagObj(cx, WasmTagObject::create(cx, tagType, proto)); + if (!tagObj) { + return false; + } + + args.rval().setObject(*tagObj); + return true; +} + +/* static */ +WasmTagObject* WasmTagObject::create(JSContext* cx, + const wasm::SharedTagType& tagType, + HandleObject proto) { + Rooted<WasmTagObject*> obj(cx, + NewObjectWithGivenProto<WasmTagObject>(cx, proto)); + if (!obj) { + return nullptr; + } + + tagType.get()->AddRef(); + obj->initReservedSlot(TYPE_SLOT, PrivateValue((void*)tagType.get())); + + return obj; +} + +const JSPropertySpec WasmTagObject::properties[] = { + JS_STRING_SYM_PS(toStringTag, "WebAssembly.Tag", JSPROP_READONLY), + JS_PS_END}; + +#ifdef ENABLE_WASM_TYPE_REFLECTIONS +/* static */ +bool WasmTagObject::typeImpl(JSContext* cx, const CallArgs& args) { + Rooted<WasmTagObject*> tag(cx, &args.thisv().toObject().as<WasmTagObject>()); + RootedObject typeObj(cx, TagTypeToObject(cx, tag->valueTypes())); + if (!typeObj) { + return false; + } + args.rval().setObject(*typeObj); + return true; +} + +/* static */ +bool WasmTagObject::type(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsTag, typeImpl>(cx, args); +} +#endif + +const JSFunctionSpec WasmTagObject::methods[] = { +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + JS_FN("type", WasmTagObject::type, 0, JSPROP_ENUMERATE), +#endif + JS_FS_END}; + +const JSFunctionSpec WasmTagObject::static_methods[] = {JS_FS_END}; + +const TagType* WasmTagObject::tagType() const { + return (const TagType*)getFixedSlot(TYPE_SLOT).toPrivate(); +}; + +const wasm::ValTypeVector& WasmTagObject::valueTypes() const { + return tagType()->argTypes_; +}; + +wasm::ResultType WasmTagObject::resultType() const { + return wasm::ResultType::Vector(valueTypes()); +} + +// ============================================================================ +// WebAssembly.Exception class and methods + +const JSClassOps WasmExceptionObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + WasmExceptionObject::finalize, // finalize + nullptr, // call + nullptr, // construct + WasmExceptionObject::trace, // trace +}; + +const JSClass WasmExceptionObject::class_ = { + "WebAssembly.Exception", + JSCLASS_HAS_RESERVED_SLOTS(WasmExceptionObject::RESERVED_SLOTS) | + JSCLASS_FOREGROUND_FINALIZE, + &WasmExceptionObject::classOps_, &WasmExceptionObject::classSpec_}; + +const JSClass& WasmExceptionObject::protoClass_ = PlainObject::class_; + +static constexpr char WasmExceptionName[] = "Exception"; + +const ClassSpec WasmExceptionObject::classSpec_ = { + CreateWasmConstructor<WasmExceptionObject, WasmExceptionName>, + GenericCreatePrototype<WasmExceptionObject>, + WasmExceptionObject::static_methods, + nullptr, + WasmExceptionObject::methods, + WasmExceptionObject::properties, + nullptr, + ClassSpec::DontDefineConstructor}; + +/* static */ +void WasmExceptionObject::finalize(JS::GCContext* gcx, JSObject* obj) { + WasmExceptionObject& exnObj = obj->as<WasmExceptionObject>(); + if (exnObj.isNewborn()) { + return; + } + gcx->free_(obj, exnObj.typedMem(), exnObj.tagType()->size_, + MemoryUse::WasmExceptionData); + exnObj.tagType()->Release(); +} + +/* static */ +void WasmExceptionObject::trace(JSTracer* trc, JSObject* obj) { + WasmExceptionObject& exnObj = obj->as<WasmExceptionObject>(); + if (exnObj.isNewborn()) { + return; + } + + wasm::SharedTagType tag = exnObj.tagType(); + const wasm::ValTypeVector& params = tag->argTypes_; + const wasm::TagOffsetVector& offsets = tag->argOffsets_; + uint8_t* typedMem = exnObj.typedMem(); + for (size_t i = 0; i < params.length(); i++) { + ValType paramType = params[i]; + if (paramType.isRefRepr()) { + GCPtr<JSObject*>* objectPtr = + reinterpret_cast<GCPtr<JSObject*>*>(typedMem + offsets[i]); + TraceNullableEdge(trc, objectPtr, "reference-obj"); + } + } +} + +static bool IsException(HandleValue v) { + return v.isObject() && v.toObject().is<WasmExceptionObject>(); +} + +struct ExceptionOptions { + bool traceStack; + + ExceptionOptions() : traceStack(false) {} + + [[nodiscard]] bool init(JSContext* cx, HandleValue val); +}; + +bool ExceptionOptions::init(JSContext* cx, HandleValue val) { + if (val.isNullOrUndefined()) { + return true; + } + + if (!val.isObject()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_EXN_OPTIONS); + return false; + } + RootedObject obj(cx, &val.toObject()); + + // Get `traceStack` and coerce to boolean + RootedValue traceStackVal(cx); + if (!JS_GetProperty(cx, obj, "traceStack", &traceStackVal)) { + return false; + } + traceStack = ToBoolean(traceStackVal); + + return true; +} + +bool WasmExceptionObject::construct(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "Exception")) { + return false; + } + + if (!args.requireAtLeast(cx, "WebAssembly.Exception", 2)) { + return false; + } + + if (!IsTag(args[0])) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_EXN_ARG); + return false; + } + Rooted<WasmTagObject*> exnTag(cx, &args[0].toObject().as<WasmTagObject>()); + + if (!args.get(1).isObject()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_EXN_PAYLOAD); + return false; + } + + JS::ForOfIterator iterator(cx); + if (!iterator.init(args.get(1), JS::ForOfIterator::ThrowOnNonIterable)) { + return false; + } + + // Get the optional 'options' parameter + ExceptionOptions options; + if (!options.init(cx, args.get(2))) { + return false; + } + + // Trace the stack if requested + RootedObject stack(cx); + if (options.traceStack && !CaptureStack(cx, &stack)) { + return false; + } + + RootedObject proto( + cx, GetWasmConstructorPrototype(cx, args, JSProto_WasmException)); + if (!proto) { + ReportOutOfMemory(cx); + return false; + } + + Rooted<WasmExceptionObject*> exnObj( + cx, WasmExceptionObject::create(cx, exnTag, stack, proto)); + if (!exnObj) { + return false; + } + + wasm::SharedTagType tagType = exnObj->tagType(); + const wasm::ValTypeVector& params = tagType->argTypes_; + const wasm::TagOffsetVector& offsets = tagType->argOffsets_; + + RootedValue nextArg(cx); + for (size_t i = 0; i < params.length(); i++) { + bool done; + if (!iterator.next(&nextArg, &done)) { + return false; + } + if (done) { + UniqueChars expected(JS_smprintf("%zu", params.length())); + UniqueChars got(JS_smprintf("%zu", i)); + + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_EXN_PAYLOAD_LEN, expected.get(), + got.get()); + return false; + } + + if (!exnObj->initValue(cx, offsets[i], params[i], nextArg)) { + return false; + } + } + + args.rval().setObject(*exnObj); + return true; +} + +/* static */ +WasmExceptionObject* WasmExceptionObject::create(JSContext* cx, + Handle<WasmTagObject*> tag, + HandleObject stack, + HandleObject proto) { + Rooted<WasmExceptionObject*> obj( + cx, NewObjectWithGivenProto<WasmExceptionObject>(cx, proto)); + if (!obj) { + return nullptr; + } + const TagType* tagType = tag->tagType(); + + // Allocate the data buffer before initializing the object so that an OOM + // does not result in a partially constructed object. + uint8_t* data = (uint8_t*)js_calloc(tagType->size_); + if (!data) { + return nullptr; + } + + MOZ_ASSERT(obj->isNewborn()); + obj->initFixedSlot(TAG_SLOT, ObjectValue(*tag)); + tagType->AddRef(); + obj->initFixedSlot(TYPE_SLOT, PrivateValue((void*)tagType)); + InitReservedSlot(obj, DATA_SLOT, data, tagType->size_, + MemoryUse::WasmExceptionData); + obj->initFixedSlot(STACK_SLOT, ObjectOrNullValue(stack)); + + MOZ_ASSERT(!obj->isNewborn()); + + return obj; +} + +bool WasmExceptionObject::isNewborn() const { + MOZ_ASSERT(is<WasmExceptionObject>()); + return getReservedSlot(DATA_SLOT).isUndefined(); +} + +const JSPropertySpec WasmExceptionObject::properties[] = { + JS_PSG("stack", WasmExceptionObject::getStack, 0), + JS_STRING_SYM_PS(toStringTag, "WebAssembly.Exception", JSPROP_READONLY), + JS_PS_END}; + +/* static */ +bool WasmExceptionObject::isImpl(JSContext* cx, const CallArgs& args) { + Rooted<WasmExceptionObject*> exnObj( + cx, &args.thisv().toObject().as<WasmExceptionObject>()); + + if (!args.requireAtLeast(cx, "WebAssembly.Exception.is", 1)) { + return false; + } + + if (!IsTag(args[0])) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_EXN_ARG); + return false; + } + + Rooted<WasmTagObject*> exnTag(cx, + &args.get(0).toObject().as<WasmTagObject>()); + args.rval().setBoolean(exnTag.get() == &exnObj->tag()); + + return true; +} + +/* static */ +bool WasmExceptionObject::isMethod(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsException, isImpl>(cx, args); +} + +/* static */ +bool WasmExceptionObject::getArgImpl(JSContext* cx, const CallArgs& args) { + Rooted<WasmExceptionObject*> exnObj( + cx, &args.thisv().toObject().as<WasmExceptionObject>()); + + if (!args.requireAtLeast(cx, "WebAssembly.Exception.getArg", 2)) { + return false; + } + + if (!IsTag(args[0])) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_EXN_ARG); + return false; + } + + Rooted<WasmTagObject*> exnTag(cx, + &args.get(0).toObject().as<WasmTagObject>()); + if (exnTag.get() != &exnObj->tag()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_EXN_TAG); + return false; + } + + uint32_t index; + if (!EnforceRangeU32(cx, args.get(1), "Exception", "getArg index", &index)) { + return false; + } + + const wasm::ValTypeVector& params = exnTag->valueTypes(); + if (index >= params.length()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE, + "Exception", "getArg index"); + return false; + } + + uint32_t offset = exnTag->tagType()->argOffsets_[index]; + RootedValue result(cx); + if (!exnObj->loadValue(cx, offset, params[index], &result)) { + return false; + } + args.rval().set(result); + return true; +} + +/* static */ +bool WasmExceptionObject::getArg(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsException, getArgImpl>(cx, args); +} + +/* static */ +bool WasmExceptionObject::getStack_impl(JSContext* cx, const CallArgs& args) { + Rooted<WasmExceptionObject*> exnObj( + cx, &args.thisv().toObject().as<WasmExceptionObject>()); + RootedObject savedFrameObj(cx, exnObj->stack()); + if (!savedFrameObj) { + args.rval().setUndefined(); + return true; + } + JSPrincipals* principals = exnObj->realm()->principals(); + RootedString stackString(cx); + if (!BuildStackString(cx, principals, savedFrameObj, &stackString)) { + return false; + } + args.rval().setString(stackString); + return true; +} + +/* static */ +bool WasmExceptionObject::getStack(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsException, getStack_impl>(cx, args); +} + +JSObject* WasmExceptionObject::stack() const { + return getReservedSlot(STACK_SLOT).toObjectOrNull(); +} + +uint8_t* WasmExceptionObject::typedMem() const { + return (uint8_t*)getReservedSlot(DATA_SLOT).toPrivate(); +} + +bool WasmExceptionObject::loadValue(JSContext* cx, size_t offset, + wasm::ValType type, MutableHandleValue vp) { + if (!type.isExposable()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_VAL_TYPE); + return false; + } + return ToJSValue(cx, typedMem() + offset, type, vp); +} + +bool WasmExceptionObject::initValue(JSContext* cx, size_t offset, + wasm::ValType type, HandleValue value) { + if (!type.isExposable()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_VAL_TYPE); + return false; + } + + // Avoid rooting hazard of `this` being live across `fromJSValue` + // which may GC. + uint8_t* dest = typedMem() + offset; + RootedVal val(cx); + if (!Val::fromJSValue(cx, type, value, &val)) { + return false; + } + val.get().writeToHeapLocation(dest); + return true; +} + +const JSFunctionSpec WasmExceptionObject::methods[] = { + JS_FN("is", WasmExceptionObject::isMethod, 1, JSPROP_ENUMERATE), + JS_FN("getArg", WasmExceptionObject::getArg, 2, JSPROP_ENUMERATE), + JS_FS_END}; + +const JSFunctionSpec WasmExceptionObject::static_methods[] = {JS_FS_END}; + +const TagType* WasmExceptionObject::tagType() const { + return (const TagType*)getReservedSlot(TYPE_SLOT).toPrivate(); +} + +WasmTagObject& WasmExceptionObject::tag() const { + return getReservedSlot(TAG_SLOT).toObject().as<WasmTagObject>(); +} + +// ============================================================================ +// WebAssembly.Function and methods +#ifdef ENABLE_WASM_TYPE_REFLECTIONS +static JSObject* CreateWasmFunctionPrototype(JSContext* cx, JSProtoKey key) { + // WasmFunction's prototype should inherit from JSFunction's prototype. + RootedObject jsProto(cx, &cx->global()->getFunctionPrototype()); + return GlobalObject::createBlankPrototypeInheriting(cx, &PlainObject::class_, + jsProto); +} + +[[nodiscard]] static bool IsWasmFunction(HandleValue v) { + if (!v.isObject()) { + return false; + } + if (!v.toObject().is<JSFunction>()) { + return false; + } + return v.toObject().as<JSFunction>().isWasm(); +} + +bool WasmFunctionTypeImpl(JSContext* cx, const CallArgs& args) { + RootedFunction function(cx, &args.thisv().toObject().as<JSFunction>()); + Rooted<WasmInstanceObject*> instanceObj( + cx, ExportedFunctionToInstanceObject(function)); + uint32_t funcIndex = ExportedFunctionToFuncIndex(function); + Instance& instance = instanceObj->instance(); + const FuncExport& fe = + instance.metadata(instance.code().bestTier()).lookupFuncExport(funcIndex); + const FuncType& funcType = instance.metadata().getFuncExportType(fe); + RootedObject typeObj(cx, FuncTypeToObject(cx, funcType)); + if (!typeObj) { + return false; + } + args.rval().setObject(*typeObj); + return true; +} + +bool WasmFunctionType(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsWasmFunction, WasmFunctionTypeImpl>(cx, args); +} + +JSFunction* WasmFunctionCreate(JSContext* cx, HandleFunction func, + wasm::ValTypeVector&& params, + wasm::ValTypeVector&& results, + HandleObject proto) { + MOZ_RELEASE_ASSERT(!IsWasmExportedFunction(func)); + + // We want to import the function to a wasm module and then export it again so + // that it behaves exactly like a normal wasm function and can be used like + // one in wasm tables. We synthesize such a module below, instantiate it, and + // then return the exported function as the result. + FeatureOptions options; + ScriptedCaller scriptedCaller; + SharedCompileArgs compileArgs = + CompileArgs::buildAndReport(cx, std::move(scriptedCaller), options); + if (!compileArgs) { + return nullptr; + } + + ModuleEnvironment moduleEnv(compileArgs->features); + CompilerEnvironment compilerEnv(CompileMode::Once, Tier::Optimized, + DebugEnabled::False); + compilerEnv.computeParameters(); + + if (!moduleEnv.init()) { + return nullptr; + } + + FuncType funcType = FuncType(std::move(params), std::move(results)); + if (!moduleEnv.types->addType(std::move(funcType))) { + return nullptr; + } + + // Add an (import (func ...)) + FuncDesc funcDesc = FuncDesc(&(*moduleEnv.types)[0].funcType(), 0); + if (!moduleEnv.funcs.append(funcDesc)) { + return nullptr; + } + moduleEnv.numFuncImports = 1; + + // Add an (export (func 0)) + moduleEnv.declareFuncExported(0, /* eager */ true, /* canRefFunc */ true); + + // We will be looking up and using the function in the future by index so the + // name doesn't matter. + CacheableName fieldName; + if (!moduleEnv.exports.emplaceBack(std::move(fieldName), 0, + DefinitionKind::Function)) { + return nullptr; + } + + ModuleGenerator mg(*compileArgs, &moduleEnv, &compilerEnv, nullptr, nullptr, + nullptr); + if (!mg.init(nullptr)) { + return nullptr; + } + // We're not compiling any function definitions. + if (!mg.finishFuncDefs()) { + return nullptr; + } + SharedBytes shareableBytes = js_new<ShareableBytes>(); + if (!shareableBytes) { + return nullptr; + } + SharedModule module = mg.finishModule(*shareableBytes); + if (!module) { + return nullptr; + } + + // Instantiate the module. + Rooted<ImportValues> imports(cx); + if (!imports.get().funcs.append(func)) { + return nullptr; + } + Rooted<WasmInstanceObject*> instance(cx); + if (!module->instantiate(cx, imports.get(), nullptr, &instance)) { + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + return nullptr; + } + + // Get the exported function which wraps the JS function to return. + RootedFunction wasmFunc(cx); + if (!instance->getExportedFunction(cx, instance, 0, &wasmFunc)) { + return nullptr; + } + return wasmFunc; +} + +bool WasmFunctionConstruct(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "WebAssembly.Function")) { + return false; + } + + if (!args.requireAtLeast(cx, "WebAssembly.Function", 2)) { + return false; + } + + if (!args[0].isObject()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_DESC_ARG, "function"); + return false; + } + RootedObject typeObj(cx, &args[0].toObject()); + + // Extract properties in lexicographic order per spec. + + RootedValue parametersVal(cx); + if (!JS_GetProperty(cx, typeObj, "parameters", ¶metersVal)) { + return false; + } + + ValTypeVector params; + if (!ParseValTypes(cx, parametersVal, params)) { + return false; + } + + RootedValue resultsVal(cx); + if (!JS_GetProperty(cx, typeObj, "results", &resultsVal)) { + return false; + } + + ValTypeVector results; + if (!ParseValTypes(cx, resultsVal, results)) { + return false; + } + + // Get the target function + + if (!args[1].isObject() || !args[1].toObject().is<JSFunction>() || + IsWasmFunction(args[1])) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_FUNCTION_VALUE); + return false; + } + RootedFunction func(cx, &args[1].toObject().as<JSFunction>()); + + RootedObject proto( + cx, GetWasmConstructorPrototype(cx, args, JSProto_WasmFunction)); + if (!proto) { + ReportOutOfMemory(cx); + return false; + } + + RootedFunction wasmFunc(cx, WasmFunctionCreate(cx, func, std::move(params), + std::move(results), proto)); + if (!wasmFunc) { + ReportOutOfMemory(cx); + return false; + } + args.rval().setObject(*wasmFunc); + + return true; +} + +static constexpr char WasmFunctionName[] = "Function"; + +static JSObject* CreateWasmFunctionConstructor(JSContext* cx, JSProtoKey key) { + RootedObject proto(cx, &cx->global()->getFunctionConstructor()); + + Rooted<JSAtom*> className( + cx, Atomize(cx, WasmFunctionName, strlen(WasmFunctionName))); + if (!className) { + return nullptr; + } + return NewFunctionWithProto(cx, WasmFunctionConstruct, 1, + FunctionFlags::NATIVE_CTOR, nullptr, className, + proto, gc::AllocKind::FUNCTION, TenuredObject); +} + +const JSFunctionSpec WasmFunctionMethods[] = { + JS_FN("type", WasmFunctionType, 0, 0), JS_FS_END}; + +const ClassSpec WasmFunctionClassSpec = {CreateWasmFunctionConstructor, + CreateWasmFunctionPrototype, + nullptr, + nullptr, + WasmFunctionMethods, + nullptr, + nullptr, + ClassSpec::DontDefineConstructor}; + +const JSClass js::WasmFunctionClass = { + "WebAssembly.Function", 0, JS_NULL_CLASS_OPS, &WasmFunctionClassSpec}; + +#endif + +// ============================================================================ +// WebAssembly class and static methods + +static bool WebAssembly_toSource(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setString(cx->names().WebAssembly); + return true; +} + +static bool RejectWithPendingException(JSContext* cx, + Handle<PromiseObject*> promise) { + if (!cx->isExceptionPending()) { + return false; + } + + RootedValue rejectionValue(cx); + if (!GetAndClearException(cx, &rejectionValue)) { + return false; + } + + return PromiseObject::reject(cx, promise, rejectionValue); +} + +static bool Reject(JSContext* cx, const CompileArgs& args, + Handle<PromiseObject*> promise, const UniqueChars& error) { + if (!error) { + ReportOutOfMemory(cx); + return RejectWithPendingException(cx, promise); + } + + RootedObject stack(cx, promise->allocationSite()); + RootedString fileName(cx); + if (const char* filename = args.scriptedCaller.filename.get()) { + fileName = + JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(filename, strlen(filename))); + } else { + fileName = JS_GetEmptyString(cx); + } + if (!fileName) { + return false; + } + + unsigned line = args.scriptedCaller.line; + + // Ideally we'd report a JSMSG_WASM_COMPILE_ERROR here, but there's no easy + // way to create an ErrorObject for an arbitrary error code with multiple + // replacements. + UniqueChars str(JS_smprintf("wasm validation error: %s", error.get())); + if (!str) { + return false; + } + + size_t len = strlen(str.get()); + RootedString message(cx, NewStringCopyN<CanGC>(cx, str.get(), len)); + if (!message) { + return false; + } + + // There's no error |cause| available here. + auto cause = JS::NothingHandleValue; + + RootedObject errorObj( + cx, ErrorObject::create(cx, JSEXN_WASMCOMPILEERROR, stack, fileName, 0, + line, 0, nullptr, message, cause)); + if (!errorObj) { + return false; + } + + RootedValue rejectionValue(cx, ObjectValue(*errorObj)); + return PromiseObject::reject(cx, promise, rejectionValue); +} + +static void LogAsync(JSContext* cx, const char* funcName, + const Module& module) { + Log(cx, "async %s succeeded%s", funcName, + module.loggingDeserialized() ? " (loaded from cache)" : ""); +} + +enum class Ret { Pair, Instance }; + +class AsyncInstantiateTask : public OffThreadPromiseTask { + SharedModule module_; + PersistentRooted<ImportValues> imports_; + Ret ret_; + + public: + AsyncInstantiateTask(JSContext* cx, const Module& module, Ret ret, + Handle<PromiseObject*> promise) + : OffThreadPromiseTask(cx, promise), + module_(&module), + imports_(cx), + ret_(ret) {} + + ImportValues& imports() { return imports_.get(); } + + bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override { + RootedObject instanceProto( + cx, &cx->global()->getPrototype(JSProto_WasmInstance)); + + Rooted<WasmInstanceObject*> instanceObj(cx); + if (!module_->instantiate(cx, imports_.get(), instanceProto, + &instanceObj)) { + return RejectWithPendingException(cx, promise); + } + + RootedValue resolutionValue(cx); + if (ret_ == Ret::Instance) { + resolutionValue = ObjectValue(*instanceObj); + } else { + RootedObject resultObj(cx, JS_NewPlainObject(cx)); + if (!resultObj) { + return RejectWithPendingException(cx, promise); + } + + RootedObject moduleProto(cx, + &cx->global()->getPrototype(JSProto_WasmModule)); + RootedObject moduleObj( + cx, WasmModuleObject::create(cx, *module_, moduleProto)); + if (!moduleObj) { + return RejectWithPendingException(cx, promise); + } + + RootedValue val(cx, ObjectValue(*moduleObj)); + if (!JS_DefineProperty(cx, resultObj, "module", val, JSPROP_ENUMERATE)) { + return RejectWithPendingException(cx, promise); + } + + val = ObjectValue(*instanceObj); + if (!JS_DefineProperty(cx, resultObj, "instance", val, + JSPROP_ENUMERATE)) { + return RejectWithPendingException(cx, promise); + } + + resolutionValue = ObjectValue(*resultObj); + } + + if (!PromiseObject::resolve(cx, promise, resolutionValue)) { + return RejectWithPendingException(cx, promise); + } + + LogAsync(cx, "instantiate", *module_); + return true; + } +}; + +static bool AsyncInstantiate(JSContext* cx, const Module& module, + HandleObject importObj, Ret ret, + Handle<PromiseObject*> promise) { + auto task = js::MakeUnique<AsyncInstantiateTask>(cx, module, ret, promise); + if (!task || !task->init(cx)) { + return false; + } + + if (!GetImports(cx, module, importObj, &task->imports())) { + return RejectWithPendingException(cx, promise); + } + + task.release()->dispatchResolveAndDestroy(); + return true; +} + +static bool ResolveCompile(JSContext* cx, const Module& module, + Handle<PromiseObject*> promise) { + RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule)); + RootedObject moduleObj(cx, WasmModuleObject::create(cx, module, proto)); + if (!moduleObj) { + return RejectWithPendingException(cx, promise); + } + + RootedValue resolutionValue(cx, ObjectValue(*moduleObj)); + if (!PromiseObject::resolve(cx, promise, resolutionValue)) { + return RejectWithPendingException(cx, promise); + } + + LogAsync(cx, "compile", module); + return true; +} + +struct CompileBufferTask : PromiseHelperTask { + MutableBytes bytecode; + SharedCompileArgs compileArgs; + UniqueChars error; + UniqueCharsVector warnings; + SharedModule module; + bool instantiate; + PersistentRootedObject importObj; + + CompileBufferTask(JSContext* cx, Handle<PromiseObject*> promise, + HandleObject importObj) + : PromiseHelperTask(cx, promise), + instantiate(true), + importObj(cx, importObj) {} + + CompileBufferTask(JSContext* cx, Handle<PromiseObject*> promise) + : PromiseHelperTask(cx, promise), instantiate(false) {} + + bool init(JSContext* cx, const char* introducer) { + compileArgs = InitCompileArgs(cx, introducer); + if (!compileArgs) { + return false; + } + return PromiseHelperTask::init(cx); + } + + void execute() override { + module = CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr); + } + + bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override { + if (!ReportCompileWarnings(cx, warnings)) { + return false; + } + if (!module) { + return Reject(cx, *compileArgs, promise, error); + } + if (instantiate) { + return AsyncInstantiate(cx, *module, importObj, Ret::Pair, promise); + } + return ResolveCompile(cx, *module, promise); + } +}; + +static bool RejectWithPendingException(JSContext* cx, + Handle<PromiseObject*> promise, + CallArgs& callArgs) { + if (!RejectWithPendingException(cx, promise)) { + return false; + } + + callArgs.rval().setObject(*promise); + return true; +} + +static bool EnsurePromiseSupport(JSContext* cx) { + if (!cx->runtime()->offThreadPromiseState.ref().initialized()) { + JS_ReportErrorASCII( + cx, "WebAssembly Promise APIs not supported in this runtime."); + return false; + } + return true; +} + +static bool GetBufferSource(JSContext* cx, CallArgs callArgs, const char* name, + MutableBytes* bytecode) { + if (!callArgs.requireAtLeast(cx, name, 1)) { + return false; + } + + if (!callArgs[0].isObject()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_BUF_ARG); + return false; + } + + return GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG, + bytecode); +} + +static bool WebAssembly_compile(JSContext* cx, unsigned argc, Value* vp) { + if (!EnsurePromiseSupport(cx)) { + return false; + } + + Log(cx, "async compile() started"); + + Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx)); + if (!promise) { + return false; + } + + CallArgs callArgs = CallArgsFromVp(argc, vp); + + if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_CSP_BLOCKED_WASM, "WebAssembly.compile"); + return RejectWithPendingException(cx, promise, callArgs); + } + + auto task = cx->make_unique<CompileBufferTask>(cx, promise); + if (!task || !task->init(cx, "WebAssembly.compile")) { + return false; + } + + if (!GetBufferSource(cx, callArgs, "WebAssembly.compile", &task->bytecode)) { + return RejectWithPendingException(cx, promise, callArgs); + } + + if (!StartOffThreadPromiseHelperTask(cx, std::move(task))) { + return false; + } + + callArgs.rval().setObject(*promise); + return true; +} + +static bool GetInstantiateArgs(JSContext* cx, CallArgs callArgs, + MutableHandleObject firstArg, + MutableHandleObject importObj) { + if (!callArgs.requireAtLeast(cx, "WebAssembly.instantiate", 1)) { + return false; + } + + if (!callArgs[0].isObject()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_BUF_MOD_ARG); + return false; + } + + firstArg.set(&callArgs[0].toObject()); + + return GetImportArg(cx, callArgs, importObj); +} + +static bool WebAssembly_instantiate(JSContext* cx, unsigned argc, Value* vp) { + if (!EnsurePromiseSupport(cx)) { + return false; + } + + Log(cx, "async instantiate() started"); + + Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx)); + if (!promise) { + return false; + } + + CallArgs callArgs = CallArgsFromVp(argc, vp); + + RootedObject firstArg(cx); + RootedObject importObj(cx); + if (!GetInstantiateArgs(cx, callArgs, &firstArg, &importObj)) { + return RejectWithPendingException(cx, promise, callArgs); + } + + const Module* module; + if (IsModuleObject(firstArg, &module)) { + if (!AsyncInstantiate(cx, *module, importObj, Ret::Instance, promise)) { + return false; + } + } else { + if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_CSP_BLOCKED_WASM, + "WebAssembly.instantiate"); + return RejectWithPendingException(cx, promise, callArgs); + } + + auto task = cx->make_unique<CompileBufferTask>(cx, promise, importObj); + if (!task || !task->init(cx, "WebAssembly.instantiate")) { + return false; + } + + if (!GetBufferSource(cx, firstArg, JSMSG_WASM_BAD_BUF_MOD_ARG, + &task->bytecode)) { + return RejectWithPendingException(cx, promise, callArgs); + } + + if (!StartOffThreadPromiseHelperTask(cx, std::move(task))) { + return false; + } + } + + callArgs.rval().setObject(*promise); + return true; +} + +static bool WebAssembly_validate(JSContext* cx, unsigned argc, Value* vp) { + CallArgs callArgs = CallArgsFromVp(argc, vp); + + MutableBytes bytecode; + if (!GetBufferSource(cx, callArgs, "WebAssembly.validate", &bytecode)) { + return false; + } + + FeatureOptions options; + UniqueChars error; + bool validated = Validate(cx, *bytecode, options, &error); + + // If the reason for validation failure was OOM (signalled by null error + // message), report out-of-memory so that validate's return is always + // correct. + if (!validated && !error) { + ReportOutOfMemory(cx); + return false; + } + + if (error) { + MOZ_ASSERT(!validated); + Log(cx, "validate() failed with: %s", error.get()); + } + + callArgs.rval().setBoolean(validated); + return true; +} + +static bool EnsureStreamSupport(JSContext* cx) { + // This should match wasm::StreamingCompilationAvailable(). + + if (!EnsurePromiseSupport(cx)) { + return false; + } + + if (!CanUseExtraThreads()) { + JS_ReportErrorASCII( + cx, "WebAssembly.compileStreaming not supported with --no-threads"); + return false; + } + + if (!cx->runtime()->consumeStreamCallback) { + JS_ReportErrorASCII(cx, + "WebAssembly streaming not supported in this runtime"); + return false; + } + + return true; +} + +// This value is chosen and asserted to be disjoint from any host error code. +static const size_t StreamOOMCode = 0; + +static bool RejectWithStreamErrorNumber(JSContext* cx, size_t errorCode, + Handle<PromiseObject*> promise) { + if (errorCode == StreamOOMCode) { + ReportOutOfMemory(cx); + return false; + } + + cx->runtime()->reportStreamErrorCallback(cx, errorCode); + return RejectWithPendingException(cx, promise); +} + +class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer { + // The stream progresses monotonically through these states; the helper + // thread wait()s for streamState_ to reach Closed. + enum StreamState { Env, Code, Tail, Closed }; + ExclusiveWaitableData<StreamState> streamState_; + + // Immutable: + const bool instantiate_; + const PersistentRootedObject importObj_; + + // Immutable after noteResponseURLs() which is called at most once before + // first call on stream thread: + const MutableCompileArgs compileArgs_; + + // Immutable after Env state: + Bytes envBytes_; + SectionRange codeSection_; + + // The code section vector is resized once during the Env state and filled + // in chunk by chunk during the Code state, updating the end-pointer after + // each chunk: + Bytes codeBytes_; + uint8_t* codeBytesEnd_; + ExclusiveBytesPtr exclusiveCodeBytesEnd_; + + // Immutable after Tail state: + Bytes tailBytes_; + ExclusiveStreamEndData exclusiveStreamEnd_; + + // Written once before Closed state and read in Closed state on main thread: + SharedModule module_; + Maybe<size_t> streamError_; + UniqueChars compileError_; + UniqueCharsVector warnings_; + + // Set on stream thread and read racily on helper thread to abort compilation: + Atomic<bool> streamFailed_; + + // Called on some thread before consumeChunk(), streamEnd(), streamError()): + + void noteResponseURLs(const char* url, const char* sourceMapUrl) override { + if (url) { + compileArgs_->scriptedCaller.filename = DuplicateString(url); + compileArgs_->scriptedCaller.filenameIsURL = true; + } + if (sourceMapUrl) { + compileArgs_->sourceMapURL = DuplicateString(sourceMapUrl); + } + } + + // Called on a stream thread: + + // Until StartOffThreadPromiseHelperTask succeeds, we are responsible for + // dispatching ourselves back to the JS thread. + // + // Warning: After this function returns, 'this' can be deleted at any time, so + // the caller must immediately return from the stream callback. + void setClosedAndDestroyBeforeHelperThreadStarted() { + streamState_.lock().get() = Closed; + dispatchResolveAndDestroy(); + } + + // See setClosedAndDestroyBeforeHelperThreadStarted() comment. + bool rejectAndDestroyBeforeHelperThreadStarted(size_t errorNumber) { + MOZ_ASSERT(streamState_.lock() == Env); + MOZ_ASSERT(!streamError_); + streamError_ = Some(errorNumber); + setClosedAndDestroyBeforeHelperThreadStarted(); + return false; + } + + // Once StartOffThreadPromiseHelperTask succeeds, the helper thread will + // dispatchResolveAndDestroy() after execute() returns, but execute() + // wait()s for state to be Closed. + // + // Warning: After this function returns, 'this' can be deleted at any time, so + // the caller must immediately return from the stream callback. + void setClosedAndDestroyAfterHelperThreadStarted() { + auto streamState = streamState_.lock(); + MOZ_ASSERT(streamState != Closed); + streamState.get() = Closed; + streamState.notify_one(/* stream closed */); + } + + // See setClosedAndDestroyAfterHelperThreadStarted() comment. + bool rejectAndDestroyAfterHelperThreadStarted(size_t errorNumber) { + MOZ_ASSERT(!streamError_); + streamError_ = Some(errorNumber); + streamFailed_ = true; + exclusiveCodeBytesEnd_.lock().notify_one(); + exclusiveStreamEnd_.lock().notify_one(); + setClosedAndDestroyAfterHelperThreadStarted(); + return false; + } + + bool consumeChunk(const uint8_t* begin, size_t length) override { + switch (streamState_.lock().get()) { + case Env: { + if (!envBytes_.append(begin, length)) { + return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode); + } + + if (!StartsCodeSection(envBytes_.begin(), envBytes_.end(), + &codeSection_)) { + return true; + } + + uint32_t extraBytes = envBytes_.length() - codeSection_.start; + if (extraBytes) { + envBytes_.shrinkTo(codeSection_.start); + } + + if (codeSection_.size > MaxCodeSectionBytes) { + return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode); + } + + if (!codeBytes_.resize(codeSection_.size)) { + return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode); + } + + codeBytesEnd_ = codeBytes_.begin(); + exclusiveCodeBytesEnd_.lock().get() = codeBytesEnd_; + + if (!StartOffThreadPromiseHelperTask(this)) { + return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode); + } + + // Set the state to Code iff StartOffThreadPromiseHelperTask() + // succeeds so that the state tells us whether we are before or + // after the helper thread started. + streamState_.lock().get() = Code; + + if (extraBytes) { + return consumeChunk(begin + length - extraBytes, extraBytes); + } + + return true; + } + case Code: { + size_t copyLength = + std::min<size_t>(length, codeBytes_.end() - codeBytesEnd_); + memcpy(codeBytesEnd_, begin, copyLength); + codeBytesEnd_ += copyLength; + + { + auto codeStreamEnd = exclusiveCodeBytesEnd_.lock(); + codeStreamEnd.get() = codeBytesEnd_; + codeStreamEnd.notify_one(); + } + + if (codeBytesEnd_ != codeBytes_.end()) { + return true; + } + + streamState_.lock().get() = Tail; + + if (uint32_t extraBytes = length - copyLength) { + return consumeChunk(begin + copyLength, extraBytes); + } + + return true; + } + case Tail: { + if (!tailBytes_.append(begin, length)) { + return rejectAndDestroyAfterHelperThreadStarted(StreamOOMCode); + } + + return true; + } + case Closed: + MOZ_CRASH("consumeChunk() in Closed state"); + } + MOZ_CRASH("unreachable"); + } + + void streamEnd(JS::OptimizedEncodingListener* tier2Listener) override { + switch (streamState_.lock().get()) { + case Env: { + SharedBytes bytecode = js_new<ShareableBytes>(std::move(envBytes_)); + if (!bytecode) { + rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode); + return; + } + module_ = CompileBuffer(*compileArgs_, *bytecode, &compileError_, + &warnings_, nullptr); + setClosedAndDestroyBeforeHelperThreadStarted(); + return; + } + case Code: + case Tail: + // Unlock exclusiveStreamEnd_ before locking streamState_. + { + auto streamEnd = exclusiveStreamEnd_.lock(); + MOZ_ASSERT(!streamEnd->reached); + streamEnd->reached = true; + streamEnd->tailBytes = &tailBytes_; + streamEnd->tier2Listener = tier2Listener; + streamEnd.notify_one(); + } + setClosedAndDestroyAfterHelperThreadStarted(); + return; + case Closed: + MOZ_CRASH("streamEnd() in Closed state"); + } + } + + void streamError(size_t errorCode) override { + MOZ_ASSERT(errorCode != StreamOOMCode); + switch (streamState_.lock().get()) { + case Env: + rejectAndDestroyBeforeHelperThreadStarted(errorCode); + return; + case Tail: + case Code: + rejectAndDestroyAfterHelperThreadStarted(errorCode); + return; + case Closed: + MOZ_CRASH("streamError() in Closed state"); + } + } + + void consumeOptimizedEncoding(const uint8_t* begin, size_t length) override { + module_ = Module::deserialize(begin, length); + + MOZ_ASSERT(streamState_.lock().get() == Env); + setClosedAndDestroyBeforeHelperThreadStarted(); + } + + // Called on a helper thread: + + void execute() override { + module_ = CompileStreaming(*compileArgs_, envBytes_, codeBytes_, + exclusiveCodeBytesEnd_, exclusiveStreamEnd_, + streamFailed_, &compileError_, &warnings_); + + // When execute() returns, the CompileStreamTask will be dispatched + // back to its JS thread to call resolve() and then be destroyed. We + // can't let this happen until the stream has been closed lest + // consumeChunk() or streamEnd() be called on a dead object. + auto streamState = streamState_.lock(); + while (streamState != Closed) { + streamState.wait(/* stream closed */); + } + } + + // Called on a JS thread after streaming compilation completes/errors: + + bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override { + MOZ_ASSERT(streamState_.lock() == Closed); + + if (!ReportCompileWarnings(cx, warnings_)) { + return false; + } + if (module_) { + MOZ_ASSERT(!streamFailed_ && !streamError_ && !compileError_); + if (instantiate_) { + return AsyncInstantiate(cx, *module_, importObj_, Ret::Pair, promise); + } + return ResolveCompile(cx, *module_, promise); + } + + if (streamError_) { + return RejectWithStreamErrorNumber(cx, *streamError_, promise); + } + + return Reject(cx, *compileArgs_, promise, compileError_); + } + + public: + CompileStreamTask(JSContext* cx, Handle<PromiseObject*> promise, + CompileArgs& compileArgs, bool instantiate, + HandleObject importObj) + : PromiseHelperTask(cx, promise), + streamState_(mutexid::WasmStreamStatus, Env), + instantiate_(instantiate), + importObj_(cx, importObj), + compileArgs_(&compileArgs), + codeSection_{}, + codeBytesEnd_(nullptr), + exclusiveCodeBytesEnd_(mutexid::WasmCodeBytesEnd, nullptr), + exclusiveStreamEnd_(mutexid::WasmStreamEnd), + streamFailed_(false) { + MOZ_ASSERT_IF(importObj_, instantiate_); + } +}; + +// A short-lived object that captures the arguments of a +// WebAssembly.{compileStreaming,instantiateStreaming} while waiting for +// the Promise<Response> to resolve to a (hopefully) Promise. +class ResolveResponseClosure : public NativeObject { + static const unsigned COMPILE_ARGS_SLOT = 0; + static const unsigned PROMISE_OBJ_SLOT = 1; + static const unsigned INSTANTIATE_SLOT = 2; + static const unsigned IMPORT_OBJ_SLOT = 3; + static const JSClassOps classOps_; + + static void finalize(JS::GCContext* gcx, JSObject* obj) { + auto& closure = obj->as<ResolveResponseClosure>(); + gcx->release(obj, &closure.compileArgs(), + MemoryUse::WasmResolveResponseClosure); + } + + public: + static const unsigned RESERVED_SLOTS = 4; + static const JSClass class_; + + static ResolveResponseClosure* create(JSContext* cx, const CompileArgs& args, + HandleObject promise, bool instantiate, + HandleObject importObj) { + MOZ_ASSERT_IF(importObj, instantiate); + + AutoSetNewObjectMetadata metadata(cx); + auto* obj = NewObjectWithGivenProto<ResolveResponseClosure>(cx, nullptr); + if (!obj) { + return nullptr; + } + + args.AddRef(); + InitReservedSlot(obj, COMPILE_ARGS_SLOT, const_cast<CompileArgs*>(&args), + MemoryUse::WasmResolveResponseClosure); + obj->setReservedSlot(PROMISE_OBJ_SLOT, ObjectValue(*promise)); + obj->setReservedSlot(INSTANTIATE_SLOT, BooleanValue(instantiate)); + obj->setReservedSlot(IMPORT_OBJ_SLOT, ObjectOrNullValue(importObj)); + return obj; + } + + CompileArgs& compileArgs() const { + return *(CompileArgs*)getReservedSlot(COMPILE_ARGS_SLOT).toPrivate(); + } + PromiseObject& promise() const { + return getReservedSlot(PROMISE_OBJ_SLOT).toObject().as<PromiseObject>(); + } + bool instantiate() const { + return getReservedSlot(INSTANTIATE_SLOT).toBoolean(); + } + JSObject* importObj() const { + return getReservedSlot(IMPORT_OBJ_SLOT).toObjectOrNull(); + } +}; + +const JSClassOps ResolveResponseClosure::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + ResolveResponseClosure::finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +const JSClass ResolveResponseClosure::class_ = { + "WebAssembly ResolveResponseClosure", + JSCLASS_DELAY_METADATA_BUILDER | + JSCLASS_HAS_RESERVED_SLOTS(ResolveResponseClosure::RESERVED_SLOTS) | + JSCLASS_FOREGROUND_FINALIZE, + &ResolveResponseClosure::classOps_, +}; + +static ResolveResponseClosure* ToResolveResponseClosure(CallArgs args) { + return &args.callee() + .as<JSFunction>() + .getExtendedSlot(0) + .toObject() + .as<ResolveResponseClosure>(); +} + +static bool RejectWithErrorNumber(JSContext* cx, uint32_t errorNumber, + Handle<PromiseObject*> promise) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber); + return RejectWithPendingException(cx, promise); +} + +static bool ResolveResponse_OnFulfilled(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs callArgs = CallArgsFromVp(argc, vp); + + Rooted<ResolveResponseClosure*> closure(cx, + ToResolveResponseClosure(callArgs)); + Rooted<PromiseObject*> promise(cx, &closure->promise()); + CompileArgs& compileArgs = closure->compileArgs(); + bool instantiate = closure->instantiate(); + Rooted<JSObject*> importObj(cx, closure->importObj()); + + auto task = cx->make_unique<CompileStreamTask>(cx, promise, compileArgs, + instantiate, importObj); + if (!task || !task->init(cx)) { + return false; + } + + if (!callArgs.get(0).isObject()) { + return RejectWithErrorNumber(cx, JSMSG_WASM_BAD_RESPONSE_VALUE, promise); + } + + RootedObject response(cx, &callArgs.get(0).toObject()); + if (!cx->runtime()->consumeStreamCallback(cx, response, JS::MimeType::Wasm, + task.get())) { + return RejectWithPendingException(cx, promise); + } + + (void)task.release(); + + callArgs.rval().setUndefined(); + return true; +} + +static bool ResolveResponse_OnRejected(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted<ResolveResponseClosure*> closure(cx, ToResolveResponseClosure(args)); + Rooted<PromiseObject*> promise(cx, &closure->promise()); + + if (!PromiseObject::reject(cx, promise, args.get(0))) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +static bool ResolveResponse(JSContext* cx, CallArgs callArgs, + Handle<PromiseObject*> promise, + bool instantiate = false, + HandleObject importObj = nullptr) { + MOZ_ASSERT_IF(importObj, instantiate); + + const char* introducer = instantiate ? "WebAssembly.instantiateStreaming" + : "WebAssembly.compileStreaming"; + + SharedCompileArgs compileArgs = InitCompileArgs(cx, introducer); + if (!compileArgs) { + return false; + } + + RootedObject closure( + cx, ResolveResponseClosure::create(cx, *compileArgs, promise, instantiate, + importObj)); + if (!closure) { + return false; + } + + RootedFunction onResolved( + cx, NewNativeFunction(cx, ResolveResponse_OnFulfilled, 1, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); + if (!onResolved) { + return false; + } + + RootedFunction onRejected( + cx, NewNativeFunction(cx, ResolveResponse_OnRejected, 1, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); + if (!onRejected) { + return false; + } + + onResolved->setExtendedSlot(0, ObjectValue(*closure)); + onRejected->setExtendedSlot(0, ObjectValue(*closure)); + + RootedObject resolve(cx, + PromiseObject::unforgeableResolve(cx, callArgs.get(0))); + if (!resolve) { + return false; + } + + return JS::AddPromiseReactions(cx, resolve, onResolved, onRejected); +} + +static bool WebAssembly_compileStreaming(JSContext* cx, unsigned argc, + Value* vp) { + if (!EnsureStreamSupport(cx)) { + return false; + } + + Log(cx, "async compileStreaming() started"); + + Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx)); + if (!promise) { + return false; + } + + CallArgs callArgs = CallArgsFromVp(argc, vp); + + if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_CSP_BLOCKED_WASM, + "WebAssembly.compileStreaming"); + return RejectWithPendingException(cx, promise, callArgs); + } + + if (!ResolveResponse(cx, callArgs, promise)) { + return RejectWithPendingException(cx, promise, callArgs); + } + + callArgs.rval().setObject(*promise); + return true; +} + +static bool WebAssembly_instantiateStreaming(JSContext* cx, unsigned argc, + Value* vp) { + if (!EnsureStreamSupport(cx)) { + return false; + } + + Log(cx, "async instantiateStreaming() started"); + + Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx)); + if (!promise) { + return false; + } + + CallArgs callArgs = CallArgsFromVp(argc, vp); + + if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_CSP_BLOCKED_WASM, + "WebAssembly.instantiateStreaming"); + return RejectWithPendingException(cx, promise, callArgs); + } + + RootedObject firstArg(cx); + RootedObject importObj(cx); + if (!GetInstantiateArgs(cx, callArgs, &firstArg, &importObj)) { + return RejectWithPendingException(cx, promise, callArgs); + } + + if (!ResolveResponse(cx, callArgs, promise, true, importObj)) { + return RejectWithPendingException(cx, promise, callArgs); + } + + callArgs.rval().setObject(*promise); + return true; +} + +#ifdef ENABLE_WASM_MOZ_INTGEMM + +static bool WebAssembly_mozIntGemm(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted<WasmModuleObject*> module(cx); + wasm::IntrinsicId ids[] = { + wasm::IntrinsicId::I8PrepareB, + wasm::IntrinsicId::I8PrepareBFromTransposed, + wasm::IntrinsicId::I8PrepareBFromQuantizedTransposed, + wasm::IntrinsicId::I8PrepareA, + wasm::IntrinsicId::I8PrepareBias, + wasm::IntrinsicId::I8MultiplyAndAddBias, + wasm::IntrinsicId::I8SelectColumnsOfB}; + if (!wasm::CompileIntrinsicModule(cx, ids, Shareable::False, &module)) { + ReportOutOfMemory(cx); + return false; + } + args.rval().set(ObjectValue(*module.get())); + return true; +} + +static const JSFunctionSpec WebAssembly_mozIntGemm_methods[] = { + JS_FN("mozIntGemm", WebAssembly_mozIntGemm, 0, JSPROP_ENUMERATE), + JS_FS_END}; + +#endif // ENABLE_WASM_MOZ_INTGEMM + +static const JSFunctionSpec WebAssembly_static_methods[] = { + JS_FN(js_toSource_str, WebAssembly_toSource, 0, 0), + JS_FN("compile", WebAssembly_compile, 1, JSPROP_ENUMERATE), + JS_FN("instantiate", WebAssembly_instantiate, 1, JSPROP_ENUMERATE), + JS_FN("validate", WebAssembly_validate, 1, JSPROP_ENUMERATE), + JS_FN("compileStreaming", WebAssembly_compileStreaming, 1, + JSPROP_ENUMERATE), + JS_FN("instantiateStreaming", WebAssembly_instantiateStreaming, 1, + JSPROP_ENUMERATE), + JS_FS_END}; + +static const JSPropertySpec WebAssembly_static_properties[] = { + JS_STRING_SYM_PS(toStringTag, "WebAssembly", JSPROP_READONLY), JS_PS_END}; + +static JSObject* CreateWebAssemblyObject(JSContext* cx, JSProtoKey key) { + MOZ_RELEASE_ASSERT(HasSupport(cx)); + + RootedObject proto(cx, &cx->global()->getObjectPrototype()); + return NewTenuredObjectWithGivenProto(cx, &WasmNamespaceObject::class_, + proto); +} + +struct NameAndProtoKey { + const char* const name; + JSProtoKey key; +}; + +static bool WebAssemblyDefineConstructor(JSContext* cx, + Handle<WasmNamespaceObject*> wasm, + NameAndProtoKey entry, + MutableHandleValue ctorValue, + MutableHandleId id) { + JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, entry.key); + if (!ctor) { + return false; + } + ctorValue.setObject(*ctor); + + JSAtom* className = Atomize(cx, entry.name, strlen(entry.name)); + if (!className) { + return false; + } + id.set(AtomToId(className)); + + return DefineDataProperty(cx, wasm, id, ctorValue, 0); +} + +static bool WebAssemblyClassFinish(JSContext* cx, HandleObject object, + HandleObject proto) { + Handle<WasmNamespaceObject*> wasm = object.as<WasmNamespaceObject>(); + + constexpr NameAndProtoKey entries[] = { + {"Module", JSProto_WasmModule}, + {"Instance", JSProto_WasmInstance}, + {"Memory", JSProto_WasmMemory}, + {"Table", JSProto_WasmTable}, + {"Global", JSProto_WasmGlobal}, + {"CompileError", GetExceptionProtoKey(JSEXN_WASMCOMPILEERROR)}, + {"LinkError", GetExceptionProtoKey(JSEXN_WASMLINKERROR)}, + {"RuntimeError", GetExceptionProtoKey(JSEXN_WASMRUNTIMEERROR)}, +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + {"Function", JSProto_WasmFunction}, +#endif + }; + RootedValue ctorValue(cx); + RootedId id(cx); + for (const auto& entry : entries) { + if (!WebAssemblyDefineConstructor(cx, wasm, entry, &ctorValue, &id)) { + return false; + } + } + + if (ExceptionsAvailable(cx)) { + constexpr NameAndProtoKey exceptionEntries[] = { + {"Tag", JSProto_WasmTag}, + {"Exception", JSProto_WasmException}, + }; + for (const auto& entry : exceptionEntries) { + if (!WebAssemblyDefineConstructor(cx, wasm, entry, &ctorValue, &id)) { + return false; + } + } + } + +#ifdef ENABLE_WASM_MOZ_INTGEMM + if (MozIntGemmAvailable(cx) && + !JS_DefineFunctions(cx, wasm, WebAssembly_mozIntGemm_methods)) { + return false; + } +#endif + + return true; +} + +static const ClassSpec WebAssemblyClassSpec = { + CreateWebAssemblyObject, nullptr, WebAssembly_static_methods, + WebAssembly_static_properties, nullptr, nullptr, + WebAssemblyClassFinish}; + +const JSClass js::WasmNamespaceObject::class_ = { + js_WebAssembly_str, JSCLASS_HAS_CACHED_PROTO(JSProto_WebAssembly), + JS_NULL_CLASS_OPS, &WebAssemblyClassSpec}; + +// Sundry |