#include "wasm/WasmModule.h"
#include <chrono>
#include <thread>
#include "builtin/TypedObject.h"
#include "jit/JitOptions.h"
#include "js/BuildId.h"
#include "threading/LockGuard.h"
#include "util/NSPR.h"
#include "wasm/WasmBaselineCompile.h"
#include "wasm/WasmCompile.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmJS.h"
#include "wasm/WasmSerialize.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/Debugger-inl.h"
#include "vm/JSAtom-inl.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
class Module::Tier2GeneratorTaskImpl : public Tier2GeneratorTask {
SharedCompileArgs compileArgs_;
SharedBytes bytecode_;
SharedModule module_;
Atomic<bool> cancelled_;
public:
Tier2GeneratorTaskImpl(const CompileArgs& compileArgs,
const ShareableBytes& bytecode, Module& module)
: compileArgs_(&compileArgs),
bytecode_(&bytecode),
module_(&module),
cancelled_(false) {}
~Tier2GeneratorTaskImpl() override {
module_->tier2Listener_ = nullptr;
module_->testingTier2Active_ = false;
}
void cancel() override { cancelled_ = true; }
void execute() override {
CompileTier2(*compileArgs_, bytecode_->bytes, *module_, &cancelled_);
}
};
Module::~Module() {
MOZ_ASSERT(!tier2Listener_);
MOZ_ASSERT(!testingTier2Active_);
}
void Module::startTier2(const CompileArgs& args, const ShareableBytes& bytecode,
JS::OptimizedEncodingListener* listener) {
MOZ_ASSERT(!testingTier2Active_);
auto task = MakeUnique<Tier2GeneratorTaskImpl>(args, bytecode, *this);
if (!task) {
return;
}
tier2Listener_ = listener;
testingTier2Active_ = true;
StartOffThreadWasmTier2Generator(std::move(task));
}
bool Module::finishTier2(const LinkData& linkData2,
UniqueCodeTier code2) const {
MOZ_ASSERT(code().bestTier() == Tier::Baseline &&
code2->tier() == Tier::Optimized);
if (!code().setTier2(std::move(code2), linkData2)) {
return false;
}
{
const MetadataTier& metadataTier1 = metadata(Tier::Baseline);
auto stubs1 = code().codeTier(Tier::Baseline).lazyStubs().lock();
auto stubs2 = code().codeTier(Tier::Optimized).lazyStubs().lock();
MOZ_ASSERT(stubs2->empty());
Uint32Vector funcExportIndices;
for (size_t i = 0; i < metadataTier1.funcExports.length(); i++) {
const FuncExport& fe = metadataTier1.funcExports[i];
if (fe.hasEagerStubs()) {
continue;
}
if (!stubs1->hasStub(fe.funcIndex())) {
continue;
}
if (!funcExportIndices.emplaceBack(i)) {
return false;
}
}
const CodeTier& tier2 = code().codeTier(Tier::Optimized);
Maybe<size_t> stub2Index;
if (!stubs2->createTier2(funcExportIndices, tier2, &stub2Index)) {
return false;
}
MOZ_ASSERT(!code().hasTier2());
code().commitTier2();
stubs2->setJitEntries(stub2Index, code());
}
uint8_t* base = code().segment(Tier::Optimized).base();
for (const CodeRange& cr : metadata(Tier::Optimized).codeRanges) {
if (cr.isFunction()) {
code().setTieringEntry(cr.funcIndex(), base + cr.funcTierEntry());
} else if (cr.isJitEntry()) {
code().setJitEntry(cr.funcIndex(), base + cr.begin());
}
}
if (tier2Listener_) {
serialize(linkData2, *tier2Listener_);
tier2Listener_ = nullptr;
}
testingTier2Active_ = false;
return true;
}
void Module::testingBlockOnTier2Complete() const {
while (testingTier2Active_) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
size_t Module::serializedSize(const LinkData& linkData) const {
JS::BuildIdCharVector buildId;
{
AutoEnterOOMUnsafeRegion oom;
if (!GetOptimizedEncodingBuildId(&buildId)) {
oom.crash("getting build id");
}
}
return SerializedPodVectorSize(buildId) + linkData.serializedSize() +
SerializedVectorSize(imports_) + SerializedVectorSize(exports_) +
SerializedVectorSize(dataSegments_) +
SerializedVectorSize(elemSegments_) +
SerializedVectorSize(customSections_) + code_->serializedSize();
}
void Module::serialize(const LinkData& linkData, uint8_t* begin,
size_t size) const {
MOZ_RELEASE_ASSERT(!metadata().debugEnabled);
MOZ_RELEASE_ASSERT(code_->hasTier(Tier::Serialized));
JS::BuildIdCharVector buildId;
{
AutoEnterOOMUnsafeRegion oom;
if (!GetOptimizedEncodingBuildId(&buildId)) {
oom.crash("getting build id");
}
}
uint8_t* cursor = begin;
cursor = SerializePodVector(cursor, buildId);
cursor = linkData.serialize(cursor);
cursor = SerializeVector(cursor, imports_);
cursor = SerializeVector(cursor, exports_);
cursor = SerializeVector(cursor, dataSegments_);
cursor = SerializeVector(cursor, elemSegments_);
cursor = SerializeVector(cursor, customSections_);
cursor = code_->serialize(cursor, linkData);
MOZ_RELEASE_ASSERT(cursor == begin + size);
}
MutableModule Module::deserialize(const uint8_t* begin, size_t size,
Metadata* maybeMetadata) {
MutableMetadata metadata(maybeMetadata);
if (!metadata) {
metadata = js_new<Metadata>();
if (!metadata) {
return nullptr;
}
}
const uint8_t* cursor = begin;
JS::BuildIdCharVector currentBuildId;
if (!GetOptimizedEncodingBuildId(¤tBuildId)) {
return nullptr;
}
JS::BuildIdCharVector deserializedBuildId;
cursor = DeserializePodVector(cursor, &deserializedBuildId);
if (!cursor) {
return nullptr;
}
MOZ_RELEASE_ASSERT(EqualContainers(currentBuildId, deserializedBuildId));
LinkData linkData(Tier::Serialized);
cursor = linkData.deserialize(cursor);
if (!cursor) {
return nullptr;
}
ImportVector imports;
cursor = DeserializeVector(cursor, &imports);
if (!cursor) {
return nullptr;
}
ExportVector exports;
cursor = DeserializeVector(cursor, &exports);
if (!cursor) {
return nullptr;
}
DataSegmentVector dataSegments;
cursor = DeserializeVector(cursor, &dataSegments);
if (!cursor) {
return nullptr;
}
ElemSegmentVector elemSegments;
cursor = DeserializeVector(cursor, &elemSegments);
if (!cursor) {
return nullptr;
}
CustomSectionVector customSections;
cursor = DeserializeVector(cursor, &customSections);
if (!cursor) {
return nullptr;
}
if (metadata->nameCustomSectionIndex) {
metadata->namePayload =
customSections[*metadata->nameCustomSectionIndex].payload;
} else {
MOZ_RELEASE_ASSERT(!metadata->moduleName);
MOZ_RELEASE_ASSERT(metadata->funcNames.empty());
}
SharedCode code;
cursor = Code::deserialize(cursor, linkData, *metadata, &code);
if (!cursor) {
return nullptr;
}
MOZ_RELEASE_ASSERT(cursor == begin + size);
MOZ_RELEASE_ASSERT(!!maybeMetadata == code->metadata().isAsmJS());
return js_new<Module>(*code, std::move(imports), std::move(exports),
std::move(dataSegments), std::move(elemSegments),
std::move(customSections));
}
void Module::serialize(const LinkData& linkData,
JS::OptimizedEncodingListener& listener) const {
Vector<uint8_t, 0, SystemAllocPolicy> bytes;
if (!bytes.resize(serializedSize(linkData))) {
return;
}
serialize(linkData, bytes.begin(), bytes.length());
listener.storeOptimizedEncoding(bytes.begin(), bytes.length());
}
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);
}
bool wasm::GetOptimizedEncodingBuildId(JS::BuildIdCharVector* buildId) {
if (!GetBuildId || !GetBuildId(buildId)) {
return false;
}
uint32_t cpu = ObservedCPUFeatures();
if (!buildId->reserve(buildId->length() + 10 )) {
return false;
}
buildId->infallibleAppend('(');
while (cpu) {
buildId->infallibleAppend('0' + (cpu & 0xf));
cpu >>= 4;
}
buildId->infallibleAppend(')');
return true;
}
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;
}
uint8_t* memory = (uint8_t*)PR_MemMap(map, 0, info->size);
PR_CloseFileMap(map);
return UniqueMapping(memory, MemUnmap(info->size));
}
RefPtr<JS::WasmModule> wasm::DeserializeModule(PRFileDesc* bytecodeFile,
UniqueChars filename,
unsigned line) {
if (!BaselineCanCompile() && !IonCanCompile()) {
return nullptr;
}
PRFileInfo bytecodeInfo;
UniqueMapping bytecodeMapping = MapFile(bytecodeFile, &bytecodeInfo);
if (!bytecodeMapping) {
return nullptr;
}
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 = std::move(filename);
scriptedCaller.line = line;
MutableCompileArgs args = js_new<CompileArgs>(std::move(scriptedCaller));
if (!args) {
return nullptr;
}
args->ionEnabled = IonCanCompile();
args->baselineEnabled = BaselineCanCompile();
args->sharedMemoryEnabled = true;
UniqueChars error;
UniqueCharsVector warnings;
SharedModule module = CompileBuffer(*args, *bytecode, &error, &warnings);
if (!module) {
return nullptr;
}
return RefPtr<JS::WasmModule>(const_cast<Module*>(module.get()));
}
void Module::addSizeOfMisc(MallocSizeOf mallocSizeOf,
Metadata::SeenSet* seenMetadata,
ShareableBytes::SeenSet* seenBytes,
Code::SeenSet* seenCode, size_t* code,
size_t* data) const {
code_->addSizeOfMiscIfNotSeen(mallocSizeOf, seenMetadata, seenCode, code,
data);
*data += mallocSizeOf(this) +
SizeOfVectorExcludingThis(imports_, mallocSizeOf) +
SizeOfVectorExcludingThis(exports_, mallocSizeOf) +
SizeOfVectorExcludingThis(dataSegments_, mallocSizeOf) +
SizeOfVectorExcludingThis(elemSegments_, mallocSizeOf) +
SizeOfVectorExcludingThis(customSections_, mallocSizeOf);
if (debugUnlinkedCode_) {
*data += debugUnlinkedCode_->sizeOfExcludingThis(mallocSizeOf);
}
}
bool Module::extractCode(JSContext* cx, Tier tier,
MutableHandleValue vp) const {
RootedPlainObject result(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!result) {
return false;
}
testingBlockOnTier2Complete();
if (!code_->hasTier(tier)) {
vp.setNull();
return true;
}
const ModuleSegment& moduleSegment = code_->segment(tier);
RootedObject code(cx, JS_NewUint8Array(cx, moduleSegment.length()));
if (!code) {
return false;
}
memcpy(code->as<TypedArrayObject>().dataPointerUnshared(),
moduleSegment.base(), moduleSegment.length());
RootedValue value(cx, ObjectValue(*code));
if (!JS_DefineProperty(cx, result, "code", value, JSPROP_ENUMERATE)) {
return false;
}
RootedObject segments(cx, NewDenseEmptyArray(cx));
if (!segments) {
return false;
}
for (const CodeRange& p : metadata(tier).codeRanges) {
RootedObject segment(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
if (!segment) {
return false;
}
value.setNumber((uint32_t)p.begin());
if (!JS_DefineProperty(cx, segment, "begin", value, JSPROP_ENUMERATE)) {
return false;
}
value.setNumber((uint32_t)p.end());
if (!JS_DefineProperty(cx, segment, "end", value, JSPROP_ENUMERATE)) {
return false;
}
value.setNumber((uint32_t)p.kind());
if (!JS_DefineProperty(cx, segment, "kind", value, JSPROP_ENUMERATE)) {
return false;
}
if (p.isFunction()) {
value.setNumber((uint32_t)p.funcIndex());
if (!JS_DefineProperty(cx, segment, "funcIndex", value,
JSPROP_ENUMERATE)) {
return false;
}
value.setNumber((uint32_t)p.funcNormalEntry());
if (!JS_DefineProperty(cx, segment, "funcBodyBegin", value,
JSPROP_ENUMERATE)) {
return false;
}
value.setNumber((uint32_t)p.end());
if (!JS_DefineProperty(cx, segment, "funcBodyEnd", value,
JSPROP_ENUMERATE)) {
return false;
}
}
if (!NewbornArrayPush(cx, segments, ObjectValue(*segment))) {
return false;
}
}
value.setObject(*segments);
if (!JS_DefineProperty(cx, result, "segments", value, JSPROP_ENUMERATE)) {
return false;
}
vp.setObject(*result);
return true;
}
static uint32_t EvaluateInitExpr(HandleValVector globalImportValues,
InitExpr initExpr) {
switch (initExpr.kind()) {
case InitExpr::Kind::Constant:
return initExpr.val().i32();
case InitExpr::Kind::GetGlobal:
return globalImportValues[initExpr.globalIndex()].get().i32();
}
MOZ_CRASH("bad initializer expression");
}
bool Module::initSegments(JSContext* cx, HandleWasmInstanceObject instanceObj,
Handle<FunctionVector> funcImports,
HandleWasmMemoryObject memoryObj,
HandleValVector globalImportValues) const {
MOZ_ASSERT_IF(!memoryObj, dataSegments_.empty());
Instance& instance = instanceObj->instance();
const SharedTableVector& tables = instance.tables();
#ifndef ENABLE_WASM_BULKMEM_OPS
for (const ElemSegment* seg : elemSegments_) {
if (!seg->active()) {
continue;
}
uint32_t tableLength = tables[seg->tableIndex]->length();
uint32_t offset = EvaluateInitExpr(globalImportValues, seg->offset());
if (offset > tableLength || tableLength - offset < seg->length()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_FIT,
"elem", "table");
return false;
}
}
if (memoryObj) {
uint32_t memoryLength = memoryObj->volatileMemoryLength();
for (const DataSegment* seg : dataSegments_) {
if (!seg->active()) {
continue;
}
uint32_t offset = EvaluateInitExpr(globalImportValues, seg->offset());
if (offset > memoryLength ||
memoryLength - offset < seg->bytes.length()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_FIT, "data", "memory");
return false;
}
}
}
#endif
for (const ElemSegment* seg : elemSegments_) {
if (seg->active()) {
uint32_t offset = EvaluateInitExpr(globalImportValues, seg->offset());
uint32_t count = seg->length();
#ifdef ENABLE_WASM_BULKMEM_OPS
uint32_t tableLength = tables[seg->tableIndex]->length();
bool fail = false;
if (offset > tableLength) {
fail = true;
count = 0;
} else if (tableLength - offset < count) {
fail = true;
count = tableLength - offset;
}
#endif
if (count) {
instance.initElems(seg->tableIndex, *seg, offset, 0, count);
}
#ifdef ENABLE_WASM_BULKMEM_OPS
if (fail) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_FIT, "elem", "table");
return false;
}
#endif
}
}
if (memoryObj) {
#ifdef ENABLE_WASM_BULKMEM_OPS
uint32_t memoryLength = memoryObj->volatileMemoryLength();
#endif
uint8_t* memoryBase =
memoryObj->buffer().dataPointerEither().unwrap();
for (const DataSegment* seg : dataSegments_) {
if (!seg->active()) {
continue;
}
uint32_t offset = EvaluateInitExpr(globalImportValues, seg->offset());
uint32_t count = seg->bytes.length();
#ifdef ENABLE_WASM_BULKMEM_OPS
bool fail = false;
if (offset > memoryLength) {
fail = true;
count = 0;
} else if (memoryLength - offset < count) {
fail = true;
count = memoryLength - offset;
}
#endif
if (count) {
memcpy(memoryBase + offset, seg->bytes.begin(), count);
}
#ifdef ENABLE_WASM_BULKMEM_OPS
if (fail) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_FIT, "data", "memory");
return false;
}
#endif
}
}
return true;
}
static const Import& FindImportForFuncImport(const ImportVector& imports,
uint32_t funcImportIndex) {
for (const Import& import : imports) {
if (import.kind != DefinitionKind::Function) {
continue;
}
if (funcImportIndex == 0) {
return import;
}
funcImportIndex--;
}
MOZ_CRASH("ran out of imports");
}
bool Module::instantiateFunctions(JSContext* cx,
Handle<FunctionVector> funcImports) const {
#ifdef DEBUG
for (auto t : code().tiers()) {
MOZ_ASSERT(funcImports.length() == metadata(t).funcImports.length());
}
#endif
if (metadata().isAsmJS()) {
return true;
}
Tier tier = code().stableTier();
for (size_t i = 0; i < metadata(tier).funcImports.length(); i++) {
HandleFunction f = funcImports[i];
if (!IsExportedFunction(f) || ExportedFunctionToInstance(f).isAsmJS()) {
continue;
}
uint32_t funcIndex = ExportedFunctionToFuncIndex(f);
Instance& instance = ExportedFunctionToInstance(f);
Tier otherTier = instance.code().stableTier();
const FuncExport& funcExport =
instance.metadata(otherTier).lookupFuncExport(funcIndex);
if (funcExport.funcType() != metadata(tier).funcImports[i].funcType()) {
const Import& import = FindImportForFuncImport(imports_, i);
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_IMPORT_SIG, import.module.get(),
import.field.get());
return false;
}
}
return true;
}
static bool CheckLimits(JSContext* cx, uint32_t declaredMin,
const Maybe<uint32_t>& declaredMax,
uint32_t actualLength, const 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_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_IMP_SIZE, kind);
return false;
}
if ((actualMax && declaredMax && *actualMax > *declaredMax) ||
(!actualMax && declaredMax)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_IMP_MAX, kind);
return false;
}
return true;
}
static bool CheckSharing(JSContext* cx, bool declaredShared, bool isShared) {
if (isShared &&
!cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_NO_SHMEM_LINK);
return false;
}
if (declaredShared && !isShared) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_IMP_SHARED_REQD);
return false;
}
if (!declaredShared && isShared) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_IMP_SHARED_BANNED);
return false;
}
return true;
}
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;
bool declaredShared = metadata().memoryUsage == MemoryUsage::Shared;
if (memory) {
MOZ_ASSERT_IF(metadata().isAsmJS(), memory->buffer().isPreparedForAsmJS());
MOZ_ASSERT_IF(!metadata().isAsmJS(), memory->buffer().isWasm());
if (!CheckLimits(
cx, declaredMin, declaredMax, memory->volatileMemoryLength(),
memory->buffer().wasmMaxSize(), metadata().isAsmJS(), "Memory")) {
return false;
}
if (!CheckSharing(cx, declaredShared, memory->isShared())) {
return false;
}
} else {
MOZ_ASSERT(!metadata().isAsmJS());
RootedArrayBufferObjectMaybeShared buffer(cx);
Limits l(declaredMin, declaredMax,
declaredShared ? Shareable::True : Shareable::False);
if (!CreateWasmBuffer(cx, l, &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::instantiateImportedTable(JSContext* cx, const TableDesc& td,
Handle<WasmTableObject*> tableObj,
WasmTableObjectVector* tableObjs,
SharedTableVector* tables) const {
MOZ_ASSERT(tableObj);
MOZ_ASSERT(!metadata().isAsmJS());
Table& table = tableObj->table();
if (!CheckLimits(cx, td.limits.initial, td.limits.maximum, table.length(),
table.maximum(), metadata().isAsmJS(), "Table")) {
return false;
}
if (!tables->append(&table)) {
ReportOutOfMemory(cx);
return false;
}
if (!tableObjs->append(tableObj)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
bool Module::instantiateLocalTable(JSContext* cx, const TableDesc& td,
WasmTableObjectVector* tableObjs,
SharedTableVector* tables) const {
SharedTable table;
Rooted<WasmTableObject*> tableObj(cx);
if (td.importedOrExported) {
tableObj.set(WasmTableObject::create(cx, td.limits, td.kind));
if (!tableObj) {
return false;
}
table = &tableObj->table();
} else {
table = Table::create(cx, td, nullptr);
if (!table) {
return false;
}
}
if (!tableObjs->append(tableObj.get())) {
ReportOutOfMemory(cx);
return false;
}
if (!tables->emplaceBack(table)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
bool Module::instantiateTables(JSContext* cx,
WasmTableObjectVector& tableImports,
MutableHandle<WasmTableObjectVector> tableObjs,
SharedTableVector* tables) const {
uint32_t tableIndex = 0;
for (const TableDesc& td : metadata().tables) {
if (tableIndex < tableImports.length()) {
Rooted<WasmTableObject*> tableObj(cx, tableImports[tableIndex]);
if (!instantiateImportedTable(cx, td, tableObj, &tableObjs.get(),
tables)) {
return false;
}
} else {
if (!instantiateLocalTable(cx, td, &tableObjs.get(), tables)) {
return false;
}
}
tableIndex++;
}
return true;
}
static void ExtractGlobalValue(HandleValVector globalImportValues,
uint32_t globalIndex, const GlobalDesc& global,
MutableHandleVal result) {
switch (global.kind()) {
case GlobalKind::Import: {
result.set(Val(globalImportValues[globalIndex]));
return;
}
case GlobalKind::Variable: {
const InitExpr& init = global.initExpr();
switch (init.kind()) {
case InitExpr::Kind::Constant:
result.set(Val(init.val()));
return;
case InitExpr::Kind::GetGlobal:
result.set(Val(globalImportValues[init.globalIndex()]));
return;
}
break;
}
case GlobalKind::Constant: {
result.set(Val(global.constantValue()));
return;
}
}
MOZ_CRASH("Not a global value");
}
static bool EnsureGlobalObject(JSContext* cx,
HandleValVector globalImportValues,
size_t globalIndex, const GlobalDesc& global,
WasmGlobalObjectVector& globalObjs) {
if (globalIndex < globalObjs.length() && globalObjs[globalIndex]) {
return true;
}
RootedVal val(cx);
ExtractGlobalValue(globalImportValues, globalIndex, global, &val);
RootedWasmGlobalObject go(
cx, WasmGlobalObject::create(cx, val, global.isMutable()));
if (!go) {
return false;
}
if (globalObjs.length() <= globalIndex &&
!globalObjs.resize(globalIndex + 1)) {
ReportOutOfMemory(cx);
return false;
}
globalObjs[globalIndex] = go;
return true;
}
bool Module::instantiateGlobals(JSContext* cx,
HandleValVector globalImportValues,
WasmGlobalObjectVector& globalObjs) const {
const GlobalDescVector& globals = metadata().globals;
for (const Export& exp : exports_) {
if (exp.kind() != DefinitionKind::Global) {
continue;
}
unsigned globalIndex = exp.globalIndex();
const GlobalDesc& global = globals[globalIndex];
if (!EnsureGlobalObject(cx, globalImportValues, globalIndex, global,
globalObjs)) {
return false;
}
}
#ifdef DEBUG
size_t numGlobalImports = 0;
for (const Import& import : imports_) {
if (import.kind != DefinitionKind::Global) {
continue;
}
size_t globalIndex = numGlobalImports++;
const GlobalDesc& global = globals[globalIndex];
MOZ_ASSERT(global.importIndex() == globalIndex);
MOZ_ASSERT_IF(global.isIndirect(),
globalIndex < globalObjs.length() || globalObjs[globalIndex]);
}
MOZ_ASSERT_IF(!metadata().isAsmJS(),
numGlobalImports == globals.length() ||
!globals[numGlobalImports].isImport());
#endif
return true;
}
SharedCode Module::getDebugEnabledCode() const {
MOZ_ASSERT(metadata().debugEnabled);
MOZ_ASSERT(debugUnlinkedCode_);
MOZ_ASSERT(debugLinkData_);
if (debugCodeClaimed_.compareExchange(false, true)) {
return code_;
}
Tier tier = Tier::Baseline;
auto segment =
ModuleSegment::create(tier, *debugUnlinkedCode_, *debugLinkData_);
if (!segment) {
return nullptr;
}
UniqueMetadataTier metadataTier = js::MakeUnique<MetadataTier>(tier);
if (!metadataTier || !metadataTier->clone(metadata(tier))) {
return nullptr;
}
auto codeTier =
js::MakeUnique<CodeTier>(std::move(metadataTier), std::move(segment));
if (!codeTier) {
return nullptr;
}
JumpTables jumpTables;
if (!jumpTables.init(CompileMode::Once, codeTier->segment(),
metadata(tier).codeRanges)) {
return nullptr;
}
StructTypeVector structTypes;
if (!structTypes.resize(code_->structTypes().length())) {
return nullptr;
}
for (uint32_t i = 0; i < code_->structTypes().length(); i++) {
if (!structTypes[i].copyFrom(code_->structTypes()[i])) {
return nullptr;
}
}
MutableCode debugCode =
js_new<Code>(std::move(codeTier), metadata(), std::move(jumpTables),
std::move(structTypes));
if (!debugCode || !debugCode->initialize(*debugLinkData_)) {
return nullptr;
}
return debugCode;
}
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 CreateExportObject(JSContext* cx,
HandleWasmInstanceObject instanceObj,
Handle<FunctionVector> funcImports,
const WasmTableObjectVector& tableObjs,
HandleWasmMemoryObject memoryObj,
const WasmGlobalObjectVector& globalObjs,
const ExportVector& exports) {
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;
}
instanceObj->initExportsObj(val.toObject());
return true;
}
RootedObject exportObj(cx);
if (metadata.isAsmJS()) {
exportObj = NewBuiltinClassInstance<PlainObject>(cx);
} else {
exportObj = 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(*tableObjs[exp.tableIndex()]);
break;
case DefinitionKind::Memory:
val = ObjectValue(*memoryObj);
break;
case DefinitionKind::Global:
val.setObject(*globalObjs[exp.globalIndex()]);
break;
}
if (!JS_DefinePropertyById(cx, exportObj, id, val, JSPROP_ENUMERATE)) {
return false;
}
}
if (!metadata.isAsmJS()) {
if (!JS_FreezeObject(cx, exportObj)) {
return false;
}
}
instanceObj->initExportsObj(*exportObj);
return true;
}
#ifdef ENABLE_WASM_GC
static bool MakeStructField(JSContext* cx, const ValType& v, bool isMutable,
const char* format, uint32_t fieldNo,
AutoIdVector* ids, AutoValueVector* fieldTypeObjs,
Vector<StructFieldProps>* fieldProps) {
char buf[20];
sprintf(buf, format, fieldNo);
JSAtom* atom = Atomize(cx, buf, strlen(buf));
if (!atom) {
return false;
}
RootedId id(cx, AtomToId(atom));
StructFieldProps props;
props.isMutable = isMutable;
Rooted<TypeDescr*> t(cx);
switch (v.code()) {
case ValType::I32:
t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(),
Scalar::Int32);
break;
case ValType::I64:
props.alignAsInt64 = true;
t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(),
Scalar::Int32);
break;
case ValType::F32:
t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(),
Scalar::Float32);
break;
case ValType::F64:
t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(),
Scalar::Float64);
break;
case ValType::Ref:
t = GlobalObject::getOrCreateReferenceTypeDescr(
cx, cx->global(), ReferenceType::TYPE_OBJECT);
break;
case ValType::AnyRef:
t = GlobalObject::getOrCreateReferenceTypeDescr(
cx, cx->global(), ReferenceType::TYPE_WASM_ANYREF);
break;
default:
MOZ_CRASH("Bad field type");
}
MOZ_ASSERT(t != nullptr);
if (!ids->append(id)) {
return false;
}
if (!fieldTypeObjs->append(ObjectValue(*t))) {
return false;
}
if (!fieldProps->append(props)) {
return false;
}
return true;
}
#endif
bool Module::makeStructTypeDescrs(
JSContext* cx,
MutableHandle<StructTypeDescrVector> structTypeDescrs) const {
if (structTypes().length() == 0) {
return true;
}
#ifndef ENABLE_WASM_GC
MOZ_CRASH("Should not have seen any struct types");
#else
# ifndef ENABLE_TYPED_OBJECTS
# error "GC types require TypedObject"
# endif
RootedObject typedObjectModule(
cx, GlobalObject::getOrCreateTypedObjectModule(cx, cx->global()));
if (!typedObjectModule) {
return false;
}
RootedNativeObject toModule(cx, &typedObjectModule->as<NativeObject>());
RootedObject prototype(
cx,
&toModule->getReservedSlot(TypedObjectModuleObject::StructTypePrototype)
.toObject());
for (const StructType& structType : structTypes()) {
AutoIdVector ids(cx);
AutoValueVector fieldTypeObjs(cx);
Vector<StructFieldProps> fieldProps(cx);
bool allowConstruct = true;
uint32_t k = 0;
for (StructField sf : structType.fields_) {
const ValType& v = sf.type;
if (v.code() == ValType::I64) {
sf.isMutable = false;
allowConstruct = false;
if (!MakeStructField(cx, ValType::I64, sf.isMutable, "_%d_low", k, &ids,
&fieldTypeObjs, &fieldProps)) {
return false;
}
if (!MakeStructField(cx, ValType::I32, sf.isMutable, "_%d_high", k++,
&ids, &fieldTypeObjs, &fieldProps)) {
return false;
}
} else {
if (v.isRef()) {
sf.isMutable = false;
allowConstruct = false;
}
if (!MakeStructField(cx, v, sf.isMutable, "_%d", k++, &ids,
&fieldTypeObjs, &fieldProps)) {
return false;
}
}
}
Rooted<StructTypeDescr*> structTypeDescr(
cx, StructMetaTypeDescr::createFromArrays(cx, prototype,
true,
allowConstruct, ids,
fieldTypeObjs, fieldProps));
if (!structTypeDescr || !structTypeDescrs.append(structTypeDescr)) {
return false;
}
}
return true;
#endif
}
bool Module::instantiate(JSContext* cx, Handle<FunctionVector> funcImports,
WasmTableObjectVector& tableImports,
HandleWasmMemoryObject memoryImport,
HandleValVector globalImportValues,
WasmGlobalObjectVector& globalObjs,
HandleObject instanceProto,
MutableHandleWasmInstanceObject instance) const {
MOZ_RELEASE_ASSERT(cx->wasmHaveSignalHandlers);
if (!instantiateFunctions(cx, funcImports)) {
return false;
}
RootedWasmMemoryObject memory(cx, memoryImport);
if (!instantiateMemory(cx, &memory)) {
return false;
}
Rooted<WasmTableObjectVector> tableObjs(cx);
SharedTableVector tables;
if (!instantiateTables(cx, tableImports, &tableObjs, &tables)) {
return false;
}
if (!instantiateGlobals(cx, globalImportValues, globalObjs)) {
return false;
}
UniqueTlsData tlsData = CreateTlsData(metadata().globalDataLength);
if (!tlsData) {
ReportOutOfMemory(cx);
return false;
}
SharedCode code;
UniqueDebugState maybeDebug;
if (metadata().debugEnabled) {
code = getDebugEnabledCode();
if (!code) {
ReportOutOfMemory(cx);
return false;
}
maybeDebug = cx->make_unique<DebugState>(*code, *this);
if (!maybeDebug) {
return false;
}
} else {
code = code_;
}
Rooted<StructTypeDescrVector> structTypeDescrs(cx);
if (!makeStructTypeDescrs(cx, &structTypeDescrs)) {
return false;
}
instance.set(WasmInstanceObject::create(
cx, code, dataSegments_, elemSegments_, std::move(tlsData), memory,
std::move(tables), std::move(structTypeDescrs.get()), funcImports,
metadata().globals, globalImportValues, globalObjs, instanceProto,
std::move(maybeDebug)));
if (!instance) {
return false;
}
if (!CreateExportObject(cx, instance, funcImports, tableObjs.get(), memory,
globalObjs, exports_)) {
return false;
}
if (!cx->realm()->wasm.registerInstance(cx, instance)) {
return false;
}
if (!initSegments(cx, instance, funcImports, memory, globalImportValues)) {
return false;
}
if (metadata().startFuncIndex) {
FixedInvokeArgs<0> args(cx);
if (!instance->instance().callExport(cx, *metadata().startFuncIndex,
args)) {
return false;
}
}
JSUseCounter useCounter =
metadata().isAsmJS() ? JSUseCounter::ASMJS : JSUseCounter::WASM;
cx->runtime()->setUseCounter(instance, useCounter);
if (cx->options().testWasmAwaitTier2()) {
testingBlockOnTier2Complete();
}
return true;
}