summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmModule.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/wasm/WasmModule.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/wasm/WasmModule.cpp')
-rw-r--r--js/src/wasm/WasmModule.cpp1134
1 files changed, 1134 insertions, 0 deletions
diff --git a/js/src/wasm/WasmModule.cpp b/js/src/wasm/WasmModule.cpp
new file mode 100644
index 0000000000..b30c810e0f
--- /dev/null
+++ b/js/src/wasm/WasmModule.cpp
@@ -0,0 +1,1134 @@
+/* -*- 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 2015 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/WasmModule.h"
+
+#include <chrono>
+
+#include "jit/FlushICache.h" // for FlushExecutionContextForAllThreads
+#include "js/BuildId.h" // JS::BuildIdCharVector
+#include "js/experimental/TypedData.h" // JS_NewUint8Array
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/Printf.h" // JS_smprintf
+#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById
+#include "js/StreamConsumer.h"
+#include "threading/LockGuard.h"
+#include "threading/Thread.h"
+#include "vm/HelperThreadState.h" // Tier2GeneratorTask
+#include "vm/PlainObject.h" // js::PlainObject
+#include "wasm/WasmBaselineCompile.h"
+#include "wasm/WasmCompile.h"
+#include "wasm/WasmDebug.h"
+#include "wasm/WasmInstance.h"
+#include "wasm/WasmIonCompile.h"
+#include "wasm/WasmJS.h"
+#include "wasm/WasmSerialize.h"
+#include "wasm/WasmUtility.h"
+
+#include "debugger/DebugAPI-inl.h"
+#include "vm/ArrayBufferObject-inl.h"
+#include "vm/JSAtom-inl.h"
+#include "wasm/WasmInstance-inl.h"
+
+using namespace js;
+using namespace js::jit;
+using namespace js::wasm;
+
+static UniqueChars Tier2ResultsContext(const ScriptedCaller& scriptedCaller) {
+ return scriptedCaller.filename
+ ? JS_smprintf("%s:%d", scriptedCaller.filename.get(),
+ scriptedCaller.line)
+ : UniqueChars();
+}
+
+static void ReportTier2ResultsOffThread(bool success,
+ const ScriptedCaller& scriptedCaller,
+ const UniqueChars& error,
+ const UniqueCharsVector& warnings) {
+ // Get context to describe this tier-2 task.
+ UniqueChars context = Tier2ResultsContext(scriptedCaller);
+ const char* contextString = context ? context.get() : "unknown";
+
+ // Display the main error, if any.
+ if (!success) {
+ const char* errorString = error ? error.get() : "out of memory";
+ LogOffThread("'%s': wasm tier-2 failed with '%s'.\n", contextString,
+ errorString);
+ }
+
+ // Display warnings as a follow-up, avoiding spamming the console.
+ size_t numWarnings = std::min<size_t>(warnings.length(), 3);
+
+ for (size_t i = 0; i < numWarnings; i++) {
+ LogOffThread("'%s': wasm tier-2 warning: '%s'.\n'.", contextString,
+ warnings[i].get());
+ }
+ if (warnings.length() > numWarnings) {
+ LogOffThread("'%s': other warnings suppressed.\n", contextString);
+ }
+}
+
+class Module::Tier2GeneratorTaskImpl : public Tier2GeneratorTask {
+ SharedCompileArgs compileArgs_;
+ SharedBytes bytecode_;
+ SharedModule module_;
+ Atomic<bool> cancelled_;
+
+ public:
+ Tier2GeneratorTaskImpl(const CompileArgs& compileArgs,
+ const ShareableBytes& bytecode, Module& module)
+ : compileArgs_(&compileArgs),
+ bytecode_(&bytecode),
+ module_(&module),
+ cancelled_(false) {}
+
+ ~Tier2GeneratorTaskImpl() override {
+ module_->tier2Listener_ = nullptr;
+ module_->testingTier2Active_ = false;
+ }
+
+ void cancel() override { cancelled_ = true; }
+
+ void runHelperThreadTask(AutoLockHelperThreadState& locked) override {
+ {
+ AutoUnlockHelperThreadState unlock(locked);
+
+ // Compile tier-2 and report any warning/errors as long as it's not a
+ // cancellation. Encountering a warning/error during compilation and
+ // being cancelled may race with each other, but the only observable race
+ // should be being cancelled after a warning/error is set, and that's
+ // okay.
+ UniqueChars error;
+ UniqueCharsVector warnings;
+ bool success = CompileTier2(*compileArgs_, bytecode_->bytes, *module_,
+ &error, &warnings, &cancelled_);
+ if (!cancelled_) {
+ // We could try to dispatch a runnable to the thread that started this
+ // compilation, so as to report the warning/error using a JSContext*.
+ // For now we just report to stderr.
+ ReportTier2ResultsOffThread(success, compileArgs_->scriptedCaller,
+ error, warnings);
+ }
+ }
+
+ // During shutdown the main thread will wait for any ongoing (cancelled)
+ // tier-2 generation to shut down normally. To do so, it waits on the
+ // HelperThreadState's condition variable for the count of finished
+ // generators to rise.
+ HelperThreadState().incWasmTier2GeneratorsFinished(locked);
+
+ // The task is finished, release it.
+ js_delete(this);
+ }
+
+ ThreadType threadType() override {
+ return ThreadType::THREAD_TYPE_WASM_GENERATOR_TIER2;
+ }
+};
+
+Module::~Module() {
+ // Note: Modules can be destroyed on any thread.
+ MOZ_ASSERT(!tier2Listener_);
+ MOZ_ASSERT(!testingTier2Active_);
+}
+
+void Module::startTier2(const CompileArgs& args, const ShareableBytes& bytecode,
+ JS::OptimizedEncodingListener* listener) {
+ MOZ_ASSERT(!testingTier2Active_);
+
+ auto task = MakeUnique<Tier2GeneratorTaskImpl>(args, bytecode, *this);
+ if (!task) {
+ return;
+ }
+
+ // These will be cleared asynchronously by ~Tier2GeneratorTaskImpl() if not
+ // sooner by finishTier2().
+ tier2Listener_ = listener;
+ testingTier2Active_ = true;
+
+ StartOffThreadWasmTier2Generator(std::move(task));
+}
+
+bool Module::finishTier2(const LinkData& linkData2,
+ UniqueCodeTier code2) const {
+ MOZ_ASSERT(code().bestTier() == Tier::Baseline &&
+ code2->tier() == Tier::Optimized);
+
+ // Install the data in the data structures. They will not be visible
+ // until commitTier2().
+
+ const CodeTier* borrowedTier2;
+ if (!code().setAndBorrowTier2(std::move(code2), linkData2, &borrowedTier2)) {
+ return false;
+ }
+
+ // Before we can make tier-2 live, we need to compile tier2 versions of any
+ // extant tier1 lazy stubs (otherwise, tiering would break the assumption
+ // that any extant exported wasm function has had a lazy entry stub already
+ // compiled for it).
+ //
+ // Also see doc block for stubs in WasmJS.cpp.
+ {
+ // We need to prevent new tier1 stubs generation until we've committed
+ // the newer tier2 stubs, otherwise we might not generate one tier2
+ // stub that has been generated for tier1 before we committed.
+
+ const MetadataTier& metadataTier1 = metadata(Tier::Baseline);
+
+ auto stubs1 = code().codeTier(Tier::Baseline).lazyStubs().readLock();
+ auto stubs2 = borrowedTier2->lazyStubs().writeLock();
+
+ MOZ_ASSERT(stubs2->entryStubsEmpty());
+
+ Uint32Vector funcExportIndices;
+ for (size_t i = 0; i < metadataTier1.funcExports.length(); i++) {
+ const FuncExport& fe = metadataTier1.funcExports[i];
+ if (fe.hasEagerStubs()) {
+ continue;
+ }
+ if (!stubs1->hasEntryStub(fe.funcIndex())) {
+ continue;
+ }
+ if (!funcExportIndices.emplaceBack(i)) {
+ return false;
+ }
+ }
+
+ Maybe<size_t> stub2Index;
+ if (!stubs2->createTier2(funcExportIndices, metadata(), *borrowedTier2,
+ &stub2Index)) {
+ return false;
+ }
+
+ // Initializing the code above will have flushed the icache for all cores.
+ // However, there could still be stale data in the execution pipeline of
+ // other cores on some platforms. Force an execution context flush on all
+ // threads to fix this before we commit the code.
+ //
+ // This is safe due to the check in `PlatformCanTier` in WasmCompile.cpp
+ jit::FlushExecutionContextForAllThreads();
+
+ // Now that we can't fail or otherwise abort tier2, make it live.
+
+ MOZ_ASSERT(!code().hasTier2());
+ code().commitTier2();
+
+ stubs2->setJitEntries(stub2Index, code());
+ }
+
+ // And we update the jump vectors with pointers to tier-2 functions and eager
+ // stubs. Callers will continue to invoke tier-1 code until, suddenly, they
+ // will invoke tier-2 code. This is benign.
+
+ uint8_t* base = code().segment(Tier::Optimized).base();
+ for (const CodeRange& cr : metadata(Tier::Optimized).codeRanges) {
+ // These are racy writes that we just want to be visible, atomically,
+ // eventually. All hardware we care about will do this right. But
+ // we depend on the compiler not splitting the stores hidden inside the
+ // set*Entry functions.
+ if (cr.isFunction()) {
+ code().setTieringEntry(cr.funcIndex(), base + cr.funcTierEntry());
+ } else if (cr.isJitEntry()) {
+ code().setJitEntry(cr.funcIndex(), base + cr.begin());
+ }
+ }
+
+ // Tier-2 is done; let everyone know. Mark tier-2 active for testing
+ // purposes so that wasmHasTier2CompilationCompleted() only returns true
+ // after tier-2 has been fully cached.
+
+ if (tier2Listener_) {
+ Bytes bytes;
+ if (serialize(linkData2, &bytes)) {
+ tier2Listener_->storeOptimizedEncoding(bytes.begin(), bytes.length());
+ }
+ tier2Listener_ = nullptr;
+ }
+ testingTier2Active_ = false;
+
+ return true;
+}
+
+void Module::testingBlockOnTier2Complete() const {
+ while (testingTier2Active_) {
+ ThisThread::SleepMilliseconds(1);
+ }
+}
+
+/* virtual */
+JSObject* Module::createObject(JSContext* cx) const {
+ if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly)) {
+ return nullptr;
+ }
+
+ if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_CSP_BLOCKED_WASM, "WebAssembly.Module");
+ return nullptr;
+ }
+
+ RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule));
+ return WasmModuleObject::create(cx, *this, proto);
+}
+
+/* virtual */
+JSObject* Module::createObjectForAsmJS(JSContext* cx) const {
+ // Use nullptr to get the default object prototype. These objects are never
+ // exposed to script for asm.js.
+ return WasmModuleObject::create(cx, *this, nullptr);
+}
+
+bool wasm::GetOptimizedEncodingBuildId(JS::BuildIdCharVector* buildId) {
+ // From a JS API perspective, the "build id" covers everything that can
+ // cause machine code to become invalid, so include both the actual build-id
+ // and cpu-id.
+
+ if (!GetBuildId || !GetBuildId(buildId)) {
+ return false;
+ }
+
+ uint32_t cpu = ObservedCPUFeatures();
+
+ if (!buildId->reserve(buildId->length() +
+ 13 /* "()" + 8 nibbles + "m[+-][+-]" */)) {
+ return false;
+ }
+
+ buildId->infallibleAppend('(');
+ while (cpu) {
+ buildId->infallibleAppend('0' + (cpu & 0xf));
+ cpu >>= 4;
+ }
+ buildId->infallibleAppend(')');
+
+ buildId->infallibleAppend('m');
+ buildId->infallibleAppend(wasm::IsHugeMemoryEnabled(IndexType::I32) ? '+'
+ : '-');
+ buildId->infallibleAppend(wasm::IsHugeMemoryEnabled(IndexType::I64) ? '+'
+ : '-');
+
+ return true;
+}
+
+/* virtual */
+void Module::addSizeOfMisc(MallocSizeOf mallocSizeOf,
+ Metadata::SeenSet* seenMetadata,
+ Code::SeenSet* seenCode, size_t* code,
+ size_t* data) const {
+ code_->addSizeOfMiscIfNotSeen(mallocSizeOf, seenMetadata, seenCode, code,
+ data);
+ *data += mallocSizeOf(this) +
+ SizeOfVectorExcludingThis(imports_, mallocSizeOf) +
+ SizeOfVectorExcludingThis(exports_, mallocSizeOf) +
+ SizeOfVectorExcludingThis(dataSegments_, mallocSizeOf) +
+ SizeOfVectorExcludingThis(elemSegments_, mallocSizeOf) +
+ SizeOfVectorExcludingThis(customSections_, mallocSizeOf);
+}
+
+// Extracting machine code as JS object. The result has the "code" property, as
+// a Uint8Array, and the "segments" property as array objects. The objects
+// contain offsets in the "code" array and basic information about a code
+// segment/function body.
+bool Module::extractCode(JSContext* cx, Tier tier,
+ MutableHandleValue vp) const {
+ Rooted<PlainObject*> result(cx, NewPlainObject(cx));
+ if (!result) {
+ return false;
+ }
+
+ // This function is only used for testing purposes so we can simply
+ // block on tiered compilation to complete.
+ testingBlockOnTier2Complete();
+
+ if (!code_->hasTier(tier)) {
+ vp.setNull();
+ return true;
+ }
+
+ const ModuleSegment& moduleSegment = code_->segment(tier);
+ RootedObject code(cx, JS_NewUint8Array(cx, moduleSegment.length()));
+ if (!code) {
+ return false;
+ }
+
+ memcpy(code->as<TypedArrayObject>().dataPointerUnshared(),
+ moduleSegment.base(), moduleSegment.length());
+
+ RootedValue value(cx, ObjectValue(*code));
+ if (!JS_DefineProperty(cx, result, "code", value, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ RootedObject segments(cx, NewDenseEmptyArray(cx));
+ if (!segments) {
+ return false;
+ }
+
+ for (const CodeRange& p : metadata(tier).codeRanges) {
+ RootedObject segment(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!segment) {
+ return false;
+ }
+
+ value.setNumber((uint32_t)p.begin());
+ if (!JS_DefineProperty(cx, segment, "begin", value, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ value.setNumber((uint32_t)p.end());
+ if (!JS_DefineProperty(cx, segment, "end", value, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ value.setNumber((uint32_t)p.kind());
+ if (!JS_DefineProperty(cx, segment, "kind", value, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ if (p.isFunction()) {
+ value.setNumber((uint32_t)p.funcIndex());
+ if (!JS_DefineProperty(cx, segment, "funcIndex", value,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ value.setNumber((uint32_t)p.funcUncheckedCallEntry());
+ if (!JS_DefineProperty(cx, segment, "funcBodyBegin", value,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ value.setNumber((uint32_t)p.end());
+ if (!JS_DefineProperty(cx, segment, "funcBodyEnd", value,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ if (!NewbornArrayPush(cx, segments, ObjectValue(*segment))) {
+ return false;
+ }
+ }
+
+ value.setObject(*segments);
+ if (!JS_DefineProperty(cx, result, "segments", value, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ vp.setObject(*result);
+ return true;
+}
+
+#ifdef DEBUG
+static bool AllSegmentsArePassive(const DataSegmentVector& vec) {
+ for (const DataSegment* seg : vec) {
+ if (seg->active()) {
+ return false;
+ }
+ }
+ return true;
+}
+#endif
+
+bool Module::initSegments(JSContext* cx,
+ Handle<WasmInstanceObject*> instanceObj,
+ Handle<WasmMemoryObject*> memoryObj) const {
+ MOZ_ASSERT_IF(!memoryObj, AllSegmentsArePassive(dataSegments_));
+
+ Instance& instance = instanceObj->instance();
+ const SharedTableVector& tables = instance.tables();
+
+ // Write data/elem segments into memories/tables.
+
+ for (const ElemSegment* seg : elemSegments_) {
+ if (seg->active()) {
+ RootedVal offsetVal(cx);
+ if (!seg->offset().evaluate(cx, instanceObj, &offsetVal)) {
+ return false; // OOM
+ }
+ uint32_t offset = offsetVal.get().i32();
+ uint32_t count = seg->length();
+
+ uint32_t tableLength = tables[seg->tableIndex]->length();
+ if (offset > tableLength || tableLength - offset < count) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_OUT_OF_BOUNDS);
+ return false;
+ }
+
+ if (!instance.initElems(seg->tableIndex, *seg, offset, 0, count)) {
+ return false; // OOM
+ }
+ }
+ }
+
+ if (memoryObj) {
+ size_t memoryLength = memoryObj->volatileMemoryLength();
+ uint8_t* memoryBase =
+ memoryObj->buffer().dataPointerEither().unwrap(/* memcpy */);
+
+ for (const DataSegment* seg : dataSegments_) {
+ if (!seg->active()) {
+ continue;
+ }
+
+ RootedVal offsetVal(cx);
+ if (!seg->offset().evaluate(cx, instanceObj, &offsetVal)) {
+ return false; // OOM
+ }
+ uint64_t offset = memoryObj->indexType() == IndexType::I32
+ ? offsetVal.get().i32()
+ : offsetVal.get().i64();
+ uint32_t count = seg->bytes.length();
+
+ if (offset > memoryLength || memoryLength - offset < count) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_OUT_OF_BOUNDS);
+ return false;
+ }
+ memcpy(memoryBase + uintptr_t(offset), seg->bytes.begin(), count);
+ }
+ }
+
+ return true;
+}
+
+static const Import& FindImportFunction(const ImportVector& imports,
+ uint32_t funcImportIndex) {
+ for (const Import& import : imports) {
+ if (import.kind != DefinitionKind::Function) {
+ continue;
+ }
+ if (funcImportIndex == 0) {
+ return import;
+ }
+ funcImportIndex--;
+ }
+ MOZ_CRASH("ran out of imports");
+}
+
+bool Module::instantiateFunctions(JSContext* cx,
+ const JSObjectVector& funcImports) const {
+#ifdef DEBUG
+ for (auto t : code().tiers()) {
+ MOZ_ASSERT(funcImports.length() == metadata(t).funcImports.length());
+ }
+#endif
+
+ if (metadata().isAsmJS()) {
+ return true;
+ }
+
+ Tier tier = code().stableTier();
+
+ for (size_t i = 0; i < metadata(tier).funcImports.length(); i++) {
+ if (!funcImports[i]->is<JSFunction>()) {
+ continue;
+ }
+
+ JSFunction* f = &funcImports[i]->as<JSFunction>();
+ if (!IsWasmExportedFunction(f)) {
+ continue;
+ }
+
+ uint32_t funcIndex = ExportedFunctionToFuncIndex(f);
+ Instance& instance = ExportedFunctionToInstance(f);
+ Tier otherTier = instance.code().stableTier();
+
+ const FuncType& exportFuncType = instance.metadata().getFuncExportType(
+ instance.metadata(otherTier).lookupFuncExport(funcIndex));
+ const FuncType& importFuncType =
+ metadata().getFuncImportType(metadata(tier).funcImports[i]);
+
+ if (!FuncType::strictlyEquals(exportFuncType, importFuncType)) {
+ const Import& import = FindImportFunction(imports_, i);
+ UniqueChars importModuleName = import.module.toQuotedString(cx);
+ UniqueChars importFieldName = import.field.toQuotedString(cx);
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_BAD_IMPORT_SIG,
+ importModuleName.get(), importFieldName.get());
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <typename T>
+static bool CheckLimits(JSContext* cx, T declaredMin,
+ const Maybe<T>& declaredMax, T defaultMax,
+ T actualLength, const Maybe<T>& actualMax, bool isAsmJS,
+ const char* kind) {
+ if (isAsmJS) {
+ MOZ_ASSERT(actualLength >= declaredMin);
+ MOZ_ASSERT(!declaredMax);
+ MOZ_ASSERT(actualLength == actualMax.value());
+ return true;
+ }
+
+ if (actualLength < declaredMin ||
+ actualLength > declaredMax.valueOr(defaultMax)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_BAD_IMP_SIZE, kind);
+ return false;
+ }
+
+ if ((actualMax && declaredMax && *actualMax > *declaredMax) ||
+ (!actualMax && declaredMax)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_BAD_IMP_MAX, kind);
+ return false;
+ }
+
+ return true;
+}
+
+static bool CheckSharing(JSContext* cx, bool declaredShared, bool isShared) {
+ if (isShared &&
+ !cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_NO_SHMEM_LINK);
+ return false;
+ }
+
+ if (declaredShared && !isShared) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_IMP_SHARED_REQD);
+ return false;
+ }
+
+ if (!declaredShared && isShared) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_IMP_SHARED_BANNED);
+ return false;
+ }
+
+ return true;
+}
+
+// asm.js module instantiation supplies its own buffer, but for wasm, create and
+// initialize the buffer if one is requested. Either way, the buffer is wrapped
+// in a WebAssembly.Memory object which is what the Instance stores.
+bool Module::instantiateMemory(JSContext* cx,
+ MutableHandle<WasmMemoryObject*> memory) const {
+ if (!metadata().usesMemory()) {
+ MOZ_ASSERT(!memory);
+ MOZ_ASSERT(AllSegmentsArePassive(dataSegments_));
+ return true;
+ }
+
+ MemoryDesc desc = *metadata().memory;
+ if (memory) {
+ MOZ_ASSERT_IF(metadata().isAsmJS(), memory->buffer().isPreparedForAsmJS());
+ MOZ_ASSERT_IF(!metadata().isAsmJS(), memory->buffer().isWasm());
+
+ if (memory->indexType() != desc.indexType()) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_BAD_IMP_INDEX,
+ ToString(memory->indexType()));
+ return false;
+ }
+
+ if (!CheckLimits(cx, desc.initialPages(), desc.maximumPages(),
+ /* defaultMax */ MaxMemoryPages(desc.indexType()),
+ /* actualLength */
+ memory->volatilePages(), memory->sourceMaxPages(),
+ metadata().isAsmJS(), "Memory")) {
+ return false;
+ }
+
+ if (!CheckSharing(cx, desc.isShared(), memory->isShared())) {
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(!metadata().isAsmJS());
+
+ if (desc.initialPages() > MaxMemoryPages(desc.indexType())) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_MEM_IMP_LIMIT);
+ return false;
+ }
+
+ RootedArrayBufferObjectMaybeShared buffer(cx);
+ if (!CreateWasmBuffer(cx, desc, &buffer)) {
+ return false;
+ }
+
+ RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmMemory));
+ memory.set(WasmMemoryObject::create(
+ cx, buffer, IsHugeMemoryEnabled(desc.indexType()), proto));
+ if (!memory) {
+ return false;
+ }
+ }
+
+ MOZ_RELEASE_ASSERT(memory->isHuge() == metadata().omitsBoundsChecks);
+
+ return true;
+}
+
+bool Module::instantiateTags(JSContext* cx,
+ WasmTagObjectVector& tagObjs) const {
+ size_t tagLength = metadata().tags.length();
+ if (tagLength == 0) {
+ return true;
+ }
+ size_t importedTagsLength = tagObjs.length();
+ if (tagObjs.length() <= tagLength && !tagObjs.resize(tagLength)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ uint32_t tagIndex = 0;
+ RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTag));
+ for (const TagDesc& desc : metadata().tags) {
+ if (tagIndex >= importedTagsLength) {
+ Rooted<WasmTagObject*> tagObj(
+ cx, WasmTagObject::create(cx, desc.type, proto));
+ if (!tagObj) {
+ return false;
+ }
+ tagObjs[tagIndex] = tagObj;
+ }
+ tagIndex++;
+ }
+ return true;
+}
+
+bool Module::instantiateImportedTable(JSContext* cx, const TableDesc& td,
+ Handle<WasmTableObject*> tableObj,
+ WasmTableObjectVector* tableObjs,
+ SharedTableVector* tables) const {
+ MOZ_ASSERT(tableObj);
+ MOZ_ASSERT(!metadata().isAsmJS());
+
+ Table& table = tableObj->table();
+ if (!CheckLimits(cx, td.initialLength, td.maximumLength,
+ /* declaredMin */ MaxTableLimitField,
+ /* actualLength */ table.length(), table.maximum(),
+ metadata().isAsmJS(), "Table")) {
+ return false;
+ }
+
+ if (!tables->append(&table)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ if (!tableObjs->append(tableObj)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ return true;
+}
+
+bool Module::instantiateLocalTable(JSContext* cx, const TableDesc& td,
+ WasmTableObjectVector* tableObjs,
+ SharedTableVector* tables) const {
+ if (td.initialLength > MaxTableLength) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_TABLE_IMP_LIMIT);
+ return false;
+ }
+
+ SharedTable table;
+ Rooted<WasmTableObject*> tableObj(cx);
+ if (td.isExported) {
+ RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTable));
+ tableObj.set(WasmTableObject::create(cx, td.initialLength, td.maximumLength,
+ td.elemType, proto));
+ if (!tableObj) {
+ return false;
+ }
+ table = &tableObj->table();
+ } else {
+ table = Table::create(cx, td, /* Handle<WasmTableObject*> = */ nullptr);
+ if (!table) {
+ return false;
+ }
+ }
+
+ // Note, appending a null pointer for non-exported local tables.
+ if (!tableObjs->append(tableObj.get())) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ if (!tables->emplaceBack(table)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ return true;
+}
+
+bool Module::instantiateTables(JSContext* cx,
+ const WasmTableObjectVector& tableImports,
+ MutableHandle<WasmTableObjectVector> tableObjs,
+ SharedTableVector* tables) const {
+ uint32_t tableIndex = 0;
+ for (const TableDesc& td : metadata().tables) {
+ if (tableIndex < tableImports.length()) {
+ Rooted<WasmTableObject*> tableObj(cx, tableImports[tableIndex]);
+ if (!instantiateImportedTable(cx, td, tableObj, &tableObjs.get(),
+ tables)) {
+ return false;
+ }
+ } else {
+ if (!instantiateLocalTable(cx, td, &tableObjs.get(), tables)) {
+ return false;
+ }
+ }
+ tableIndex++;
+ }
+ return true;
+}
+
+static bool EnsureExportedGlobalObject(JSContext* cx,
+ const ValVector& globalImportValues,
+ size_t globalIndex,
+ const GlobalDesc& global,
+ WasmGlobalObjectVector& globalObjs) {
+ if (globalIndex < globalObjs.length() && globalObjs[globalIndex]) {
+ return true;
+ }
+
+ RootedVal val(cx);
+ if (global.kind() == GlobalKind::Import) {
+ // If this is an import, then this must be a constant global that was
+ // provided without a global object. We must initialize it with the
+ // provided value while we still can differentiate this case.
+ MOZ_ASSERT(!global.isMutable());
+ val.set(Val(globalImportValues[globalIndex]));
+ } else {
+ // If this is not an import, then the initial value will be set by
+ // Instance::init() for indirect globals or else by CreateExportObject().
+ // In either case, we initialize with a default value here.
+ val.set(Val(global.type()));
+ }
+
+ RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmGlobal));
+ Rooted<WasmGlobalObject*> go(
+ cx, WasmGlobalObject::create(cx, val, global.isMutable(), proto));
+ if (!go) {
+ return false;
+ }
+
+ if (globalObjs.length() <= globalIndex &&
+ !globalObjs.resize(globalIndex + 1)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ globalObjs[globalIndex] = go;
+ return true;
+}
+
+bool Module::instantiateGlobals(JSContext* cx,
+ const ValVector& globalImportValues,
+ WasmGlobalObjectVector& globalObjs) const {
+ // If there are exported globals that aren't in globalObjs because they
+ // originate in this module or because they were immutable imports that came
+ // in as primitive values then we must create cells in the globalObjs for
+ // them here, as WasmInstanceObject::create() and CreateExportObject() will
+ // need the cells to exist.
+
+ const GlobalDescVector& globals = metadata().globals;
+
+ for (const Export& exp : exports_) {
+ if (exp.kind() != DefinitionKind::Global) {
+ continue;
+ }
+ unsigned globalIndex = exp.globalIndex();
+ const GlobalDesc& global = globals[globalIndex];
+ if (!EnsureExportedGlobalObject(cx, globalImportValues, globalIndex, global,
+ globalObjs)) {
+ return false;
+ }
+ }
+
+ // Imported globals that are not re-exported may also have received only a
+ // primitive value; these globals are always immutable. Assert that we do
+ // not need to create any additional Global objects for such imports.
+
+#ifdef DEBUG
+ size_t numGlobalImports = 0;
+ for (const Import& import : imports_) {
+ if (import.kind != DefinitionKind::Global) {
+ continue;
+ }
+ size_t globalIndex = numGlobalImports++;
+ const GlobalDesc& global = globals[globalIndex];
+ MOZ_ASSERT(global.importIndex() == globalIndex);
+ MOZ_ASSERT_IF(global.isIndirect(),
+ globalIndex < globalObjs.length() || globalObjs[globalIndex]);
+ }
+ MOZ_ASSERT_IF(!metadata().isAsmJS(),
+ numGlobalImports == globals.length() ||
+ !globals[numGlobalImports].isImport());
+#endif
+ return true;
+}
+
+static bool GetFunctionExport(JSContext* cx,
+ Handle<WasmInstanceObject*> instanceObj,
+ const JSObjectVector& funcImports,
+ uint32_t funcIndex, MutableHandleFunction func) {
+ if (funcIndex < funcImports.length() &&
+ funcImports[funcIndex]->is<JSFunction>()) {
+ JSFunction* f = &funcImports[funcIndex]->as<JSFunction>();
+ if (IsWasmExportedFunction(f)) {
+ func.set(f);
+ return true;
+ }
+ }
+
+ return instanceObj->getExportedFunction(cx, instanceObj, funcIndex, func);
+}
+
+static bool GetGlobalExport(JSContext* cx,
+ Handle<WasmInstanceObject*> instanceObj,
+ const JSObjectVector& funcImports,
+ const GlobalDesc& global, uint32_t globalIndex,
+ const ValVector& globalImportValues,
+ const WasmGlobalObjectVector& globalObjs,
+ MutableHandleValue val) {
+ // A global object for this index is guaranteed to exist by
+ // instantiateGlobals.
+ Rooted<WasmGlobalObject*> globalObj(cx, globalObjs[globalIndex]);
+ val.setObject(*globalObj);
+
+ // We are responsible to set the initial value of the global object here if
+ // it's not imported or indirect. Imported global objects have their initial
+ // value set by their defining module, or are set by
+ // EnsureExportedGlobalObject when a constant value is provided as an import.
+ // Indirect exported globals that are not imported, are initialized in
+ // Instance::init.
+ if (global.isIndirect() || global.isImport()) {
+ return true;
+ }
+
+ // This must be an exported immutable global defined in this module. The
+ // instance either has compiled the value into the code or has its own copy
+ // in its global data area. Either way, we must initialize the global object
+ // with the same initial value.
+ MOZ_ASSERT(!global.isMutable());
+ MOZ_ASSERT(!global.isImport());
+ RootedVal globalVal(cx);
+ MOZ_RELEASE_ASSERT(!global.isImport());
+ const InitExpr& init = global.initExpr();
+ if (!init.evaluate(cx, instanceObj, &globalVal)) {
+ return false;
+ }
+ globalObj->val() = globalVal;
+ return true;
+}
+
+static bool CreateExportObject(
+ JSContext* cx, Handle<WasmInstanceObject*> instanceObj,
+ const JSObjectVector& funcImports, const WasmTableObjectVector& tableObjs,
+ Handle<WasmMemoryObject*> memoryObj, const WasmTagObjectVector& tagObjs,
+ const ValVector& globalImportValues,
+ const WasmGlobalObjectVector& globalObjs, const ExportVector& exports) {
+ const Instance& instance = instanceObj->instance();
+ const Metadata& metadata = instance.metadata();
+ const GlobalDescVector& globals = metadata.globals;
+
+ if (metadata.isAsmJS() && exports.length() == 1 &&
+ exports[0].fieldName().isEmpty()) {
+ RootedFunction func(cx);
+ if (!GetFunctionExport(cx, instanceObj, funcImports, exports[0].funcIndex(),
+ &func)) {
+ return false;
+ }
+ instanceObj->initExportsObj(*func.get());
+ return true;
+ }
+
+ RootedObject exportObj(cx);
+ uint8_t propertyAttr = JSPROP_ENUMERATE;
+
+ if (metadata.isAsmJS()) {
+ exportObj = NewPlainObject(cx);
+ } else {
+ exportObj = NewPlainObjectWithProto(cx, nullptr);
+ propertyAttr |= JSPROP_READONLY | JSPROP_PERMANENT;
+ }
+ if (!exportObj) {
+ return false;
+ }
+
+ for (const Export& exp : exports) {
+ JSAtom* atom = exp.fieldName().toAtom(cx);
+ if (!atom) {
+ return false;
+ }
+
+ RootedId id(cx, AtomToId(atom));
+ RootedValue val(cx);
+ switch (exp.kind()) {
+ case DefinitionKind::Function: {
+ RootedFunction func(cx);
+ if (!GetFunctionExport(cx, instanceObj, funcImports, exp.funcIndex(),
+ &func)) {
+ return false;
+ }
+ val = ObjectValue(*func);
+ break;
+ }
+ case DefinitionKind::Table: {
+ val = ObjectValue(*tableObjs[exp.tableIndex()]);
+ break;
+ }
+ case DefinitionKind::Memory: {
+ val = ObjectValue(*memoryObj);
+ break;
+ }
+ case DefinitionKind::Global: {
+ const GlobalDesc& global = globals[exp.globalIndex()];
+ if (!GetGlobalExport(cx, instanceObj, funcImports, global,
+ exp.globalIndex(), globalImportValues, globalObjs,
+ &val)) {
+ return false;
+ }
+ break;
+ }
+ case DefinitionKind::Tag: {
+ val = ObjectValue(*tagObjs[exp.tagIndex()]);
+ break;
+ }
+ }
+
+ if (!JS_DefinePropertyById(cx, exportObj, id, val, propertyAttr)) {
+ return false;
+ }
+ }
+
+ if (!metadata.isAsmJS()) {
+ if (!PreventExtensions(cx, exportObj)) {
+ return false;
+ }
+ }
+
+ instanceObj->initExportsObj(*exportObj);
+ return true;
+}
+
+bool Module::instantiate(JSContext* cx, ImportValues& imports,
+ HandleObject instanceProto,
+ MutableHandle<WasmInstanceObject*> instance) const {
+ MOZ_RELEASE_ASSERT(cx->wasm().haveSignalHandlers);
+
+ if (!instantiateFunctions(cx, imports.funcs)) {
+ return false;
+ }
+
+ Rooted<WasmMemoryObject*> memory(cx, imports.memory);
+ if (!instantiateMemory(cx, &memory)) {
+ return false;
+ }
+
+ // Note that the following will extend imports.exceptionObjs with wrappers for
+ // the local (non-imported) exceptions of the module.
+ // The resulting vector is sparse, i.e., it will be null in slots that contain
+ // exceptions that are neither exported or imported.
+ // On the contrary, all the slots of exceptionTags will be filled with
+ // unique tags.
+
+ if (!instantiateTags(cx, imports.tagObjs)) {
+ return false;
+ }
+
+ // Note that tableObjs is sparse: it will be null in slots that contain
+ // tables that are neither exported nor imported.
+
+ Rooted<WasmTableObjectVector> tableObjs(cx);
+ SharedTableVector tables;
+ if (!instantiateTables(cx, imports.tables, &tableObjs, &tables)) {
+ return false;
+ }
+
+ if (!instantiateGlobals(cx, imports.globalValues, imports.globalObjs)) {
+ return false;
+ }
+
+ UniqueDebugState maybeDebug;
+ if (metadata().debugEnabled) {
+ maybeDebug = cx->make_unique<DebugState>(*code_, *this);
+ if (!maybeDebug) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ instance.set(WasmInstanceObject::create(
+ cx, code_, dataSegments_, elemSegments_, metadata().instanceDataLength,
+ memory, std::move(tables), imports.funcs, metadata().globals,
+ imports.globalValues, imports.globalObjs, imports.tagObjs, instanceProto,
+ std::move(maybeDebug)));
+ if (!instance) {
+ return false;
+ }
+
+ if (!CreateExportObject(cx, instance, imports.funcs, tableObjs.get(), memory,
+ imports.tagObjs, imports.globalValues,
+ imports.globalObjs, exports_)) {
+ return false;
+ }
+
+ // Register the instance with the Realm so that it can find out about global
+ // events like profiling being enabled in the realm. Registration does not
+ // require a fully-initialized instance and must precede initSegments as the
+ // final pre-requisite for a live instance.
+
+ if (!cx->realm()->wasm.registerInstance(cx, instance)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ // Perform initialization as the final step after the instance is fully
+ // constructed since this can make the instance live to content (even if the
+ // start function fails).
+
+ if (!initSegments(cx, instance, memory)) {
+ return false;
+ }
+
+ // Now that the instance is fully live and initialized, the start function.
+ // Note that failure may cause instantiation to throw, but the instance may
+ // still be live via edges created by initSegments or the start function.
+
+ if (metadata().startFuncIndex) {
+ FixedInvokeArgs<0> args(cx);
+ if (!instance->instance().callExport(cx, *metadata().startFuncIndex,
+ args)) {
+ return false;
+ }
+ }
+
+ JSUseCounter useCounter =
+ metadata().isAsmJS() ? JSUseCounter::ASMJS : JSUseCounter::WASM;
+ cx->runtime()->setUseCounter(instance, useCounter);
+
+ if (cx->options().testWasmAwaitTier2()) {
+ testingBlockOnTier2Complete();
+ }
+
+ return true;
+}