diff options
Diffstat (limited to 'js/src/wasm/WasmModule.cpp')
-rw-r--r-- | js/src/wasm/WasmModule.cpp | 1069 |
1 files changed, 1069 insertions, 0 deletions
diff --git a/js/src/wasm/WasmModule.cpp b/js/src/wasm/WasmModule.cpp new file mode 100644 index 000000000..be7ddba8f --- /dev/null +++ b/js/src/wasm/WasmModule.cpp @@ -0,0 +1,1069 @@ +/* -*- 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<DefinitionKind>(cursor, kind); + return cursor; +} + +const uint8_t* +Import::deserialize(const uint8_t* cursor) +{ + (cursor = module.deserialize(cursor)) && + (cursor = field.deserialize(cursor)) && + (cursor = ReadScalar<DefinitionKind>(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<ShareableBytes>(); + 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<Metadata>(); + 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<Module>(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<uint8_t, MemUnmap> 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<ShareableBytes>(); + 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<PlainObject>(cx)); + if (!result) + return false; + + RootedObject code(cx, JS_NewUint8Array(cx, code_.length())); + if (!code) + return false; + + memcpy(code->as<TypedArrayObject>().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<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.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<FunctionVector> 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<FunctionVector> 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<uint32_t> declaredMax, uint32_t actualLength, + Maybe<uint32_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_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<uint32_t> declaredMax = metadata_->maxMemoryLength; + + if (memory) { + ArrayBufferObjectMaybeShared& buffer = memory->buffer(); + MOZ_ASSERT_IF(metadata_->isAsmJS(), buffer.isPreparedForAsmJS()); + MOZ_ASSERT_IF(!metadata_->isAsmJS(), buffer.as<ArrayBufferObject>().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<FunctionVector> 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<FunctionVector> 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<PlainObject>(cx)); + else + exportObj.set(NewObjectWithGivenProto<PlainObject>(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<FunctionVector> 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) + return false; + + auto code = cx->make_unique<Code>(Move(codeSegment), *metadata_, maybeBytecode); + if (!code) + 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; + } + + uint32_t mode = uint32_t(metadata().isAsmJS() ? Telemetry::ASMJS : Telemetry::WASM); + cx->runtime()->addTelemetry(JS_TELEMETRY_AOT_USAGE, mode); + + return true; +} |