summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmModule.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/wasm/WasmModule.cpp')
-rw-r--r--js/src/wasm/WasmModule.cpp1360
1 files changed, 1360 insertions, 0 deletions
diff --git a/js/src/wasm/WasmModule.cpp b/js/src/wasm/WasmModule.cpp
new file mode 100644
index 0000000000..033810948a
--- /dev/null
+++ b/js/src/wasm/WasmModule.cpp
@@ -0,0 +1,1360 @@
+/* -*- 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 <thread>
+
+#include "jit/JitOptions.h"
+#include "js/BuildId.h" // JS::BuildIdCharVector
+#include "js/experimental/TypedData.h" // JS_NewUint8Array
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "threading/LockGuard.h"
+#include "vm/HelperThreadState.h" // Tier2GeneratorTask
+#include "vm/PlainObject.h" // js::PlainObject
+#include "wasm/TypedObject.h"
+#include "wasm/WasmBaselineCompile.h"
+#include "wasm/WasmCompile.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"
+
+using namespace js;
+using namespace js::jit;
+using namespace js::wasm;
+
+class Module::Tier2GeneratorTaskImpl : public Tier2GeneratorTask {
+ SharedCompileArgs compileArgs_;
+ SharedBytes bytecode_;
+ SharedModule module_;
+ Atomic<bool> cancelled_;
+ JSTelemetrySender telemetrySender_;
+
+ public:
+ Tier2GeneratorTaskImpl(const CompileArgs& compileArgs,
+ const ShareableBytes& bytecode, Module& module,
+ JSTelemetrySender telemetrySender)
+ : compileArgs_(&compileArgs),
+ bytecode_(&bytecode),
+ module_(&module),
+ cancelled_(false),
+ telemetrySender_(telemetrySender) {}
+
+ ~Tier2GeneratorTaskImpl() override {
+ module_->tier2Listener_ = nullptr;
+ module_->testingTier2Active_ = false;
+ }
+
+ void cancel() override { cancelled_ = true; }
+
+ void runHelperThreadTask(AutoLockHelperThreadState& locked) override {
+ {
+ AutoUnlockHelperThreadState unlock(locked);
+ CompileTier2(*compileArgs_, bytecode_->bytes, *module_, &cancelled_,
+ telemetrySender_);
+ }
+
+ // 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
+ // CONSUMER condition 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_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,
+ JSTelemetrySender telemetrySender) {
+ MOZ_ASSERT(!testingTier2Active_);
+
+ auto task = MakeUnique<Tier2GeneratorTaskImpl>(args, bytecode, *this,
+ telemetrySender);
+ 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().
+
+ if (!code().setTier2(std::move(code2), linkData2)) {
+ 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).
+ {
+ // 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().lock();
+ auto stubs2 = code().codeTier(Tier::Optimized).lazyStubs().lock();
+
+ MOZ_ASSERT(stubs2->empty());
+
+ Uint32Vector funcExportIndices;
+ for (size_t i = 0; i < metadataTier1.funcExports.length(); i++) {
+ const FuncExport& fe = metadataTier1.funcExports[i];
+ if (fe.hasEagerStubs()) {
+ continue;
+ }
+ if (!stubs1->hasStub(fe.funcIndex())) {
+ continue;
+ }
+ if (!funcExportIndices.emplaceBack(i)) {
+ return false;
+ }
+ }
+
+ const CodeTier& tier2 = code().codeTier(Tier::Optimized);
+
+ Maybe<size_t> stub2Index;
+ if (!stubs2->createTier2(funcExportIndices, tier2, &stub2Index)) {
+ return false;
+ }
+
+ // 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 vector.
+
+ 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_) {
+ serialize(linkData2, *tier2Listener_);
+ tier2Listener_ = nullptr;
+ }
+ testingTier2Active_ = false;
+
+ return true;
+}
+
+void Module::testingBlockOnTier2Complete() const {
+ while (testingTier2Active_) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+}
+
+/* virtual */
+size_t Module::serializedSize(const LinkData& linkData) const {
+ JS::BuildIdCharVector buildId;
+ {
+ AutoEnterOOMUnsafeRegion oom;
+ if (!GetOptimizedEncodingBuildId(&buildId)) {
+ oom.crash("getting build id");
+ }
+ }
+
+ return SerializedPodVectorSize(buildId) + linkData.serializedSize() +
+ SerializedVectorSize(imports_) + SerializedVectorSize(exports_) +
+ SerializedVectorSize(dataSegments_) +
+ SerializedVectorSize(elemSegments_) +
+ SerializedVectorSize(customSections_) + code_->serializedSize();
+}
+
+/* virtual */
+void Module::serialize(const LinkData& linkData, uint8_t* begin,
+ size_t size) const {
+ MOZ_RELEASE_ASSERT(!metadata().debugEnabled);
+ MOZ_RELEASE_ASSERT(code_->hasTier(Tier::Serialized));
+
+ JS::BuildIdCharVector buildId;
+ {
+ AutoEnterOOMUnsafeRegion oom;
+ if (!GetOptimizedEncodingBuildId(&buildId)) {
+ oom.crash("getting build id");
+ }
+ }
+
+ uint8_t* cursor = begin;
+ cursor = SerializePodVector(cursor, buildId);
+ cursor = linkData.serialize(cursor);
+ cursor = SerializeVector(cursor, imports_);
+ cursor = SerializeVector(cursor, exports_);
+ cursor = SerializeVector(cursor, dataSegments_);
+ cursor = SerializeVector(cursor, elemSegments_);
+ cursor = SerializeVector(cursor, customSections_);
+ cursor = code_->serialize(cursor, linkData);
+ MOZ_RELEASE_ASSERT(cursor == begin + size);
+}
+
+/* static */
+MutableModule Module::deserialize(const uint8_t* begin, size_t size,
+ Metadata* maybeMetadata) {
+ MutableMetadata metadata(maybeMetadata);
+ if (!metadata) {
+ metadata = js_new<Metadata>();
+ if (!metadata) {
+ return nullptr;
+ }
+ }
+
+ const uint8_t* cursor = begin;
+
+ JS::BuildIdCharVector currentBuildId;
+ if (!GetOptimizedEncodingBuildId(&currentBuildId)) {
+ return nullptr;
+ }
+
+ JS::BuildIdCharVector deserializedBuildId;
+ cursor = DeserializePodVector(cursor, &deserializedBuildId);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ MOZ_RELEASE_ASSERT(EqualContainers(currentBuildId, deserializedBuildId));
+
+ LinkData linkData(Tier::Serialized);
+ cursor = linkData.deserialize(cursor);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ ImportVector imports;
+ cursor = DeserializeVector(cursor, &imports);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ ExportVector exports;
+ cursor = DeserializeVector(cursor, &exports);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ DataSegmentVector dataSegments;
+ cursor = DeserializeVector(cursor, &dataSegments);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ ElemSegmentVector elemSegments;
+ cursor = DeserializeVector(cursor, &elemSegments);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ CustomSectionVector customSections;
+ cursor = DeserializeVector(cursor, &customSections);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ SharedCode code;
+ cursor = Code::deserialize(cursor, linkData, *metadata, &code);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ MOZ_RELEASE_ASSERT(cursor == begin + size);
+ MOZ_RELEASE_ASSERT(!!maybeMetadata == code->metadata().isAsmJS());
+
+ if (metadata->nameCustomSectionIndex) {
+ metadata->namePayload =
+ customSections[*metadata->nameCustomSectionIndex].payload;
+ } else {
+ MOZ_RELEASE_ASSERT(!metadata->moduleName);
+ MOZ_RELEASE_ASSERT(metadata->funcNames.empty());
+ }
+
+ return js_new<Module>(*code, std::move(imports), std::move(exports),
+ std::move(dataSegments), std::move(elemSegments),
+ std::move(customSections), nullptr, nullptr, nullptr,
+ /* loggingDeserialized = */ true);
+}
+
+void Module::serialize(const LinkData& linkData,
+ JS::OptimizedEncodingListener& listener) const {
+ auto bytes = MakeUnique<JS::OptimizedEncodingBytes>();
+ if (!bytes || !bytes->resize(serializedSize(linkData))) {
+ return;
+ }
+
+ serialize(linkData, bytes->begin(), bytes->length());
+
+ listener.storeOptimizedEncoding(std::move(bytes));
+}
+
+/* virtual */
+JSObject* Module::createObject(JSContext* cx) const {
+ if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly)) {
+ return nullptr;
+ }
+
+ RootedObject proto(
+ cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
+ 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() +
+ 12 /* "()" + 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() ? '+' : '-');
+
+ 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);
+
+ if (debugUnlinkedCode_) {
+ *data += debugUnlinkedCode_->sizeOfExcludingThis(mallocSizeOf);
+ }
+}
+
+void Module::initGCMallocBytesExcludingCode() {
+ // The size doesn't have to be exact so use the serialization framework to
+ // calculate a value.
+ gcMallocBytesExcludingCode_ = sizeof(*this) + SerializedVectorSize(imports_) +
+ SerializedVectorSize(exports_) +
+ SerializedVectorSize(dataSegments_) +
+ SerializedVectorSize(elemSegments_) +
+ SerializedVectorSize(customSections_);
+}
+
+// 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 {
+ RootedPlainObject result(cx, NewBuiltinClassInstance<PlainObject>(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, NewObjectWithGivenProto<PlainObject>(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;
+}
+
+static uint32_t EvaluateOffsetInitExpr(const ValVector& globalImportValues,
+ InitExpr initExpr) {
+ switch (initExpr.kind()) {
+ case InitExpr::Kind::Constant:
+ return initExpr.val().i32();
+ case InitExpr::Kind::GetGlobal:
+ return globalImportValues[initExpr.globalIndex()].i32();
+ case InitExpr::Kind::RefFunc:
+ break;
+ }
+
+ MOZ_CRASH("bad initializer expression");
+}
+
+#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, HandleWasmInstanceObject instanceObj,
+ HandleWasmMemoryObject memoryObj,
+ const ValVector& globalImportValues) 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()) {
+ uint32_t offset =
+ EvaluateOffsetInitExpr(globalImportValues, seg->offset());
+ 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) {
+ uint32_t memoryLength = memoryObj->volatileMemoryLength32();
+ uint8_t* memoryBase =
+ memoryObj->buffer().dataPointerEither().unwrap(/* memcpy */);
+
+ for (const DataSegment* seg : dataSegments_) {
+ if (!seg->active()) {
+ continue;
+ }
+
+ uint32_t offset =
+ EvaluateOffsetInitExpr(globalImportValues, seg->offset());
+ 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 + 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 JSFunctionVector& 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++) {
+ JSFunction* f = funcImports[i];
+ if (!IsWasmExportedFunction(f)) {
+ continue;
+ }
+
+ uint32_t funcIndex = ExportedFunctionToFuncIndex(f);
+ Instance& instance = ExportedFunctionToInstance(f);
+ Tier otherTier = instance.code().stableTier();
+
+ const FuncExport& funcExport =
+ instance.metadata(otherTier).lookupFuncExport(funcIndex);
+
+ if (funcExport.funcType() != metadata(tier).funcImports[i].funcType()) {
+ const Import& import = FindImportFunction(imports_, i);
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_BAD_IMPORT_SIG, import.module.get(),
+ import.field.get());
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <typename T>
+static bool CheckLimits(JSContext* cx, T declaredMin,
+ const Maybe<T>& declaredMax, 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(UINT32_MAX)) {
+ 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,
+ MutableHandleWasmMemoryObject memory) const {
+ if (!metadata().usesMemory()) {
+ MOZ_ASSERT(!memory);
+ MOZ_ASSERT(AllSegmentsArePassive(dataSegments_));
+ return true;
+ }
+
+ uint64_t declaredMin = metadata().minMemoryLength;
+ Maybe<uint64_t> declaredMax = metadata().maxMemoryLength;
+ bool declaredShared = metadata().memoryUsage == MemoryUsage::Shared;
+
+ if (memory) {
+ MOZ_ASSERT_IF(metadata().isAsmJS(), memory->buffer().isPreparedForAsmJS());
+ MOZ_ASSERT_IF(!metadata().isAsmJS(), memory->buffer().isWasm());
+
+ if (!CheckLimits(cx, declaredMin, declaredMax,
+ uint64_t(memory->volatileMemoryLength32()),
+ memory->buffer().wasmMaxSize(), metadata().isAsmJS(),
+ "Memory")) {
+ return false;
+ }
+
+ if (!CheckSharing(cx, declaredShared, memory->isShared())) {
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(!metadata().isAsmJS());
+
+ if (declaredMin / PageSize > MaxMemory32Pages) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_WASM_MEM_IMP_LIMIT);
+ return false;
+ }
+
+ RootedArrayBufferObjectMaybeShared buffer(cx);
+ Limits l(declaredMin, declaredMax,
+ declaredShared ? Shareable::True : Shareable::False);
+ if (!CreateWasmBuffer(cx, MemoryKind::Memory32, l, &buffer)) {
+ return false;
+ }
+
+ RootedObject proto(
+ cx, &cx->global()->getPrototype(JSProto_WasmMemory).toObject());
+ memory.set(WasmMemoryObject::create(cx, buffer, proto));
+ if (!memory) {
+ return false;
+ }
+ }
+
+ MOZ_RELEASE_ASSERT(memory->isHuge() == metadata().omitsBoundsChecks);
+
+ return true;
+}
+
+#ifdef ENABLE_WASM_EXCEPTIONS
+bool Module::instantiateImportedException(
+ JSContext* cx, Handle<WasmExceptionObject*> exnObj,
+ WasmExceptionObjectVector& exnObjs, SharedExceptionTagVector* tags) const {
+ MOZ_ASSERT(exnObj);
+ // The check whether the EventDesc signature matches the exnObj value types
+ // is done by js::wasm::GetImports().
+
+ // Collects the exception tag from the imported exception.
+ ExceptionTag& tag = exnObj->tag();
+
+ if (!tags->append(&tag)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ return true;
+}
+
+bool Module::instantiateLocalException(JSContext* cx, const EventDesc& ed,
+ WasmExceptionObjectVector& exnObjs,
+ SharedExceptionTagVector* tags,
+ uint32_t exnIndex) const {
+ SharedExceptionTag tag;
+ // Extend exnObjs in anticipation of an exported exception object.
+ if (exnObjs.length() <= exnIndex && !exnObjs.resize(exnIndex + 1)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ if (ed.isExport) {
+ // If the exception description is exported, create an export exception
+ // object for it.
+ RootedObject proto(
+ cx, &cx->global()->getPrototype(JSProto_WasmException).toObject());
+ RootedWasmExceptionObject exnObj(
+ cx, WasmExceptionObject::create(cx, ed.type, proto));
+ if (!exnObj) {
+ return false;
+ }
+ // Take the exception tag that was created inside the WasmExceptionObject.
+ tag = &exnObj->tag();
+ // Save the new export exception object.
+ exnObjs[exnIndex] = exnObj;
+ } else {
+ // Create a new tag for every non exported exception.
+ tag = SharedExceptionTag(cx->new_<ExceptionTag>());
+ if (!tag) {
+ return false;
+ }
+ // The exnObj is null if the exception is neither exported nor imported.
+ }
+ // Collect a tag for every exception.
+ if (!tags->emplaceBack(tag)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ return true;
+}
+
+bool Module::instantiateExceptions(JSContext* cx,
+ WasmExceptionObjectVector& exnObjs,
+ SharedExceptionTagVector* tags) const {
+ uint32_t exnIndex = 0;
+ for (const EventDesc& ed : metadata().events) {
+ if (exnIndex < exnObjs.length()) {
+ Rooted<WasmExceptionObject*> exnObj(cx, exnObjs[exnIndex]);
+ if (!instantiateImportedException(cx, exnObj, exnObjs, tags)) {
+ return false;
+ }
+ } else {
+ if (!instantiateLocalException(cx, ed, exnObjs, tags, exnIndex)) {
+ return false;
+ }
+ }
+ exnIndex++;
+ }
+ return true;
+}
+#endif
+
+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, 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.importedOrExported) {
+ RootedObject proto(
+ cx, &cx->global()->getPrototype(JSProto_WasmTable).toObject());
+ 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, /* HandleWasmTableObject = */ nullptr);
+ if (!table) {
+ ReportOutOfMemory(cx);
+ 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).toObject());
+ RootedWasmGlobalObject 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;
+}
+
+SharedCode Module::getDebugEnabledCode() const {
+ MOZ_ASSERT(metadata().debugEnabled);
+ MOZ_ASSERT(debugUnlinkedCode_);
+ MOZ_ASSERT(debugLinkData_);
+
+ // The first time through, use the pre-linked code in the module but
+ // mark it as having been claimed. Subsequently, instantiate the copy of the
+ // code bytes that we keep around for debugging instead, because the
+ // debugger may patch the pre-linked code at any time.
+ if (debugCodeClaimed_.compareExchange(false, true)) {
+ return code_;
+ }
+
+ Tier tier = Tier::Baseline;
+ auto segment =
+ ModuleSegment::create(tier, *debugUnlinkedCode_, *debugLinkData_);
+ if (!segment) {
+ return nullptr;
+ }
+
+ UniqueMetadataTier metadataTier = js::MakeUnique<MetadataTier>(tier);
+ if (!metadataTier || !metadataTier->clone(metadata(tier))) {
+ return nullptr;
+ }
+
+ auto codeTier =
+ js::MakeUnique<CodeTier>(std::move(metadataTier), std::move(segment));
+ if (!codeTier) {
+ return nullptr;
+ }
+
+ JumpTables jumpTables;
+ if (!jumpTables.init(CompileMode::Once, codeTier->segment(),
+ metadata(tier).codeRanges)) {
+ return nullptr;
+ }
+
+ MutableCode debugCode =
+ js_new<Code>(std::move(codeTier), metadata(), std::move(jumpTables));
+ if (!debugCode || !debugCode->initialize(*debugLinkData_)) {
+ return nullptr;
+ }
+
+ return debugCode;
+}
+
+static bool GetFunctionExport(JSContext* cx,
+ HandleWasmInstanceObject instanceObj,
+ const JSFunctionVector& funcImports,
+ uint32_t funcIndex, MutableHandleFunction func) {
+ if (funcIndex < funcImports.length() &&
+ IsWasmExportedFunction(funcImports[funcIndex])) {
+ func.set(funcImports[funcIndex]);
+ return true;
+ }
+
+ return instanceObj->getExportedFunction(cx, instanceObj, funcIndex, func);
+}
+
+static bool GetGlobalExport(JSContext* cx, HandleWasmInstanceObject instanceObj,
+ const JSFunctionVector& 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.
+ RootedWasmGlobalObject 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);
+ switch (global.kind()) {
+ case GlobalKind::Variable: {
+ const InitExpr& init = global.initExpr();
+ switch (init.kind()) {
+ case InitExpr::Kind::Constant:
+ globalVal.set(Val(init.val()));
+ break;
+ case InitExpr::Kind::GetGlobal:
+ globalVal.set(Val(globalImportValues[init.globalIndex()]));
+ break;
+ case InitExpr::Kind::RefFunc:
+ RootedFunction func(cx);
+ if (!GetFunctionExport(cx, instanceObj, funcImports,
+ init.refFuncIndex(), &func)) {
+ return false;
+ }
+ globalVal.set(
+ Val(ValType(RefType::func()), FuncRef::fromJSFunction(func)));
+ }
+ break;
+ }
+ case GlobalKind::Constant: {
+ globalVal.set(Val(global.constantValue()));
+ break;
+ }
+ case GlobalKind::Import: {
+ MOZ_CRASH();
+ }
+ }
+
+ globalObj->val() = globalVal;
+ return true;
+}
+
+static bool CreateExportObject(JSContext* cx,
+ HandleWasmInstanceObject instanceObj,
+ const JSFunctionVector& funcImports,
+ const WasmTableObjectVector& tableObjs,
+ HandleWasmMemoryObject memoryObj,
+ const WasmExceptionObjectVector& exceptionObjs,
+ 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 &&
+ strlen(exports[0].fieldName()) == 0) {
+ 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 = NewBuiltinClassInstance<PlainObject>(cx);
+ } else {
+ exportObj = NewObjectWithGivenProto<PlainObject>(cx, nullptr);
+ propertyAttr |= JSPROP_READONLY | JSPROP_PERMANENT;
+ }
+ if (!exportObj) {
+ return false;
+ }
+
+ for (const Export& exp : exports) {
+ JSAtom* atom =
+ AtomizeUTF8Chars(cx, exp.fieldName(), strlen(exp.fieldName()));
+ 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;
+ }
+#ifdef ENABLE_WASM_EXCEPTIONS
+ case DefinitionKind::Event: {
+ val = ObjectValue(*exceptionObjs[exp.eventIndex()]);
+ break;
+ }
+#endif
+ }
+
+ 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,
+ MutableHandleWasmInstanceObject instance) const {
+ MOZ_RELEASE_ASSERT(cx->wasm().haveSignalHandlers);
+
+ if (!instantiateFunctions(cx, imports.funcs)) {
+ return false;
+ }
+
+ RootedWasmMemoryObject 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.
+
+ SharedExceptionTagVector tags;
+#ifdef ENABLE_WASM_EXCEPTIONS
+ if (!instantiateExceptions(cx, imports.exceptionObjs, &tags)) {
+ return false;
+ }
+#endif
+
+ // 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;
+ }
+
+ UniqueTlsData tlsData = CreateTlsData(metadata().globalDataLength);
+ if (!tlsData) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ SharedCode code;
+ UniqueDebugState maybeDebug;
+ if (metadata().debugEnabled) {
+ code = getDebugEnabledCode();
+ if (!code) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ maybeDebug = cx->make_unique<DebugState>(*code, *this);
+ if (!maybeDebug) {
+ return false;
+ }
+ } else {
+ code = code_;
+ }
+
+ instance.set(WasmInstanceObject::create(
+ cx, code, dataSegments_, elemSegments_, std::move(tlsData), memory,
+ std::move(tags), std::move(tables), imports.funcs, metadata().globals,
+ imports.globalValues, imports.globalObjs, instanceProto,
+ std::move(maybeDebug)));
+ if (!instance) {
+ return false;
+ }
+
+ if (!CreateExportObject(cx, instance, imports.funcs, tableObjs.get(), memory,
+ imports.exceptionObjs, 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)) {
+ 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, imports.globalValues)) {
+ 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 (metadata().usesDuplicateImports) {
+ cx->runtime()->setUseCounter(instance,
+ JSUseCounter::WASM_DUPLICATE_IMPORTS);
+ }
+
+ if (cx->options().testWasmAwaitTier2()) {
+ testingBlockOnTier2Complete();
+ }
+
+ return true;
+}