/* -*- 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 #include "gc/FreeOp.h" #include "jit/AtomicOperations.h" #include "jit/JitOptions.h" #include "jit/JitRuntime.h" #include "jit/Simulator.h" #if defined(JS_CODEGEN_X64) // Assembler::HasSSE41 # include "jit/x64/Assembler-x64.h" # include "jit/x86-shared/Architecture-x86-shared.h" # include "jit/x86-shared/Assembler-x86-shared.h" #endif #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/Printf.h" #include "js/PropertySpec.h" // JS_{PS,FN}{,_END} #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/PlainObject.h" // js::PlainObject #include "vm/PromiseObject.h" // js::PromiseObject #include "vm/StringType.h" #include "vm/Warnings.h" // js::WarnNumberASCII #include "wasm/WasmBaselineCompile.h" #include "wasm/WasmBuiltins.h" #include "wasm/WasmCompile.h" #include "wasm/WasmCraneliftCompile.h" #include "wasm/WasmInstance.h" #include "wasm/WasmIonCompile.h" #include "wasm/WasmModule.h" #include "wasm/WasmProcess.h" #include "wasm/WasmSignalHandlers.h" #include "wasm/WasmStubs.h" #include "wasm/WasmValidate.h" #include "vm/ArrayBufferObject-inl.h" #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" using namespace js; using namespace js::jit; using namespace js::wasm; using mozilla::CheckedInt; using mozilla::Nothing; using mozilla::RangedPtr; using mozilla::Span; extern mozilla::Atomic fuzzingSafe; // 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 IsFuzzing() { #ifdef FUZZING return true; #else return fuzzingSafe; #endif } static inline bool IsFuzzingIon(JSContext* cx) { return IsFuzzing() && !cx->options().wasmBaseline() && cx->options().wasmIon() && !cx->options().wasmCranelift(); } static inline bool IsFuzzingCranelift(JSContext* cx) { return IsFuzzing() && !cx->options().wasmBaseline() && !cx->options().wasmIon() && cx->options().wasmCranelift(); } // 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 WasmMultiValueFlag(JSContext* cx) { #ifdef ENABLE_WASM_MULTI_VALUE return cx->options().wasmMultiValue(); #else return false; #endif } static inline bool WasmSimdFlag(JSContext* cx) { #ifdef ENABLE_WASM_SIMD if (IsFuzzingCranelift(cx)) { return false; } return cx->options().wasmSimd() && js::jit::JitSupportsWasmSimd(); #else return false; #endif } static inline bool WasmSimdWormholeFlag(JSContext* cx) { #ifdef ENABLE_WASM_SIMD_WORMHOLE return cx->options().wasmSimdWormhole(); #else return false; #endif } static inline bool WasmReftypesFlag(JSContext* cx) { #ifdef ENABLE_WASM_REFTYPES return cx->options().wasmReftypes(); #else return false; #endif } static inline bool WasmFunctionReferencesFlag(JSContext* cx) { if (IsFuzzingIon(cx) || IsFuzzingCranelift(cx)) { return false; } #ifdef ENABLE_WASM_FUNCTION_REFERENCES return WasmReftypesFlag(cx) && cx->options().wasmFunctionReferences(); #else return false; #endif } static inline bool WasmGcFlag(JSContext* cx) { if (IsFuzzingIon(cx) || IsFuzzingCranelift(cx)) { return false; } #ifdef ENABLE_WASM_GC return WasmFunctionReferencesFlag(cx) && cx->options().wasmGc(); #else return false; #endif } static inline bool WasmThreadsFlag(JSContext* cx) { return cx->realm() && cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled(); } static inline bool WasmExceptionsFlag(JSContext* cx) { #ifdef ENABLE_WASM_EXCEPTIONS return cx->options().wasmExceptions(); #else return false; #endif } static inline bool WasmDebuggerActive(JSContext* cx) { if (IsFuzzingIon(cx) || IsFuzzingCranelift(cx)) { return false; } return cx->realm() && cx->realm()->debuggerObservesAsmJS(); } /* * [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 two, "ion" aka "Baldr", and "Cranelift". * * * 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. * * As an example: * * On ARM64 the default compilers are Baseline and Cranelift. Say Cranelift * does not support feature X. Thus X cannot be enabled by default on ARM64. * However, X support can be compiled-in to SpiderMonkey, and the user can opt * to enable X. Doing so will disable Cranelift. * * In contrast, X can be enabled by default on x64, where the default * compilers are Baseline and Ion, both of which support X. * * A subtlety is worth noting: on x64, enabling Cranelift (thus disabling Ion) * will not disable X. Instead, the presence of X in the selected feature set * will disable Cranelift, leaving only Baseline. This follows from the logic * described above. * * In a shell build, the testing functions wasmCompilersPresent, * wasmCompileMode, wasmCraneliftDisabledByFeatures, 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. // // There's a static precedence order between the optimizing compilers. This // order currently ranks Cranelift over Ion on all platforms because Cranelift // is disabled by default on all platforms: anyone who has enabled Cranelift // will wish to use it instead of Ion. // // The precedence order is implemented by guards in IonAvailable() and // CraneliftAvailable(). We expect that it will become more complex as the // default settings change. But it should remain static. bool wasm::BaselineAvailable(JSContext* cx) { // Baseline supports every feature supported by any compiler. return cx->options().wasmBaseline() && BaselinePlatformSupport(); } bool wasm::IonAvailable(JSContext* cx) { if (!cx->options().wasmIon() || !IonPlatformSupport()) { return false; } bool isDisabled = false; MOZ_ALWAYS_TRUE(IonDisabledByFeatures(cx, &isDisabled)); return !isDisabled && !CraneliftAvailable(cx); } template 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::IonDisabledByFeatures(JSContext* cx, bool* isDisabled, JSStringBuilder* reason) { // Ion has no debugging support, no gc support. bool debug = WasmDebuggerActive(cx); bool functionReferences = WasmFunctionReferencesFlag(cx); bool gc = WasmGcFlag(cx); bool exn = WasmExceptionsFlag(cx); if (reason) { char sep = 0; if (debug && !Append(reason, "debug", &sep)) { return false; } if (functionReferences && !Append(reason, "function-references", &sep)) { return false; } if (gc && !Append(reason, "gc", &sep)) { return false; } if (exn && !Append(reason, "exceptions", &sep)) { return false; } } *isDisabled = debug || functionReferences || gc || exn; return true; } bool wasm::CraneliftAvailable(JSContext* cx) { if (!cx->options().wasmCranelift() || !CraneliftPlatformSupport()) { return false; } bool isDisabled = false; MOZ_ALWAYS_TRUE(CraneliftDisabledByFeatures(cx, &isDisabled)); return !isDisabled; } bool wasm::CraneliftDisabledByFeatures(JSContext* cx, bool* isDisabled, JSStringBuilder* reason) { // Cranelift has no debugging support, no gc support, no simd, and // no exceptions support. bool debug = WasmDebuggerActive(cx); bool functionReferences = WasmFunctionReferencesFlag(cx); bool gc = WasmGcFlag(cx); #ifdef JS_CODEGEN_ARM64 // Cranelift aarch64 has full SIMD support. bool simdOnNonAarch64 = false; #else bool simdOnNonAarch64 = WasmSimdFlag(cx); #endif bool exn = WasmExceptionsFlag(cx); if (reason) { char sep = 0; if (debug && !Append(reason, "debug", &sep)) { return false; } if (functionReferences && !Append(reason, "function-references", &sep)) { return false; } if (gc && !Append(reason, "gc", &sep)) { return false; } if (simdOnNonAarch64 && !Append(reason, "simd", &sep)) { return false; } if (exn && !Append(reason, "exceptions", &sep)) { return false; } } *isDisabled = debug || functionReferences || gc || simdOnNonAarch64 || exn; return true; } bool wasm::AnyCompilerAvailable(JSContext* cx) { return wasm::BaselineAvailable(cx) || wasm::IonAvailable(cx) || wasm::CraneliftAvailable(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. bool wasm::ReftypesAvailable(JSContext* cx) { // All compilers support reference types. return WasmReftypesFlag(cx) && AnyCompilerAvailable(cx); } bool wasm::FunctionReferencesAvailable(JSContext* cx) { // Cranelift and Ion do not support function-references. return WasmFunctionReferencesFlag(cx) && BaselineAvailable(cx); } bool wasm::GcTypesAvailable(JSContext* cx) { // Cranelift and Ion do not support GC. return WasmGcFlag(cx) && BaselineAvailable(cx); } bool wasm::MultiValuesAvailable(JSContext* cx) { return WasmMultiValueFlag(cx) && AnyCompilerAvailable(cx); } bool wasm::SimdAvailable(JSContext* cx) { return WasmSimdFlag(cx) && (BaselineAvailable(cx) || IonAvailable(cx) || CraneliftAvailable(cx)); } bool wasm::SimdWormholeAvailable(JSContext* cx) { return WasmSimdWormholeFlag(cx) && SimdAvailable(cx) && IonAvailable(cx) && !BaselineAvailable(cx) && !CraneliftAvailable(cx); } bool wasm::ThreadsAvailable(JSContext* cx) { return WasmThreadsFlag(cx) && AnyCompilerAvailable(cx); } bool wasm::ExceptionsAvailable(JSContext* cx) { // Ion & Cranelift do not support Exceptions (for now). // Exceptions require multi-value. return WasmExceptionsFlag(cx) && MultiValuesAvailable(cx) && BaselineAvailable(cx); } bool wasm::HasPlatformSupport(JSContext* cx) { #if !MOZ_LITTLE_ENDIAN() || defined(JS_CODEGEN_NONE) return false; #endif if (gc::SystemPageSize() > wasm::PageSize) { return false; } if (!JitOptions.supportsFloatingPoint) { return false; } if (!JitOptions.supportsUnalignedAccesses) { return false; } if (!wasm::EnsureFullSignalHandlers(cx)) { return false; } if (!jit::JitSupportsAtomics()) { return false; } // Wasm threads require 8-byte lock-free atomics. if (!jit::AtomicOperations::isLockfree8()) { return false; } // Lazily initialize the global type context if (!cx->wasm().ensureTypeContext(cx)) { return false; } // Test only whether the compilers are supported on the hardware, not whether // they are enabled. return BaselinePlatformSupport() || IonPlatformSupport() || CraneliftPlatformSupport(); } 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) { // 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); } // As the return values from the underlying buffer accessors will become size_t // before long, they are captured as size_t here. uint32_t wasm::ByteLength32(Handle buffer) { size_t len = buffer->byteLength().get(); MOZ_ASSERT(len <= size_t(MaxMemory32Pages) * PageSize); return uint32_t(len); } uint32_t wasm::ByteLength32(const ArrayBufferObjectMaybeShared& buffer) { size_t len = buffer.byteLength().get(); MOZ_ASSERT(len <= size_t(MaxMemory32Pages) * PageSize); return uint32_t(len); } uint32_t wasm::ByteLength32(const WasmArrayRawBuffer* buffer) { size_t len = buffer->byteLength().get(); MOZ_ASSERT(len <= size_t(MaxMemory32Pages) * PageSize); return uint32_t(len); } uint32_t wasm::ByteLength32(const ArrayBufferObject& buffer) { size_t len = buffer.byteLength().get(); MOZ_ASSERT(len <= size_t(MaxMemory32Pages) * PageSize); return uint32_t(len); } uint32_t wasm::VolatileByteLength32(const SharedArrayRawBuffer* buffer) { size_t len = buffer->volatileByteLength().get(); MOZ_ASSERT(len <= size_t(MaxMemory32Pages) * PageSize); return uint32_t(len); } // ============================================================================ // Imports static bool ThrowBadImportArg(JSContext* cx) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMPORT_ARG); return false; } static bool ThrowBadImportType(JSContext* cx, const char* field, const char* str) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMPORT_TYPE, field, str); return false; } static bool GetProperty(JSContext* cx, HandleObject obj, const char* chars, MutableHandleValue v) { JSAtom* atom = AtomizeUTF8Chars(cx, chars, strlen(chars)); if (!atom) { return false; } RootedId id(cx, AtomToId(atom)); return GetProperty(cx, obj, obj, id, v); } 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(); #ifdef ENABLE_WASM_EXCEPTIONS uint32_t eventIndex = 0; const EventDescVector& events = metadata.events; #endif uint32_t globalIndex = 0; const GlobalDescVector& globals = metadata.globals; uint32_t tableIndex = 0; const TableDescVector& tables = metadata.tables; for (const Import& import : module.imports()) { RootedValue v(cx); if (!GetProperty(cx, importObj, import.module.get(), &v)) { return false; } if (!v.isObject()) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMPORT_FIELD, import.module.get()); return false; } RootedObject obj(cx, &v.toObject()); if (!GetProperty(cx, obj, import.field.get(), &v)) { return false; } switch (import.kind) { case DefinitionKind::Function: { if (!IsFunctionObject(v)) { return ThrowBadImportType(cx, import.field.get(), "Function"); } if (!imports->funcs.append(&v.toObject().as())) { return false; } break; } case DefinitionKind::Table: { const uint32_t index = tableIndex++; if (!v.isObject() || !v.toObject().is()) { return ThrowBadImportType(cx, import.field.get(), "Table"); } RootedWasmTableObject obj(cx, &v.toObject().as()); 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()) { return ThrowBadImportType(cx, import.field.get(), "Memory"); } MOZ_ASSERT(!imports->memory); imports->memory = &v.toObject().as(); break; } #ifdef ENABLE_WASM_EXCEPTIONS case DefinitionKind::Event: { const uint32_t index = eventIndex++; if (!v.isObject() || !v.toObject().is()) { return ThrowBadImportType(cx, import.field.get(), "Exception"); } RootedWasmExceptionObject obj(cx, &v.toObject().as()); // Checks whether the signature of the imported exception object matches // the signature declared in the exception import's EventDesc. if (obj->resultType() != events[index].resultType()) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_EXN_SIG, import.module.get(), import.field.get()); return false; } if (!imports->exceptionObjs.append(obj)) { ReportOutOfMemory(cx); return false; } break; } #endif 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()) { RootedWasmGlobalObject obj(cx, &v.toObject().as()); 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 (IsNumberType(global.type())) { if (global.type() == ValType::I64 && !v.isBigInt()) { return ThrowBadImportType(cx, import.field.get(), "BigInt"); } if (global.type() != ValType::I64 && !v.isNumber()) { return ThrowBadImportType(cx, import.field.get(), "Number"); } } else { MOZ_ASSERT(global.type().isReference()); if (!global.type().isExternRef() && !v.isObjectOrNull()) { return ThrowBadImportType(cx, import.field.get(), "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(cx, af.get(), caller->line, introducer); if (!caller->filename) { return false; } } return true; } // ============================================================================ // Testing / Fuzzing support bool wasm::Eval(JSContext* cx, Handle code, HandleObject importObj, MutableHandleWasmInstanceObject instanceObj) { if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly)) { return false; } MutableBytes bytecode = cx->new_(); if (!bytecode) { return false; } if (!bytecode->append((uint8_t*)code->dataPointerEither().unwrap(), code->byteLength().get())) { ReportOutOfMemory(cx); return false; } ScriptedCaller scriptedCaller; if (!DescribeScriptedCaller(cx, &scriptedCaller, "wasm_eval")) { return false; } SharedCompileArgs compileArgs = CompileArgs::build(cx, std::move(scriptedCaller)); if (!compileArgs) { return false; } UniqueChars error; UniqueCharsVector warnings; JSTelemetrySender sender(cx->runtime()); SharedModule module = CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr, sender); if (!module) { if (error) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR, error.get()); return false; } ReportOutOfMemory(cx); return false; } Rooted 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 called = false; Bytes* serialized; explicit SerializeListener(Bytes* serialized) : serialized(serialized) {} void storeOptimizedEncoding(JS::UniqueOptimizedEncodingBytes bytes) override { MOZ_ASSERT(!called); called = true; if (serialized->resize(bytes->length())) { memcpy(serialized->begin(), bytes->begin(), bytes->length()); } } }; bool wasm::CompileAndSerialize(const ShareableBytes& bytecode, Bytes* serialized) { MutableCompileArgs compileArgs = js_new(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; // 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; // The caller must ensure that huge memory support is configured the same in // the receiving process of this serialized module. compileArgs->features.hugeMemory = wasm::IsHugeMemoryEnabled(); 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. static bool EnforceRangeU32(JSContext* cx, HandleValue v, const char* kind, const char* noun, uint32_t* u32) { // Step 4. double x; if (!ToNumber(cx, v, &x)) { return false; } // Step 5. if (mozilla::IsNegativeZero(x)) { x = 0.0; } // Step 6.1. if (!mozilla::IsFinite(x)) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_UINT32, kind, noun); return false; } // Step 6.2. x = JS::ToInteger(x); // Step 6.3. if (x < 0 || x > double(UINT32_MAX)) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_UINT32, kind, noun); return false; } *u32 = uint32_t(x); MOZ_ASSERT(double(*u32) == x); return true; } static bool GetLimits(JSContext* cx, HandleObject obj, uint32_t maximumField, const char* kind, Limits* limits, Shareable allowShared) { JSAtom* initialAtom = Atomize(cx, "initial", strlen("initial")); if (!initialAtom) { return false; } RootedId initialId(cx, AtomToId(initialAtom)); RootedValue initialVal(cx); if (!GetProperty(cx, obj, obj, initialId, &initialVal)) { return false; } uint32_t initial = 0; if (!initialVal.isUndefined() && !EnforceRangeU32(cx, initialVal, kind, "initial size", &initial)) { return false; } limits->initial = initial; if (limits->initial > maximumField) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE, kind, "initial size"); return false; } #ifdef ENABLE_WASM_TYPE_REFLECTIONS // Get minimum parameter. JSAtom* minimumAtom = Atomize(cx, "minimum", strlen("minimum")); if (!minimumAtom) { return false; } RootedId minimumId(cx, AtomToId(minimumAtom)); RootedValue minimumVal(cx); if (!GetProperty(cx, obj, obj, minimumId, &minimumVal)) { return false; } uint32_t minimum = 0; if (!minimumVal.isUndefined() && !EnforceRangeU32(cx, minimumVal, kind, "initial size", &minimum)) { return false; } if (!minimumVal.isUndefined()) { limits->initial = minimum; } #endif // Get maximum parameter. JSAtom* maximumAtom = Atomize(cx, "maximum", strlen("maximum")); if (!maximumAtom) { return false; } RootedId maximumId(cx, AtomToId(maximumAtom)); RootedValue maxVal(cx); if (!GetProperty(cx, obj, obj, maximumId, &maxVal)) { return false; } // maxVal does not have a default value. if (!maxVal.isUndefined()) { uint32_t maximum = 0; if (!EnforceRangeU32(cx, maxVal, kind, "maximum size", &maximum)) { return false; } limits->maximum = Some(maximum); if (*limits->maximum > maximumField || limits->initial > *limits->maximum) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE, kind, "maximum size"); return false; } } limits->shared = Shareable::False; if (allowShared == Shareable::True) { 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 (maxVal.isUndefined()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_MISSING_MAXIMUM, kind); return false; } if (!cx->realm() ->creationOptions() .getSharedMemoryAndAtomicsEnabled()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_SHMEM_LINK); return false; } } } } #ifdef ENABLE_WASM_TYPE_REFLECTIONS // Check both minimum and initial are not supplied. if (minimumVal.isUndefined() == initialVal.isUndefined()) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_SUPPLY_ONLY_ONE, "minimum", "initial"); return false; } #else if (initialVal.isUndefined()) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_MISSING_REQUIRED, "initial"); return false; } #endif return true; } template static JSObject* CreateWasmConstructor(JSContext* cx, JSProtoKey key) { RootedAtom className(cx, Atomize(cx, name, strlen(name))); if (!className) { return nullptr; } return NewNativeConstructor(cx, Class::construct, 1, className); } // ============================================================================ // 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, // hasInstance 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, GenericCreatePrototype, 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(JSFreeOp* fop, JSObject* obj) { const Module& module = obj->as().module(); obj->zone()->decJitMemory(module.codeLength(module.code().stableTier())); fop->release(obj, &module, module.gcMallocBytesExcludingCode(), MemoryUse::WasmModule); } static bool IsModuleObject(JSObject* obj, const Module** module) { WasmModuleObject* mobj = obj->maybeUnwrapIf(); 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 { RootedPropertyName kind; RootedPropertyName table; RootedPropertyName memory; RootedPropertyName event; RootedPropertyName signature; explicit KindNames(JSContext* cx) : kind(cx), table(cx), memory(cx), event(cx), signature(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(); #ifdef ENABLE_WASM_EXCEPTIONS JSAtom* event = Atomize(cx, "event", strlen("event")); if (!event) { return false; } names->event = event->asPropertyName(); #endif JSAtom* signature = Atomize(cx, "signature", strlen("signature")); if (!signature) { return false; } names->signature = signature->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; #ifdef ENABLE_WASM_EXCEPTIONS case DefinitionKind::Event: return names.event; #endif } MOZ_CRASH("invalid kind"); } static JSString* FuncTypeToString(JSContext* cx, const FuncType& funcType) { JSStringBuilder buf(cx); if (!buf.append('(')) { return nullptr; } bool first = true; for (ValType arg : funcType.args()) { if (!first && !buf.append(", ", strlen(", "))) { return nullptr; } UniqueChars argStr = ToString(arg); if (!argStr) { return nullptr; } if (!buf.append(argStr.get(), strlen(argStr.get()))) { return nullptr; } first = false; } if (!buf.append(") -> (", strlen(") -> ("))) { return nullptr; } first = true; for (ValType result : funcType.results()) { if (!first && !buf.append(", ", strlen(", "))) { return nullptr; } UniqueChars resultStr = ToString(result); if (!resultStr) { return nullptr; } if (!buf.append(resultStr.get(), strlen(resultStr.get()))) { return nullptr; } first = false; } if (!buf.append(')')) { return nullptr; } return buf.finishString(); } static JSString* UTF8CharsToString(JSContext* cx, const char* chars) { return NewStringCopyUTF8Z(cx, JS::ConstUTF8CharsZ(chars, strlen(chars))); } /* 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; } const FuncImportVector& funcImports = module->metadata(module->code().stableTier()).funcImports; size_t numFuncImport = 0; for (const Import& import : module->imports()) { Rooted props(cx, IdValueVector(cx)); if (!props.reserve(3)) { return false; } JSString* moduleStr = UTF8CharsToString(cx, import.module.get()); if (!moduleStr) { return false; } props.infallibleAppend( IdValuePair(NameToId(cx->names().module), StringValue(moduleStr))); JSString* nameStr = UTF8CharsToString(cx, import.field.get()); 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))); if (fuzzingSafe && import.kind == DefinitionKind::Function) { JSString* ftStr = FuncTypeToString(cx, funcImports[numFuncImport++].funcType()); if (!ftStr) { return false; } if (!props.append( IdValuePair(NameToId(names.signature), StringValue(ftStr)))) { return false; } } JSObject* obj = NewPlainObjectWithProperties(cx, props.begin(), props.length(), GenericObject); 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; } for (const Export& exp : module->exports()) { Rooted props(cx, IdValueVector(cx)); if (!props.reserve(2)) { return false; } JSString* nameStr = UTF8CharsToString(cx, exp.fieldName()); 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))); if (fuzzingSafe && exp.kind() == DefinitionKind::Function) { const FuncExport& fe = module->metadata(module->code().stableTier()) .lookupFuncExport(exp.funcIndex()); JSString* ftStr = FuncTypeToString(cx, fe.funcType()); if (!ftStr) { return false; } if (!props.append( IdValuePair(NameToId(names.signature), StringValue(ftStr)))) { return false; } } JSObject* obj = NewPlainObjectWithProperties(cx, props.begin(), props.length(), GenericObject); 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 name(cx); { RootedString str(cx, ToString(cx, args.get(1))); if (!str) { return false; } Rooted linear(cx, str->ensureLinear(cx)); if (!linear) { return false; } if (!name.initLengthUninitialized( JS::GetDeflatedUTF8StringLength(linear))) { return false; } mozilla::Unused << 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())) { continue; } buf = ArrayBufferObject::createZeroed(cx, BufferSize(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(cx, proto); if (!obj) { return nullptr; } // 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.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_(); if (!*bytecode) { return false; } JSObject* unwrapped = CheckedUnwrapStatic(obj); SharedMem 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 SharedCompileArgs InitCompileArgs(JSContext* cx, const char* introducer) { ScriptedCaller scriptedCaller; if (!DescribeScriptedCaller(cx, &scriptedCaller, introducer)) { return nullptr; } return CompileArgs::build(cx, std::move(scriptedCaller)); } static bool ReportCompileWarnings(JSContext* cx, const UniqueCharsVector& warnings) { // Avoid spamming the console. size_t numWarnings = std::min(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 (!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; JSTelemetrySender sender(cx->runtime()); SharedModule module = CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr, sender); if (!module) { if (error) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR, error.get()); return false; } ReportOutOfMemory(cx); return false; } if (!ReportCompileWarnings(cx, warnings)) { return false; } RootedObject proto(cx); if (!GetPrototypeFromBuiltinConstructor(cx, callArgs, JSProto_WasmModule, &proto)) { return false; } if (!proto) { proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmModule); } 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()); 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, // hasInstance 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, GenericCreatePrototype, WasmInstanceObject::static_methods, nullptr, WasmInstanceObject::methods, WasmInstanceObject::properties, nullptr, ClassSpec::DontDefineConstructor}; static bool IsInstance(HandleValue v) { return v.isObject() && v.toObject().is(); } /* static */ bool WasmInstanceObject::exportsGetterImpl(JSContext* cx, const CallArgs& args) { args.rval().setObject( args.thisv().toObject().as().exportsObj()); return true; } /* static */ bool WasmInstanceObject::exportsGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(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()); 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, DefaultHasher, ZoneAllocPolicy>>; class WasmInstanceObject::UnspecifiedScopeMap { public: WasmFunctionScopeMap& asWasmFunctionScopeMap() { return *(WasmFunctionScopeMap*)this; } }; /* static */ void WasmInstanceObject::finalize(JSFreeOp* fop, JSObject* obj) { WasmInstanceObject& instance = obj->as(); fop->delete_(obj, &instance.exports(), MemoryUse::WasmInstanceExports); fop->delete_(obj, &instance.scopes().asWasmFunctionScopeMap(), MemoryUse::WasmInstanceScopes); fop->delete_(obj, &instance.indirectGlobals(), MemoryUse::WasmInstanceGlobals); if (!instance.isNewborn()) { if (instance.instance().debugEnabled()) { instance.instance().debug().finalize(fop); } fop->delete_(obj, &instance.instance(), MemoryUse::WasmInstanceInstance); } } /* static */ void WasmInstanceObject::trace(JSTracer* trc, JSObject* obj) { WasmInstanceObject& instanceObj = obj->as(); instanceObj.exports().trace(trc); instanceObj.indirectGlobals().trace(trc); if (!instanceObj.isNewborn()) { instanceObj.instance().tracePrivate(trc); } } /* static */ WasmInstanceObject* WasmInstanceObject::create( JSContext* cx, SharedCode code, const DataSegmentVector& dataSegments, const ElemSegmentVector& elemSegments, UniqueTlsData tlsData, HandleWasmMemoryObject memory, SharedExceptionTagVector&& exceptionTags, SharedTableVector&& tables, const JSFunctionVector& funcImports, const GlobalDescVector& globals, const ValVector& globalImportValues, const WasmGlobalObjectVector& globalObjs, HandleObject proto, UniqueDebugState maybeDebug) { UniquePtr exports = js::MakeUnique(cx->zone()); if (!exports) { ReportOutOfMemory(cx); return nullptr; } UniquePtr scopes = js::MakeUnique(cx->zone(), cx->zone()); if (!scopes) { ReportOutOfMemory(cx); return nullptr; } uint32_t indirectGlobals = 0; for (uint32_t i = 0; i < globalObjs.length(); i++) { if (globalObjs[i] && globals[i].isIndirect()) { indirectGlobals++; } } Rooted> indirectGlobalObjs( cx, js::MakeUnique(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; RootedWasmInstanceObject 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(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()); // Root the Instance via WasmInstanceObject before any possible GC. instance = cx->new_(cx, obj, code, std::move(tlsData), memory, std::move(exceptionTags), 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, 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 instanceProto(cx); if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmInstance, &instanceProto)) { return false; } if (!instanceProto) { instanceProto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmInstance); } Rooted imports(cx); if (!GetImports(cx, *module, importObj, imports.address())) { return false; } RootedWasmInstanceObject instanceObj(cx); if (!module->instantiate(cx, imports.get(), instanceProto, &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()); Instance& instance = ExportedFunctionToInstance(callee); uint32_t funcIndex = ExportedFunctionToFuncIndex(callee); return instance.callExport(cx, funcIndex, args); } /* static */ bool WasmInstanceObject::getExportedFunction( JSContext* cx, HandleWasmInstanceObject 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); unsigned numArgs = funcExport.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. RootedAtom 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 { RootedAtom name(cx, NumberToAtom(cx, funcIndex)); if (!name) { return false; } fun.set(NewNativeFunction(cx, WasmCall, numArgs, name, gc::AllocKind::FUNCTION_EXTENDED, TenuredObject, FunctionFlags::WASM)); 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) stub as JitEntry and wait until // Instance::callExport() to create the fast entry stubs. if (funcExport.canHaveJitEntry()) { if (!funcExport.hasEagerStubs()) { if (!EnsureBuiltinThunksInitialized()) { return false; } void* provisionalJitEntryStub = ProvisionalJitEntryStub(); MOZ_ASSERT(provisionalJitEntryStub); instance.code().setJitEntryIfNull(funcIndex, provisionalJitEntryStub); } fun->setWasmJitEntry(instance.code().getAddressOfJitEntry(funcIndex)); } else { fun->setWasmFuncIndex(funcIndex); } } fun->setExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT, ObjectValue(*instanceObj)); void* tlsData = instanceObj->instance().tlsData(); fun->setExtendedSlot(FunctionExtended::WASM_TLSDATA_SLOT, PrivateValue(tlsData)); 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, HandleWasmInstanceObject instanceObj) { if (!instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT).isUndefined()) { return (WasmInstanceScope*)instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT) .toGCThing(); } Rooted 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, HandleWasmInstanceObject instanceObj, uint32_t funcIndex) { if (auto p = instanceObj->scopes().asWasmFunctionScopeMap().lookup(funcIndex)) { return p->value(); } Rooted instanceScope( cx, WasmInstanceObject::getScope(cx, instanceObj)); if (!instanceScope) { return nullptr; } Rooted 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 ExportedFunctionToInstanceObject(fun)->instance(); } WasmInstanceObject* wasm::ExportedFunctionToInstanceObject(JSFunction* fun) { MOZ_ASSERT(fun->kind() == FunctionFlags::Wasm || fun->kind() == FunctionFlags::AsmJS); const Value& v = fun->getExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT); return &v.toObject().as(); } uint32_t wasm::ExportedFunctionToFuncIndex(JSFunction* fun) { Instance& instance = ExportedFunctionToInstanceObject(fun)->instance(); return instance.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, // hasInstance 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"; const ClassSpec WasmMemoryObject::classSpec_ = { CreateWasmConstructor, GenericCreatePrototype, WasmMemoryObject::static_methods, nullptr, WasmMemoryObject::methods, WasmMemoryObject::properties, nullptr, ClassSpec::DontDefineConstructor}; /* static */ void WasmMemoryObject::finalize(JSFreeOp* fop, JSObject* obj) { WasmMemoryObject& memory = obj->as(); if (memory.hasObservers()) { fop->delete_(obj, &memory.observers(), MemoryUse::WasmMemoryObservers); } } /* static */ WasmMemoryObject* WasmMemoryObject::create( JSContext* cx, HandleArrayBufferObjectMaybeShared buffer, HandleObject proto) { AutoSetNewObjectMetadata metadata(cx); auto* obj = NewObjectWithGivenProto(cx, proto); if (!obj) { return nullptr; } obj->initReservedSlot(BUFFER_SLOT, ObjectValue(*buffer)); 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, MaxMemory32LimitField, "Memory", &limits, Shareable::True)) { return false; } if (limits.initial > MaxMemory32Pages) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_MEM_IMP_LIMIT); return false; } ConvertMemoryPagesToBytes(&limits); RootedArrayBufferObjectMaybeShared buffer(cx); if (!CreateWasmBuffer(cx, MemoryKind::Memory32, limits, &buffer)) { return false; } RootedObject proto(cx); if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmMemory, &proto)) { return false; } if (!proto) { proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmMemory); } RootedWasmMemoryObject memoryObj(cx, WasmMemoryObject::create(cx, buffer, proto)); if (!memoryObj) { return false; } args.rval().setObject(*memoryObj); return true; } static bool IsMemory(HandleValue v) { return v.isObject() && v.toObject().is(); } /* static */ bool WasmMemoryObject::bufferGetterImpl(JSContext* cx, const CallArgs& args) { RootedWasmMemoryObject memoryObj( cx, &args.thisv().toObject().as()); RootedArrayBufferObjectMaybeShared buffer(cx, &memoryObj->buffer()); if (memoryObj->isShared()) { uint32_t memoryLength = memoryObj->volatileMemoryLength32(); MOZ_ASSERT(memoryLength >= ByteLength32(buffer)); if (memoryLength > ByteLength32(buffer)) { RootedSharedArrayBufferObject newBuffer( cx, SharedArrayBufferObject::New(cx, memoryObj->sharedArrayRawBuffer(), BufferSize(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(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) { RootedWasmMemoryObject memory( cx, &args.thisv().toObject().as()); 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(ret); return true; } /* static */ bool WasmMemoryObject::grow(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(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::static_methods[] = {JS_FS_END}; ArrayBufferObjectMaybeShared& WasmMemoryObject::buffer() const { return getReservedSlot(BUFFER_SLOT) .toObject() .as(); } SharedArrayRawBuffer* WasmMemoryObject::sharedArrayRawBuffer() const { MOZ_ASSERT(isShared()); return buffer().as().rawBufferObject(); } #ifdef ENABLE_WASM_TYPE_REFLECTIONS bool WasmMemoryObject::typeImpl(JSContext* cx, const CallArgs& args) { RootedWasmMemoryObject memoryObj( cx, &args.thisv().toObject().as()); Rooted props(cx, IdValueVector(cx)); Maybe bufferMaxSize = memoryObj->buffer().wasmMaxSize(); if (bufferMaxSize.isSome()) { uint32_t maximumPages = bufferMaxSize.value() / wasm::PageSize; if (!props.append(IdValuePair(NameToId(cx->names().maximum), Int32Value(maximumPages)))) { return false; } } uint32_t minimumPages = mozilla::AssertedCast( memoryObj->volatileMemoryLength32() / wasm::PageSize); if (!props.append(IdValuePair(NameToId(cx->names().minimum), Int32Value(minimumPages)))) { return false; } if (!props.append(IdValuePair(NameToId(cx->names().shared), BooleanValue(memoryObj->isShared())))) { return false; } JSObject* memoryType = NewPlainObjectWithProperties( cx, props.begin(), props.length(), GenericObject); if (!memoryType) { return false; } args.rval().setObject(*memoryType); return true; } bool WasmMemoryObject::type(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } #endif uint32_t WasmMemoryObject::volatileMemoryLength32() const { if (isShared()) { return VolatileByteLength32(sharedArrayRawBuffer()); } return ByteLength32(buffer()); } bool WasmMemoryObject::isShared() const { return buffer().is(); } bool WasmMemoryObject::hasObservers() const { return !getReservedSlot(OBSERVERS_SLOT).isUndefined(); } WasmMemoryObject::InstanceSet& WasmMemoryObject::observers() const { MOZ_ASSERT(hasObservers()); return *reinterpret_cast( getReservedSlot(OBSERVERS_SLOT).toPrivate()); } WasmMemoryObject::InstanceSet* WasmMemoryObject::getOrCreateObservers( JSContext* cx) { if (!hasObservers()) { auto observers = MakeUnique(cx->zone(), cx->zone()); if (!observers) { ReportOutOfMemory(cx); return nullptr; } InitReservedSlot(this, OBSERVERS_SLOT, observers.release(), MemoryUse::WasmMemoryObservers); } return &observers(); } bool WasmMemoryObject::isHuge() const { #ifdef WASM_SUPPORTS_HUGE_MEMORY static_assert(MaxMemory32Bytes < HugeMappedSize, "Non-huge buffer may be confused as huge"); return buffer().wasmMappedSize() >= HugeMappedSize; #else return false; #endif } bool WasmMemoryObject::movingGrowable() const { return !isHuge() && !buffer().wasmMaxSize(); } uint32_t WasmMemoryObject::boundsCheckLimit32() const { if (!buffer().isWasm() || isHuge()) { return ByteLength32(buffer()); } size_t mappedSize = buffer().wasmMappedSize(); MOZ_ASSERT(mappedSize <= UINT32_MAX); MOZ_ASSERT(mappedSize >= wasm::GuardSize); MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize - wasm::GuardSize)); return mappedSize - wasm::GuardSize; } 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 */ uint32_t WasmMemoryObject::growShared(HandleWasmMemoryObject memory, uint32_t delta) { SharedArrayRawBuffer* rawBuf = memory->sharedArrayRawBuffer(); SharedArrayRawBuffer::Lock lock(rawBuf); MOZ_ASSERT(VolatileByteLength32(rawBuf) % PageSize == 0); uint32_t oldNumPages = VolatileByteLength32(rawBuf) / PageSize; CheckedInt newSize = oldNumPages; newSize += delta; newSize *= PageSize; if (!newSize.isValid()) { return -1; } if (newSize.value() > rawBuf->maxSize()) { return -1; } if (!rawBuf->wasmGrowToSizeInPlace(lock, BufferSize(newSize.value()))) { return -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; } /* static */ uint32_t WasmMemoryObject::grow(HandleWasmMemoryObject memory, uint32_t delta, JSContext* cx) { if (memory->isShared()) { return growShared(memory, delta); } RootedArrayBufferObject oldBuf(cx, &memory->buffer().as()); MOZ_ASSERT(ByteLength32(oldBuf) % PageSize == 0); uint32_t oldNumPages = ByteLength32(oldBuf) / PageSize; // FIXME (large ArrayBuffer): This does not allow 65536 pages, which is // technically the max. That may be a webcompat problem. We can fix this // once wasmMovingGrowToSize and wasmGrowToSizeInPlace accept size_t rather // than uint32_t. See the FIXME in WasmConstants.h for additional // information. static_assert(MaxMemory32Pages <= UINT32_MAX / PageSize, "Avoid overflows"); CheckedInt newSize = oldNumPages; newSize += delta; newSize *= PageSize; if (!newSize.isValid()) { return -1; } // Always check against the max here, do not rely on the buffer resizers to // use the correct limit, they don't have enough context. if (newSize.value() > MaxMemory32Pages * PageSize) { return -1; } RootedArrayBufferObject newBuf(cx); if (memory->movingGrowable()) { MOZ_ASSERT(!memory->isHuge()); if (!ArrayBufferObject::wasmMovingGrowToSize(BufferSize(newSize.value()), oldBuf, &newBuf, cx)) { return -1; } } else { if (Maybe maxSize = oldBuf->wasmMaxSize()) { if (newSize.value() > maxSize.value()) { return -1; } } if (!ArrayBufferObject::wasmGrowToSizeInPlace(BufferSize(newSize.value()), oldBuf, &newBuf, cx)) { return -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; } bool js::wasm::IsSharedWasmMemoryObject(JSObject* obj) { WasmMemoryObject* mobj = obj->maybeUnwrapIf(); 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, // hasInstance 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, GenericCreatePrototype, WasmTableObject::static_methods, nullptr, WasmTableObject::methods, WasmTableObject::properties, nullptr, ClassSpec::DontDefineConstructor}; bool WasmTableObject::isNewborn() const { MOZ_ASSERT(is()); return getReservedSlot(TABLE_SLOT).isUndefined(); } /* static */ void WasmTableObject::finalize(JSFreeOp* fop, JSObject* obj) { WasmTableObject& tableObj = obj->as(); if (!tableObj.isNewborn()) { auto& table = tableObj.table(); fop->release(obj, &table, table.gcMallocBytes(), MemoryUse::WasmTableTable); } } /* static */ void WasmTableObject::trace(JSTracer* trc, JSObject* obj) { WasmTableObject& tableObj = obj->as(); 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 TableDefaultValue(wasm::RefType tableType) { return tableType.isExtern() ? UndefinedValue() : NullValue(); } /* static */ WasmTableObject* WasmTableObject::create(JSContext* cx, uint32_t initialLength, Maybe maximumLength, wasm::RefType tableType, HandleObject proto) { AutoSetNewObjectMetadata metadata(cx); RootedWasmTableObject obj( cx, NewObjectWithGivenProto(cx, proto)); if (!obj) { return nullptr; } MOZ_ASSERT(obj->isNewborn()); TableDesc td(tableType, initialLength, maximumLength, /*isAsmJS*/ false, /*importedOrExported=*/true); SharedTable table = Table::create(cx, td, obj); if (!table) { ReportOutOfMemory(cx); 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; } RootedString elementStr(cx, ToString(cx, elementVal)); if (!elementStr) { return false; } RootedLinearString elementLinearStr(cx, elementStr->ensureLinear(cx)); if (!elementLinearStr) { return false; } RefType tableType; if (StringEqualsLiteral(elementLinearStr, "anyfunc") || StringEqualsLiteral(elementLinearStr, "funcref")) { tableType = RefType::func(); #ifdef ENABLE_WASM_REFTYPES } else if (StringEqualsLiteral(elementLinearStr, "externref")) { if (!ReftypesAvailable(cx)) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT); return false; } tableType = RefType::extern_(); #endif #ifdef ENABLE_WASM_GC } else if (StringEqualsLiteral(elementLinearStr, "eqref")) { if (!GcTypesAvailable(cx)) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT); return false; } tableType = RefType::eq(); #endif } else { #ifdef ENABLE_WASM_REFTYPES JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT_GENERALIZED); #else JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT); #endif return false; } Limits limits; if (!GetLimits(cx, obj, MaxTableLimitField, "Table", &limits, Shareable::False)) { return false; } if (limits.initial > MaxTableLength) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_TABLE_IMP_LIMIT); return false; } RootedObject proto(cx); if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmTable, &proto)) { return false; } if (!proto) { proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmTable); } // 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 maximumLength; if (limits.maximum) { maximumLength = Some(uint32_t(*limits.maximum)); } RootedWasmTableObject 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 ? TableDefaultValue(tableType) : args[1]); // 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->assertRangeNull(0, initialLength); } #endif args.rval().setObject(*table); return true; } static bool IsTable(HandleValue v) { return v.isObject() && v.toObject().is(); } /* static */ bool WasmTableObject::lengthGetterImpl(JSContext* cx, const CallArgs& args) { args.rval().setNumber( args.thisv().toObject().as().table().length()); return true; } /* static */ bool WasmTableObject::lengthGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(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) { Rooted props(cx, IdValueVector(cx)); Table& table = args.thisv().toObject().as().table(); const char* elementValue; switch (table.repr()) { case TableRepr::Func: elementValue = "funcref"; break; case TableRepr::Ref: elementValue = "externref"; break; default: MOZ_CRASH("Should not happen"); } JSString* elementString = UTF8CharsToString(cx, elementValue); if (!elementString) { return false; } if (!props.append(IdValuePair(NameToId(cx->names().element), StringValue(elementString)))) { return false; } if (table.maximum().isSome()) { if (!props.append(IdValuePair(NameToId(cx->names().maximum), Int32Value(table.maximum().value())))) { return false; } } if (!props.append(IdValuePair(NameToId(cx->names().minimum), Int32Value(table.length())))) { return false; } JSObject* tableType = NewPlainObjectWithProperties( cx, props.begin(), props.length(), GenericObject); if (!tableType) { return false; } args.rval().setObject(*tableType); return true; } /* static */ bool WasmTableObject::type(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } #endif /* static */ bool WasmTableObject::getImpl(JSContext* cx, const CallArgs& args) { RootedWasmTableObject tableObj( cx, &args.thisv().toObject().as()); 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; } switch (table.repr()) { case TableRepr::Func: { MOZ_RELEASE_ASSERT(!table.isAsmJS()); RootedFunction fun(cx); if (!table.getFuncRef(cx, index, &fun)) { return false; } args.rval().setObjectOrNull(fun); break; } case TableRepr::Ref: { args.rval().set(UnboxAnyRef(table.getAnyRef(index))); break; } } return true; } /* static */ bool WasmTableObject::get(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /* static */ bool WasmTableObject::setImpl(JSContext* cx, const CallArgs& args) { RootedWasmTableObject tableObj( cx, &args.thisv().toObject().as()); 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 ? TableDefaultValue(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(cx, args); } /* static */ bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) { RootedWasmTableObject tableObj( cx, &args.thisv().toObject().as()); 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; } uint32_t oldLength = table.grow(delta); if (oldLength == uint32_t(-1)) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW, "table"); return false; } // Fill the grown range of the table RootedValue fillValue( cx, args.length() < 2 ? TableDefaultValue(table.elemType()) : args[1]); // 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()) { tableObj->assertRangeNull(oldLength, delta); } #endif args.rval().setInt32(oldLength); return true; } /* static */ bool WasmTableObject::grow(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(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; } #ifdef DEBUG void WasmTableObject::assertRangeNull(uint32_t index, uint32_t length) const { Table& tab = table(); switch (tab.repr()) { case TableRepr::Func: for (uint32_t i = index; i < index + length; i++) { MOZ_ASSERT(tab.getFuncRef(i).code == nullptr); } break; case TableRepr::Ref: for (uint32_t i = index; i < index + length; i++) { MOZ_ASSERT(tab.getAnyRef(i).isNull()); } break; } } #endif // ============================================================================ // 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, // hasInstance 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, GenericCreatePrototype, WasmGlobalObject::static_methods, nullptr, WasmGlobalObject::methods, WasmGlobalObject::properties, nullptr, ClassSpec::DontDefineConstructor}; /* static */ void WasmGlobalObject::trace(JSTracer* trc, JSObject* obj) { WasmGlobalObject* global = reinterpret_cast(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(JSFreeOp* fop, JSObject* obj) { WasmGlobalObject* global = reinterpret_cast(obj); if (!global->isNewborn()) { fop->delete_(obj, &global->val(), MemoryUse::WasmGlobalCell); } } /* static */ WasmGlobalObject* WasmGlobalObject::create(JSContext* cx, HandleVal hval, bool isMutable, HandleObject proto) { AutoSetNewObjectMetadata metadata(cx); RootedWasmGlobalObject obj( cx, NewObjectWithGivenProto(cx, proto)); if (!obj) { return nullptr; } MOZ_ASSERT(obj->isNewborn()); MOZ_ASSERT(obj->isTenured(), "assumed by global.set post barriers"); GCPtrVal* val = js_new(Val(hval.get().type())); 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() = hval.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; } RootedString typeStr(cx, ToString(cx, typeVal)); if (!typeStr) { return false; } RootedLinearString typeLinearStr(cx, typeStr->ensureLinear(cx)); if (!typeLinearStr) { return false; } ValType globalType; if (StringEqualsLiteral(typeLinearStr, "i32")) { globalType = ValType::I32; } else if (StringEqualsLiteral(typeLinearStr, "i64")) { globalType = ValType::I64; } else if (StringEqualsLiteral(typeLinearStr, "f32")) { globalType = ValType::F32; } else if (StringEqualsLiteral(typeLinearStr, "f64")) { globalType = ValType::F64; #ifdef ENABLE_WASM_SIMD } else if (SimdAvailable(cx) && StringEqualsLiteral(typeLinearStr, "v128")) { globalType = ValType::V128; #endif #ifdef ENABLE_WASM_REFTYPES } else if (ReftypesAvailable(cx) && StringEqualsLiteral(typeLinearStr, "funcref")) { globalType = RefType::func(); } else if (ReftypesAvailable(cx) && StringEqualsLiteral(typeLinearStr, "externref")) { globalType = RefType::extern_(); #endif #ifdef ENABLE_WASM_GC } else if (GcTypesAvailable(cx) && StringEqualsLiteral(typeLinearStr, "eqref")) { globalType = RefType::eq(); #endif } else { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GLOBAL_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, args.get(1)); if (!valueVal.isUndefined() || (args.length() >= 2 && globalType.isReference())) { if (!Val::fromJSValue(cx, globalType, valueVal, &globalVal)) { return false; } } RootedObject proto(cx); if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmGlobal, &proto)) { return false; } if (!proto) { proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmGlobal); } 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(); } /* static */ bool WasmGlobalObject::valueGetterImpl(JSContext* cx, const CallArgs& args) { const WasmGlobalObject& globalObj = args.thisv().toObject().as(); 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(cx, args); } /* static */ bool WasmGlobalObject::valueSetterImpl(JSContext* cx, const CallArgs& args) { if (!args.requireAtLeast(cx, "WebAssembly.Global setter", 1)) { return false; } RootedWasmGlobalObject global( cx, &args.thisv().toObject().as()); 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(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(getReservedSlot(VAL_SLOT).toPrivate()); } #ifdef ENABLE_WASM_TYPE_REFLECTIONS /* static */ bool WasmGlobalObject::typeImpl(JSContext* cx, const CallArgs& args) { RootedWasmGlobalObject global( cx, &args.thisv().toObject().as()); Rooted props(cx, IdValueVector(cx)); if (!props.append(IdValuePair(NameToId(cx->names().mutable_), BooleanValue(global->isMutable())))) { return false; } JSString* valueType = UTF8CharsToString(cx, ToString(global->type()).get()); if (!valueType) { return false; } if (!props.append( IdValuePair(NameToId(cx->names().value), StringValue(valueType)))) { return false; } JSObject* globalType = NewPlainObjectWithProperties( cx, props.begin(), props.length(), GenericObject); if (!globalType) { return false; } args.rval().setObject(*globalType); return true; } /* static */ bool WasmGlobalObject::type(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } #endif // ============================================================================ // 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, // hasInstance nullptr, // construct nullptr, // 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, GenericCreatePrototype, WasmExceptionObject::static_methods, nullptr, WasmExceptionObject::methods, WasmExceptionObject::properties, nullptr, ClassSpec::DontDefineConstructor}; /* static */ void WasmExceptionObject::finalize(JSFreeOp* fop, JSObject* obj) { WasmExceptionObject& exnObj = obj->as(); if (!exnObj.isNewborn()) { fop->release(obj, &exnObj.tag(), MemoryUse::WasmExceptionTag); fop->delete_(obj, &exnObj.valueTypes(), MemoryUse::WasmExceptionType); } } bool WasmExceptionObject::construct(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (!ThrowIfNotConstructing(cx, args, "Exception")) { return false; } // FIXME: The JS API is not finalized and may specify a different behavior // here. // For now, we implement the same behavior as V8 and error when called. JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_EXN_CONSTRUCTOR); return false; } /* static */ WasmExceptionObject* WasmExceptionObject::create(JSContext* cx, const ValTypeVector& type, HandleObject proto) { AutoSetNewObjectMetadata metadata(cx); RootedWasmExceptionObject obj( cx, NewObjectWithGivenProto(cx, proto)); if (!obj) { return nullptr; } MOZ_ASSERT(obj->isNewborn()); SharedExceptionTag tag = SharedExceptionTag(cx->new_()); if (!tag) { ReportOutOfMemory(cx); return nullptr; } InitReservedSlot(obj, TAG_SLOT, tag.forget().take(), MemoryUse::WasmExceptionTag); wasm::ValTypeVector* newValueTypes = js_new(); for (uint32_t i = 0; i < type.length(); i++) { if (!newValueTypes->append(type[i])) { return nullptr; } } InitReservedSlot(obj, TYPE_SLOT, newValueTypes, MemoryUse::WasmExceptionType); MOZ_ASSERT(!obj->isNewborn()); return obj; } bool WasmExceptionObject::isNewborn() const { MOZ_ASSERT(is()); return getReservedSlot(TYPE_SLOT).isUndefined(); } const JSPropertySpec WasmExceptionObject::properties[] = { JS_STRING_SYM_PS(toStringTag, "WebAssembly.Exception", JSPROP_READONLY), JS_PS_END}; const JSFunctionSpec WasmExceptionObject::methods[] = {JS_FS_END}; const JSFunctionSpec WasmExceptionObject::static_methods[] = {JS_FS_END}; wasm::ValTypeVector& WasmExceptionObject::valueTypes() const { return *(ValTypeVector*)getFixedSlot(TYPE_SLOT).toPrivate(); }; wasm::ResultType WasmExceptionObject::resultType() const { return wasm::ResultType::Vector(valueTypes()); } ExceptionTag& WasmExceptionObject::tag() const { return *(ExceptionTag*)getReservedSlot(TAG_SLOT).toPrivate(); } // ============================================================================ // 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 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 promise, const UniqueChars& error) { if (!error) { ReportOutOfMemory(cx); return RejectWithPendingException(cx, promise); } RootedObject stack(cx, promise->allocationSite()); RootedString filename( cx, JS_NewStringCopyZ(cx, args.scriptedCaller.filename.get())); 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(cx, str.get(), len)); if (!message) { return false; } RootedObject errorObj( cx, ErrorObject::create(cx, JSEXN_WASMCOMPILEERROR, stack, filename, 0, line, 0, nullptr, message)); 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 imports_; Ret ret_; public: AsyncInstantiateTask(JSContext* cx, const Module& module, Ret ret, Handle promise) : OffThreadPromiseTask(cx, promise), module_(&module), imports_(cx), ret_(ret) {} ImportValues& imports() { return imports_.get(); } bool resolve(JSContext* cx, Handle promise) override { RootedObject instanceProto( cx, &cx->global()->getPrototype(JSProto_WasmInstance).toObject()); RootedWasmInstanceObject 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).toObject()); 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 promise) { auto task = js::MakeUnique(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 promise) { RootedObject proto( cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject()); 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; JSTelemetrySender sender; CompileBufferTask(JSContext* cx, Handle promise, HandleObject importObj) : PromiseHelperTask(cx, promise), instantiate(true), importObj(cx, importObj), sender(cx->runtime()) {} CompileBufferTask(JSContext* cx, Handle 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, sender); } bool resolve(JSContext* cx, Handle promise) override { if (!module) { return Reject(cx, *compileArgs, promise, error); } if (!ReportCompileWarnings(cx, warnings)) { return false; } if (instantiate) { return AsyncInstantiate(cx, *module, importObj, Ret::Pair, promise); } return ResolveCompile(cx, *module, promise); } }; static bool RejectWithPendingException(JSContext* cx, Handle 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 promise(cx, PromiseObject::createSkippingExecutor(cx)); if (!promise) { return false; } auto task = cx->make_unique(cx, promise); if (!task || !task->init(cx, "WebAssembly.compile")) { return false; } CallArgs callArgs = CallArgsFromVp(argc, vp); 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 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 { auto task = cx->make_unique(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; } UniqueChars error; bool validated = Validate(cx, *bytecode, &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 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_; // 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 streamError_; UniqueChars compileError_; UniqueCharsVector warnings_; // Set on stream thread and read racily on helper thread to abort compilation: Atomic streamFailed_; JSTelemetrySender sender_; // 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(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(std::move(envBytes_)); if (!bytecode) { rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode); return; } module_ = CompileBuffer(*compileArgs_, *bytecode, &compileError_, &warnings_, nullptr, sender_); 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_, sender_); // 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 promise) override { MOZ_ASSERT(streamState_.lock() == Closed); if (module_) { MOZ_ASSERT(!streamFailed_ && !streamError_ && !compileError_); if (!ReportCompileWarnings(cx, warnings_)) { return false; } 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 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), sender_(cx->runtime()) { MOZ_ASSERT_IF(importObj_, instantiate_); } }; // A short-lived object that captures the arguments of a // WebAssembly.{compileStreaming,instantiateStreaming} while waiting for // the Promise 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(JSFreeOp* fop, JSObject* obj) { auto& closure = obj->as(); fop->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(cx, nullptr); if (!obj) { return nullptr; } args.AddRef(); InitReservedSlot(obj, COMPILE_ARGS_SLOT, const_cast(&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(); } 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, // hasInstance 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() .getExtendedSlot(0) .toObject() .as(); } static bool RejectWithErrorNumber(JSContext* cx, uint32_t errorNumber, Handle 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 closure(cx, ToResolveResponseClosure(callArgs)); Rooted promise(cx, &closure->promise()); CompileArgs& compileArgs = closure->compileArgs(); bool instantiate = closure->instantiate(); Rooted importObj(cx, closure->importObj()); auto task = cx->make_unique(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); } Unused << task.release(); callArgs.rval().setUndefined(); return true; } static bool ResolveResponse_OnRejected(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); Rooted closure(cx, ToResolveResponseClosure(args)); Rooted 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 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 promise(cx, PromiseObject::createSkippingExecutor(cx)); if (!promise) { return false; } CallArgs callArgs = CallArgsFromVp(argc, vp); 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 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); } if (!ResolveResponse(cx, callArgs, promise, true, importObj)) { return RejectWithPendingException(cx, promise, callArgs); } callArgs.rval().setObject(*promise); return true; } 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 JSObject* CreateWebAssemblyObject(JSContext* cx, JSProtoKey key) { MOZ_RELEASE_ASSERT(HasSupport(cx)); Handle global = cx->global(); RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global)); if (!proto) { return nullptr; } return NewTenuredObjectWithGivenProto(cx, &WasmNamespaceObject::class_, proto); } static bool WebAssemblyClassFinish(JSContext* cx, HandleObject object, HandleObject proto) { Handle wasm = object.as(); struct NameAndProtoKey { const char* const name; JSProtoKey key; }; constexpr NameAndProtoKey entries[] = { {"Module", JSProto_WasmModule}, {"Instance", JSProto_WasmInstance}, {"Memory", JSProto_WasmMemory}, {"Table", JSProto_WasmTable}, {"Global", JSProto_WasmGlobal}, #ifdef ENABLE_WASM_EXCEPTIONS {"Exception", JSProto_WasmException}, #endif {"CompileError", GetExceptionProtoKey(JSEXN_WASMCOMPILEERROR)}, {"LinkError", GetExceptionProtoKey(JSEXN_WASMLINKERROR)}, {"RuntimeError", GetExceptionProtoKey(JSEXN_WASMRUNTIMEERROR)}, }; RootedValue ctorValue(cx); RootedId id(cx); for (const auto& entry : entries) { const char* name = entry.name; JSProtoKey key = entry.key; JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, key); if (!ctor) { return false; } ctorValue.setObject(*ctor); JSAtom* className = Atomize(cx, name, strlen(name)); if (!className) { return false; } id.set(AtomToId(className)); if (!DefineDataProperty(cx, wasm, id, ctorValue, 0)) { return false; } } return true; } static const ClassSpec WebAssemblyClassSpec = {CreateWebAssemblyObject, nullptr, WebAssembly_static_methods, nullptr, nullptr, nullptr, WebAssemblyClassFinish}; const JSClass js::WasmNamespaceObject::class_ = { js_WebAssembly_str, JSCLASS_HAS_CACHED_PROTO(JSProto_WebAssembly), JS_NULL_CLASS_OPS, &WebAssemblyClassSpec};