/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * * 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 "jsnspr.h" #include "jit/JitOptions.h" #include "wasm/WasmCompile.h" #include "wasm/WasmInstance.h" #include "wasm/WasmJS.h" #include "wasm/WasmSerialize.h" #include "jsatominlines.h" #include "vm/ArrayBufferObject-inl.h" #include "vm/Debugger-inl.h" using namespace js; using namespace js::jit; using namespace js::wasm; using mozilla::IsNaN; const char wasm::InstanceExportField[] = "exports"; #if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) // On MIPS, CodeLabels are instruction immediates so InternalLinks only // patch instruction immediates. LinkData::InternalLink::InternalLink(Kind kind) { MOZ_ASSERT(kind == CodeLabel || kind == InstructionImmediate); } bool LinkData::InternalLink::isRawPointerPatch() { return false; } #else // On the rest, CodeLabels are raw pointers so InternalLinks only patch // raw pointers. LinkData::InternalLink::InternalLink(Kind kind) { MOZ_ASSERT(kind == CodeLabel || kind == RawPointer); } bool LinkData::InternalLink::isRawPointerPatch() { return true; } #endif size_t LinkData::SymbolicLinkArray::serializedSize() const { size_t size = 0; for (const Uint32Vector& offsets : *this) size += SerializedPodVectorSize(offsets); return size; } uint8_t* LinkData::SymbolicLinkArray::serialize(uint8_t* cursor) const { for (const Uint32Vector& offsets : *this) cursor = SerializePodVector(cursor, offsets); return cursor; } const uint8_t* LinkData::SymbolicLinkArray::deserialize(const uint8_t* cursor) { for (Uint32Vector& offsets : *this) { cursor = DeserializePodVector(cursor, &offsets); if (!cursor) return nullptr; } return cursor; } size_t LinkData::SymbolicLinkArray::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { size_t size = 0; for (const Uint32Vector& offsets : *this) size += offsets.sizeOfExcludingThis(mallocSizeOf); return size; } size_t LinkData::serializedSize() const { return sizeof(pod()) + SerializedPodVectorSize(internalLinks) + symbolicLinks.serializedSize(); } uint8_t* LinkData::serialize(uint8_t* cursor) const { cursor = WriteBytes(cursor, &pod(), sizeof(pod())); cursor = SerializePodVector(cursor, internalLinks); cursor = symbolicLinks.serialize(cursor); return cursor; } const uint8_t* LinkData::deserialize(const uint8_t* cursor) { (cursor = ReadBytes(cursor, &pod(), sizeof(pod()))) && (cursor = DeserializePodVector(cursor, &internalLinks)) && (cursor = symbolicLinks.deserialize(cursor)); return cursor; } size_t LinkData::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { return internalLinks.sizeOfExcludingThis(mallocSizeOf) + symbolicLinks.sizeOfExcludingThis(mallocSizeOf); } size_t Import::serializedSize() const { return module.serializedSize() + field.serializedSize() + sizeof(kind); } uint8_t* Import::serialize(uint8_t* cursor) const { cursor = module.serialize(cursor); cursor = field.serialize(cursor); cursor = WriteScalar(cursor, kind); return cursor; } const uint8_t* Import::deserialize(const uint8_t* cursor) { (cursor = module.deserialize(cursor)) && (cursor = field.deserialize(cursor)) && (cursor = ReadScalar(cursor, &kind)); return cursor; } size_t Import::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { return module.sizeOfExcludingThis(mallocSizeOf) + field.sizeOfExcludingThis(mallocSizeOf); } Export::Export(UniqueChars fieldName, uint32_t index, DefinitionKind kind) : fieldName_(Move(fieldName)) { pod.kind_ = kind; pod.index_ = index; } Export::Export(UniqueChars fieldName, DefinitionKind kind) : fieldName_(Move(fieldName)) { pod.kind_ = kind; pod.index_ = 0; } uint32_t Export::funcIndex() const { MOZ_ASSERT(pod.kind_ == DefinitionKind::Function); return pod.index_; } uint32_t Export::globalIndex() const { MOZ_ASSERT(pod.kind_ == DefinitionKind::Global); return pod.index_; } size_t Export::serializedSize() const { return fieldName_.serializedSize() + sizeof(pod); } uint8_t* Export::serialize(uint8_t* cursor) const { cursor = fieldName_.serialize(cursor); cursor = WriteBytes(cursor, &pod, sizeof(pod)); return cursor; } const uint8_t* Export::deserialize(const uint8_t* cursor) { (cursor = fieldName_.deserialize(cursor)) && (cursor = ReadBytes(cursor, &pod, sizeof(pod))); return cursor; } size_t Export::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { return fieldName_.sizeOfExcludingThis(mallocSizeOf); } size_t ElemSegment::serializedSize() const { return sizeof(tableIndex) + sizeof(offset) + SerializedPodVectorSize(elemFuncIndices) + SerializedPodVectorSize(elemCodeRangeIndices); } uint8_t* ElemSegment::serialize(uint8_t* cursor) const { cursor = WriteBytes(cursor, &tableIndex, sizeof(tableIndex)); cursor = WriteBytes(cursor, &offset, sizeof(offset)); cursor = SerializePodVector(cursor, elemFuncIndices); cursor = SerializePodVector(cursor, elemCodeRangeIndices); return cursor; } const uint8_t* ElemSegment::deserialize(const uint8_t* cursor) { (cursor = ReadBytes(cursor, &tableIndex, sizeof(tableIndex))) && (cursor = ReadBytes(cursor, &offset, sizeof(offset))) && (cursor = DeserializePodVector(cursor, &elemFuncIndices)) && (cursor = DeserializePodVector(cursor, &elemCodeRangeIndices)); return cursor; } size_t ElemSegment::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { return elemFuncIndices.sizeOfExcludingThis(mallocSizeOf) + elemCodeRangeIndices.sizeOfExcludingThis(mallocSizeOf); } /* virtual */ void Module::serializedSize(size_t* maybeBytecodeSize, size_t* maybeCompiledSize) const { if (maybeBytecodeSize) *maybeBytecodeSize = bytecode_->bytes.length(); if (maybeCompiledSize) { *maybeCompiledSize = assumptions_.serializedSize() + SerializedPodVectorSize(code_) + linkData_.serializedSize() + SerializedVectorSize(imports_) + SerializedVectorSize(exports_) + SerializedPodVectorSize(dataSegments_) + SerializedVectorSize(elemSegments_) + metadata_->serializedSize(); } } /* virtual */ void Module::serialize(uint8_t* maybeBytecodeBegin, size_t maybeBytecodeSize, uint8_t* maybeCompiledBegin, size_t maybeCompiledSize) const { MOZ_ASSERT(!!maybeBytecodeBegin == !!maybeBytecodeSize); MOZ_ASSERT(!!maybeCompiledBegin == !!maybeCompiledSize); if (maybeBytecodeBegin) { // Bytecode deserialization is not guarded by Assumptions and thus must not // change incompatibly between builds. Thus, for simplicity, the format // of the bytecode file is simply a .wasm file (thus, backwards // compatibility is ensured by backwards compatibility of the wasm // binary format). const Bytes& bytes = bytecode_->bytes; uint8_t* bytecodeEnd = WriteBytes(maybeBytecodeBegin, bytes.begin(), bytes.length()); MOZ_RELEASE_ASSERT(bytecodeEnd == maybeBytecodeBegin + maybeBytecodeSize); } if (maybeCompiledBegin) { // Assumption must be serialized at the beginning of the compiled bytes so // that compiledAssumptionsMatch can detect a build-id mismatch before any // other decoding occurs. uint8_t* cursor = maybeCompiledBegin; cursor = assumptions_.serialize(cursor); cursor = SerializePodVector(cursor, code_); cursor = linkData_.serialize(cursor); cursor = SerializeVector(cursor, imports_); cursor = SerializeVector(cursor, exports_); cursor = SerializePodVector(cursor, dataSegments_); cursor = SerializeVector(cursor, elemSegments_); cursor = metadata_->serialize(cursor); MOZ_RELEASE_ASSERT(cursor == maybeCompiledBegin + maybeCompiledSize); } } /* static */ bool Module::assumptionsMatch(const Assumptions& current, const uint8_t* compiledBegin, size_t compiledSize) { Assumptions cached; if (!cached.deserialize(compiledBegin, compiledSize)) return false; return current == cached; } /* static */ SharedModule Module::deserialize(const uint8_t* bytecodeBegin, size_t bytecodeSize, const uint8_t* compiledBegin, size_t compiledSize, Metadata* maybeMetadata) { MutableBytes bytecode = js_new(); if (!bytecode || !bytecode->bytes.initLengthUninitialized(bytecodeSize)) return nullptr; memcpy(bytecode->bytes.begin(), bytecodeBegin, bytecodeSize); Assumptions assumptions; const uint8_t* cursor = assumptions.deserialize(compiledBegin, compiledSize); if (!cursor) return nullptr; Bytes code; cursor = DeserializePodVector(cursor, &code); if (!cursor) return nullptr; LinkData linkData; 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 = DeserializePodVector(cursor, &dataSegments); if (!cursor) return nullptr; ElemSegmentVector elemSegments; cursor = DeserializeVector(cursor, &elemSegments); if (!cursor) return nullptr; MutableMetadata metadata; if (maybeMetadata) { metadata = maybeMetadata; } else { metadata = js_new(); if (!metadata) return nullptr; } cursor = metadata->deserialize(cursor); if (!cursor) return nullptr; MOZ_RELEASE_ASSERT(cursor == compiledBegin + compiledSize); MOZ_RELEASE_ASSERT(!!maybeMetadata == metadata->isAsmJS()); return js_new(Move(assumptions), Move(code), Move(linkData), Move(imports), Move(exports), Move(dataSegments), Move(elemSegments), *metadata, *bytecode); } /* virtual */ JSObject* Module::createObject(JSContext* cx) { 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); } struct MemUnmap { uint32_t size; MemUnmap() : size(0) {} explicit MemUnmap(uint32_t size) : size(size) {} void operator()(uint8_t* p) { MOZ_ASSERT(size); PR_MemUnmap(p, size); } }; typedef UniquePtr UniqueMapping; static UniqueMapping MapFile(PRFileDesc* file, PRFileInfo* info) { if (PR_GetOpenFileInfo(file, info) != PR_SUCCESS) return nullptr; PRFileMap* map = PR_CreateFileMap(file, info->size, PR_PROT_READONLY); if (!map) return nullptr; // PRFileMap objects do not need to be kept alive after the memory has been // mapped, so unconditionally close the PRFileMap, regardless of whether // PR_MemMap succeeds. uint8_t* memory = (uint8_t*)PR_MemMap(map, 0, info->size); PR_CloseFileMap(map); return UniqueMapping(memory, MemUnmap(info->size)); } bool wasm::CompiledModuleAssumptionsMatch(PRFileDesc* compiled, JS::BuildIdCharVector&& buildId) { PRFileInfo info; UniqueMapping mapping = MapFile(compiled, &info); if (!mapping) return false; Assumptions assumptions(Move(buildId)); return Module::assumptionsMatch(assumptions, mapping.get(), info.size); } SharedModule wasm::DeserializeModule(PRFileDesc* bytecodeFile, PRFileDesc* maybeCompiledFile, JS::BuildIdCharVector&& buildId, UniqueChars filename, unsigned line, unsigned column) { PRFileInfo bytecodeInfo; UniqueMapping bytecodeMapping = MapFile(bytecodeFile, &bytecodeInfo); if (!bytecodeMapping) return nullptr; if (PRFileDesc* compiledFile = maybeCompiledFile) { PRFileInfo compiledInfo; UniqueMapping compiledMapping = MapFile(compiledFile, &compiledInfo); if (!compiledMapping) return nullptr; return Module::deserialize(bytecodeMapping.get(), bytecodeInfo.size, compiledMapping.get(), compiledInfo.size); } // Since the compiled file's assumptions don't match, we must recompile from // bytecode. The bytecode file format is simply that of a .wasm (see // Module::serialize). MutableBytes bytecode = js_new(); if (!bytecode || !bytecode->bytes.initLengthUninitialized(bytecodeInfo.size)) return nullptr; memcpy(bytecode->bytes.begin(), bytecodeMapping.get(), bytecodeInfo.size); ScriptedCaller scriptedCaller; scriptedCaller.filename = Move(filename); scriptedCaller.line = line; scriptedCaller.column = column; CompileArgs args(Assumptions(Move(buildId)), Move(scriptedCaller)); UniqueChars error; return Compile(*bytecode, Move(args), &error); } /* virtual */ void Module::addSizeOfMisc(MallocSizeOf mallocSizeOf, Metadata::SeenSet* seenMetadata, ShareableBytes::SeenSet* seenBytes, size_t* code, size_t* data) const { *data += mallocSizeOf(this) + assumptions_.sizeOfExcludingThis(mallocSizeOf) + code_.sizeOfExcludingThis(mallocSizeOf) + linkData_.sizeOfExcludingThis(mallocSizeOf) + SizeOfVectorExcludingThis(imports_, mallocSizeOf) + SizeOfVectorExcludingThis(exports_, mallocSizeOf) + dataSegments_.sizeOfExcludingThis(mallocSizeOf) + SizeOfVectorExcludingThis(elemSegments_, mallocSizeOf) + metadata_->sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenMetadata) + bytecode_->sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenBytes); } // 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, MutableHandleValue vp) { RootedPlainObject result(cx, NewBuiltinClassInstance(cx)); if (!result) return false; RootedObject code(cx, JS_NewUint8Array(cx, code_.length())); if (!code) return false; memcpy(code->as().viewDataUnshared(), code_.begin(), code_.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_->codeRanges) { RootedObject segment(cx, NewObjectWithGivenProto(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.funcNonProfilingEntry()); if (!JS_DefineProperty(cx, segment, "funcBodyBegin", value, JSPROP_ENUMERATE)) return false; value.setNumber((uint32_t)p.funcProfilingEpilogue()); 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 EvaluateInitExpr(const ValVector& globalImports, InitExpr initExpr) { switch (initExpr.kind()) { case InitExpr::Kind::Constant: return initExpr.val().i32(); case InitExpr::Kind::GetGlobal: return globalImports[initExpr.globalIndex()].i32(); } MOZ_CRASH("bad initializer expression"); } bool Module::initSegments(JSContext* cx, HandleWasmInstanceObject instanceObj, Handle funcImports, HandleWasmMemoryObject memoryObj, const ValVector& globalImports) const { Instance& instance = instanceObj->instance(); const SharedTableVector& tables = instance.tables(); // Perform all error checks up front so that this function does not perform // partial initialization if an error is reported. for (const ElemSegment& seg : elemSegments_) { uint32_t numElems = seg.elemCodeRangeIndices.length(); if (!numElems) continue; uint32_t tableLength = tables[seg.tableIndex]->length(); uint32_t offset = EvaluateInitExpr(globalImports, seg.offset); if (offset > tableLength || tableLength - offset < numElems) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_FIT, "elem", "table"); return false; } } if (memoryObj) { for (const DataSegment& seg : dataSegments_) { if (!seg.length) continue; uint32_t memoryLength = memoryObj->buffer().byteLength(); uint32_t offset = EvaluateInitExpr(globalImports, seg.offset); if (offset > memoryLength || memoryLength - offset < seg.length) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_FIT, "data", "memory"); return false; } } } else { MOZ_ASSERT(dataSegments_.empty()); } // Now that initialization can't fail partway through, write data/elem // segments into memories/tables. for (const ElemSegment& seg : elemSegments_) { Table& table = *tables[seg.tableIndex]; uint32_t offset = EvaluateInitExpr(globalImports, seg.offset); bool profilingEnabled = instance.code().profilingEnabled(); const CodeRangeVector& codeRanges = metadata().codeRanges; uint8_t* codeBase = instance.codeBase(); for (uint32_t i = 0; i < seg.elemCodeRangeIndices.length(); i++) { uint32_t funcIndex = seg.elemFuncIndices[i]; if (funcIndex < funcImports.length() && IsExportedWasmFunction(funcImports[funcIndex])) { MOZ_ASSERT(!metadata().isAsmJS()); MOZ_ASSERT(!table.isTypedFunction()); HandleFunction f = funcImports[funcIndex]; WasmInstanceObject* exportInstanceObj = ExportedFunctionToInstanceObject(f); const CodeRange& cr = exportInstanceObj->getExportedFunctionCodeRange(f); Instance& exportInstance = exportInstanceObj->instance(); table.set(offset + i, exportInstance.codeBase() + cr.funcTableEntry(), exportInstance); } else { const CodeRange& cr = codeRanges[seg.elemCodeRangeIndices[i]]; uint32_t entryOffset = table.isTypedFunction() ? profilingEnabled ? cr.funcProfilingEntry() : cr.funcNonProfilingEntry() : cr.funcTableEntry(); table.set(offset + i, codeBase + entryOffset, instance); } } } if (memoryObj) { uint8_t* memoryBase = memoryObj->buffer().dataPointerEither().unwrap(/* memcpy */); for (const DataSegment& seg : dataSegments_) { MOZ_ASSERT(seg.bytecodeOffset <= bytecode_->length()); MOZ_ASSERT(seg.length <= bytecode_->length() - seg.bytecodeOffset); uint32_t offset = EvaluateInitExpr(globalImports, seg.offset); memcpy(memoryBase + offset, bytecode_->begin() + seg.bytecodeOffset, seg.length); } } return true; } bool Module::instantiateFunctions(JSContext* cx, Handle funcImports) const { MOZ_ASSERT(funcImports.length() == metadata_->funcImports.length()); if (metadata().isAsmJS()) return true; for (size_t i = 0; i < metadata_->funcImports.length(); i++) { HandleFunction f = funcImports[i]; if (!IsExportedFunction(f) || ExportedFunctionToInstance(f).isAsmJS()) continue; uint32_t funcIndex = ExportedFunctionToFuncIndex(f); Instance& instance = ExportedFunctionToInstance(f); const FuncExport& funcExport = instance.metadata().lookupFuncExport(funcIndex); if (funcExport.sig() != metadata_->funcImports[i].sig()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMPORT_SIG); return false; } } return true; } static bool CheckLimits(JSContext* cx, uint32_t declaredMin, Maybe declaredMax, uint32_t actualLength, Maybe 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_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, kind); return false; } if ((actualMax && declaredMax && *actualMax > *declaredMax) || (!actualMax && declaredMax)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_MAX, kind); 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(dataSegments_.empty()); return true; } uint32_t declaredMin = metadata_->minMemoryLength; Maybe declaredMax = metadata_->maxMemoryLength; if (memory) { ArrayBufferObjectMaybeShared& buffer = memory->buffer(); MOZ_ASSERT_IF(metadata_->isAsmJS(), buffer.isPreparedForAsmJS()); MOZ_ASSERT_IF(!metadata_->isAsmJS(), buffer.as().isWasm()); if (!CheckLimits(cx, declaredMin, declaredMax, buffer.byteLength(), buffer.wasmMaxSize(), metadata_->isAsmJS(), "Memory")) { return false; } } else { MOZ_ASSERT(!metadata_->isAsmJS()); MOZ_ASSERT(metadata_->memoryUsage == MemoryUsage::Unshared); RootedArrayBufferObjectMaybeShared buffer(cx, ArrayBufferObject::createForWasm(cx, declaredMin, declaredMax)); if (!buffer) return false; RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmMemory).toObject()); memory.set(WasmMemoryObject::create(cx, buffer, proto)); if (!memory) return false; } return true; } bool Module::instantiateTable(JSContext* cx, MutableHandleWasmTableObject tableObj, SharedTableVector* tables) const { if (tableObj) { MOZ_ASSERT(!metadata_->isAsmJS()); MOZ_ASSERT(metadata_->tables.length() == 1); const TableDesc& td = metadata_->tables[0]; MOZ_ASSERT(td.external); Table& table = tableObj->table(); if (!CheckLimits(cx, td.limits.initial, td.limits.maximum, table.length(), table.maximum(), metadata_->isAsmJS(), "Table")) { return false; } if (!tables->append(&table)) { ReportOutOfMemory(cx); return false; } } else { for (const TableDesc& td : metadata_->tables) { SharedTable table; if (td.external) { MOZ_ASSERT(!tableObj); MOZ_ASSERT(td.kind == TableKind::AnyFunction); tableObj.set(WasmTableObject::create(cx, td.limits)); if (!tableObj) return false; table = &tableObj->table(); } else { table = Table::create(cx, td, /* HandleWasmTableObject = */ nullptr); if (!table) return false; } if (!tables->emplaceBack(table)) { ReportOutOfMemory(cx); return false; } } } return true; } static bool GetFunctionExport(JSContext* cx, HandleWasmInstanceObject instanceObj, Handle funcImports, const Export& exp, MutableHandleValue val) { if (exp.funcIndex() < funcImports.length() && IsExportedWasmFunction(funcImports[exp.funcIndex()])) { val.setObject(*funcImports[exp.funcIndex()]); return true; } RootedFunction fun(cx); if (!instanceObj->getExportedFunction(cx, instanceObj, exp.funcIndex(), &fun)) return false; val.setObject(*fun); return true; } static bool GetGlobalExport(JSContext* cx, const GlobalDescVector& globals, uint32_t globalIndex, const ValVector& globalImports, MutableHandleValue jsval) { const GlobalDesc& global = globals[globalIndex]; // Imports are located upfront in the globals array. Val val; switch (global.kind()) { case GlobalKind::Import: val = globalImports[globalIndex]; break; case GlobalKind::Variable: MOZ_CRASH("mutable variables can't be exported"); case GlobalKind::Constant: val = global.constantValue(); break; } switch (global.type()) { case ValType::I32: { jsval.set(Int32Value(val.i32())); return true; } case ValType::I64: { MOZ_ASSERT(JitOptions.wasmTestMode, "no int64 in asm.js/wasm"); RootedObject obj(cx, CreateI64Object(cx, val.i64())); if (!obj) return false; jsval.set(ObjectValue(*obj)); return true; } case ValType::F32: { float f = val.f32().fp(); if (JitOptions.wasmTestMode && IsNaN(f)) { uint32_t bits = val.f32().bits(); RootedObject obj(cx, CreateCustomNaNObject(cx, (float*)&bits)); if (!obj) return false; jsval.set(ObjectValue(*obj)); return true; } jsval.set(DoubleValue(double(f))); return true; } case ValType::F64: { double d = val.f64().fp(); if (JitOptions.wasmTestMode && IsNaN(d)) { uint64_t bits = val.f64().bits(); RootedObject obj(cx, CreateCustomNaNObject(cx, (double*)&bits)); if (!obj) return false; jsval.set(ObjectValue(*obj)); return true; } jsval.set(DoubleValue(d)); return true; } default: { break; } } MOZ_CRASH("unexpected type when creating global exports"); } static bool CreateExportObject(JSContext* cx, HandleWasmInstanceObject instanceObj, Handle funcImports, HandleWasmTableObject tableObj, HandleWasmMemoryObject memoryObj, const ValVector& globalImports, const ExportVector& exports, MutableHandleObject exportObj) { const Instance& instance = instanceObj->instance(); const Metadata& metadata = instance.metadata(); if (metadata.isAsmJS() && exports.length() == 1 && strlen(exports[0].fieldName()) == 0) { RootedValue val(cx); if (!GetFunctionExport(cx, instanceObj, funcImports, exports[0], &val)) return false; exportObj.set(&val.toObject()); return true; } if (metadata.isAsmJS()) exportObj.set(NewBuiltinClassInstance(cx)); else exportObj.set(NewObjectWithGivenProto(cx, nullptr)); 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: if (!GetFunctionExport(cx, instanceObj, funcImports, exp, &val)) return false; break; case DefinitionKind::Table: val = ObjectValue(*tableObj); break; case DefinitionKind::Memory: val = ObjectValue(*memoryObj); break; case DefinitionKind::Global: if (!GetGlobalExport(cx, metadata.globals, exp.globalIndex(), globalImports, &val)) return false; break; } if (!JS_DefinePropertyById(cx, exportObj, id, val, JSPROP_ENUMERATE)) return false; } if (!metadata.isAsmJS()) { if (!JS_FreezeObject(cx, exportObj)) return false; } return true; } bool Module::instantiate(JSContext* cx, Handle funcImports, HandleWasmTableObject tableImport, HandleWasmMemoryObject memoryImport, const ValVector& globalImports, HandleObject instanceProto, MutableHandleWasmInstanceObject instance) const { if (!instantiateFunctions(cx, funcImports)) return false; RootedWasmMemoryObject memory(cx, memoryImport); if (!instantiateMemory(cx, &memory)) return false; RootedWasmTableObject table(cx, tableImport); SharedTableVector tables; if (!instantiateTable(cx, &table, &tables)) return false; // To support viewing the source of an instance (Instance::createText), the // instance must hold onto a ref of the bytecode (keeping it alive). This // wastes memory for most users, so we try to only save the source when a // developer actually cares: when the compartment is debuggable (which is // true when the web console is open) or a names section is present (since // this going to be stripped for non-developer builds). const ShareableBytes* maybeBytecode = nullptr; if (cx->compartment()->isDebuggee() || !metadata_->funcNames.empty()) maybeBytecode = bytecode_.get(); auto codeSegment = CodeSegment::create(cx, code_, linkData_, *metadata_, memory); if (!codeSegment) { ReportOutOfMemory(cx); return false; } auto code = cx->make_unique(Move(codeSegment), *metadata_, maybeBytecode); if (!code) { ReportOutOfMemory(cx); return false; } instance.set(WasmInstanceObject::create(cx, Move(code), memory, Move(tables), funcImports, globalImports, instanceProto)); if (!instance) return false; RootedObject exportObj(cx); if (!CreateExportObject(cx, instance, funcImports, table, memory, globalImports, exports_, &exportObj)) return false; JSAtom* atom = Atomize(cx, InstanceExportField, strlen(InstanceExportField)); if (!atom) return false; RootedId id(cx, AtomToId(atom)); RootedValue val(cx, ObjectValue(*exportObj)); if (!JS_DefinePropertyById(cx, instance, id, val, JSPROP_ENUMERATE)) return false; // Register the instance with the JSCompartment so that it can find out // about global events like profiling being enabled in the compartment. // Registration does not require a fully-initialized instance and must // precede initSegments as the final pre-requisite for a live instance. if (!cx->compartment()->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, funcImports, memory, globalImports)) 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; } return true; }