Mypal/js/src/wasm/WasmModule.cpp
2021-02-04 16:48:36 +02:00

1070 lines
34 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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) {
ReportOutOfMemory(cx);
return false;
}
auto code = cx->make_unique<Code>(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;
}