1070 lines
34 KiB
C++
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;
|
|
}
|