/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * * 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/WasmGenerator.h" #include "mozilla/CheckedInt.h" #include "mozilla/EnumeratedRange.h" #include "mozilla/SHA1.h" #include "mozilla/Unused.h" #include #include #include "util/Memory.h" #include "util/Text.h" #include "vm/HelperThreadState.h" #include "vm/Time.h" #include "vm/TraceLogging.h" #include "vm/TraceLoggingTypes.h" #include "wasm/WasmBaselineCompile.h" #include "wasm/WasmCompile.h" #include "wasm/WasmCraneliftCompile.h" #include "wasm/WasmIonCompile.h" #include "wasm/WasmStubs.h" #include "jit/MacroAssembler-inl.h" using namespace js; using namespace js::jit; using namespace js::wasm; using mozilla::CheckedInt; using mozilla::MakeEnumeratedRange; using mozilla::Unused; bool CompiledCode::swap(MacroAssembler& masm) { MOZ_ASSERT(bytes.empty()); if (!masm.swapBuffer(bytes)) { return false; } callSites.swap(masm.callSites()); callSiteTargets.swap(masm.callSiteTargets()); trapSites.swap(masm.trapSites()); symbolicAccesses.swap(masm.symbolicAccesses()); codeLabels.swap(masm.codeLabels()); return true; } bool CompiledCode::swapCranelift(MacroAssembler& masm, CraneliftReusableData& data) { if (!swap(masm)) { return false; } std::swap(data, craneliftReusableData); return true; } // **************************************************************************** // ModuleGenerator static const unsigned GENERATOR_LIFO_DEFAULT_CHUNK_SIZE = 4 * 1024; static const unsigned COMPILATION_LIFO_DEFAULT_CHUNK_SIZE = 64 * 1024; static const uint32_t BAD_CODE_RANGE = UINT32_MAX; ModuleGenerator::ModuleGenerator(const CompileArgs& args, ModuleEnvironment* moduleEnv, CompilerEnvironment* compilerEnv, const Atomic* cancelled, UniqueChars* error) : compileArgs_(&args), error_(error), cancelled_(cancelled), moduleEnv_(moduleEnv), compilerEnv_(compilerEnv), linkData_(nullptr), metadataTier_(nullptr), lifo_(GENERATOR_LIFO_DEFAULT_CHUNK_SIZE), masmAlloc_(&lifo_), masm_(masmAlloc_, *moduleEnv, /* limitedSize= */ false), debugTrapCodeOffset_(), lastPatchedCallSite_(0), startOfUnpatchedCallsites_(0), parallel_(false), outstanding_(0), currentTask_(nullptr), batchedBytecode_(0), finishedFuncDefs_(false) { MOZ_ASSERT(IsCompilingWasm()); } ModuleGenerator::~ModuleGenerator() { MOZ_ASSERT_IF(finishedFuncDefs_, !batchedBytecode_); MOZ_ASSERT_IF(finishedFuncDefs_, !currentTask_); if (parallel_) { if (outstanding_) { AutoLockHelperThreadState lock; // Remove any pending compilation tasks from the worklist. size_t removed = RemovePendingWasmCompileTasks(taskState_, mode(), lock); MOZ_ASSERT(outstanding_ >= removed); outstanding_ -= removed; // Wait until all active compilation tasks have finished. while (true) { MOZ_ASSERT(outstanding_ >= taskState_.finished().length()); outstanding_ -= taskState_.finished().length(); taskState_.finished().clear(); MOZ_ASSERT(outstanding_ >= taskState_.numFailed()); outstanding_ -= taskState_.numFailed(); taskState_.numFailed() = 0; if (!outstanding_) { break; } taskState_.condVar().wait(lock); /* failed or finished */ } } } else { MOZ_ASSERT(!outstanding_); } // Propagate error state. if (error_ && !*error_) { AutoLockHelperThreadState lock; *error_ = std::move(taskState_.errorMessage()); } } bool ModuleGenerator::allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOffset) { CheckedInt newGlobalDataLength(metadata_->globalDataLength); newGlobalDataLength += ComputeByteAlignment(newGlobalDataLength.value(), align); if (!newGlobalDataLength.isValid()) { return false; } *globalDataOffset = newGlobalDataLength.value(); newGlobalDataLength += bytes; if (!newGlobalDataLength.isValid()) { return false; } metadata_->globalDataLength = newGlobalDataLength.value(); return true; } bool ModuleGenerator::init(Metadata* maybeAsmJSMetadata, JSTelemetrySender telemetrySender) { // Perform fallible metadata, linkdata, assumption allocations. telemetrySender_ = telemetrySender; MOZ_ASSERT(isAsmJS() == !!maybeAsmJSMetadata); if (maybeAsmJSMetadata) { metadata_ = maybeAsmJSMetadata; } else { metadata_ = js_new(); if (!metadata_) { return false; } } if (compileArgs_->scriptedCaller.filename) { metadata_->filename = DuplicateString(compileArgs_->scriptedCaller.filename.get()); if (!metadata_->filename) { return false; } metadata_->filenameIsURL = compileArgs_->scriptedCaller.filenameIsURL; } else { MOZ_ASSERT(!compileArgs_->scriptedCaller.filenameIsURL); } if (compileArgs_->sourceMapURL) { metadata_->sourceMapURL = DuplicateString(compileArgs_->sourceMapURL.get()); if (!metadata_->sourceMapURL) { return false; } } linkData_ = js::MakeUnique(tier()); if (!linkData_) { return false; } metadataTier_ = js::MakeUnique(tier()); if (!metadataTier_) { return false; } // funcToCodeRange maps function indices to code-range indices and all // elements will be initialized by the time module generation is finished. if (!metadataTier_->funcToCodeRange.appendN(BAD_CODE_RANGE, moduleEnv_->funcs.length())) { return false; } // Pre-reserve space for large Vectors to avoid the significant cost of the // final reallocs. In particular, the MacroAssembler can be enormous, so be // extra conservative. Since large over-reservations may fail when the // actual allocations will succeed, ignore OOM failures. Note, // shrinkStorageToFit calls at the end will trim off unneeded capacity. size_t codeSectionSize = moduleEnv_->codeSection ? moduleEnv_->codeSection->size : 0; size_t estimatedCodeSize = 1.2 * EstimateCompiledCodeSize(tier(), codeSectionSize); Unused << masm_.reserve(std::min(estimatedCodeSize, MaxCodeBytesPerProcess)); Unused << metadataTier_->codeRanges.reserve(2 * moduleEnv_->numFuncDefs()); const size_t ByteCodesPerCallSite = 50; Unused << metadataTier_->callSites.reserve(codeSectionSize / ByteCodesPerCallSite); const size_t ByteCodesPerOOBTrap = 10; Unused << metadataTier_->trapSites[Trap::OutOfBounds].reserve( codeSectionSize / ByteCodesPerOOBTrap); // Allocate space in TlsData for declarations that need it. MOZ_ASSERT(metadata_->globalDataLength == 0); for (size_t i = 0; i < moduleEnv_->funcImportGlobalDataOffsets.length(); i++) { uint32_t globalDataOffset; if (!allocateGlobalBytes(sizeof(FuncImportTls), sizeof(void*), &globalDataOffset)) { return false; } moduleEnv_->funcImportGlobalDataOffsets[i] = globalDataOffset; FuncType copy; if (!copy.clone(*moduleEnv_->funcs[i].type)) { return false; } if (!metadataTier_->funcImports.emplaceBack(std::move(copy), globalDataOffset)) { return false; } } for (TableDesc& table : moduleEnv_->tables) { if (!allocateGlobalBytes(sizeof(TableTls), sizeof(void*), &table.globalDataOffset)) { return false; } } if (!isAsmJS()) { // Copy type definitions to metadata that are required at runtime, // allocating global data so that codegen can find the type id's at // runtime. for (uint32_t typeIndex = 0; typeIndex < moduleEnv_->types.length(); typeIndex++) { const TypeDef& typeDef = moduleEnv_->types[typeIndex]; TypeIdDesc& typeId = moduleEnv_->typeIds[typeIndex]; if (TypeIdDesc::isGlobal(typeDef)) { uint32_t globalDataOffset; if (!allocateGlobalBytes(sizeof(void*), sizeof(void*), &globalDataOffset)) { return false; } typeId = TypeIdDesc::global(typeDef, globalDataOffset); TypeDef copy; if (!copy.clone(typeDef)) { return false; } if (!metadata_->types.emplaceBack(std::move(copy), typeId)) { return false; } } else { typeId = TypeIdDesc::immediate(typeDef); } } // If we allow type indices, then we need to rewrite the index space to // account for types that are omitted from metadata, such as function // types that fit in an immediate. if (moduleEnv_->functionReferencesEnabled()) { // Do a linear pass to create a map from src index to dest index. RenumberMap map; for (uint32_t srcIndex = 0, destIndex = 0; srcIndex < moduleEnv_->types.length(); srcIndex++) { const TypeDef& typeDef = moduleEnv_->types[srcIndex]; if (!TypeIdDesc::isGlobal(typeDef)) { continue; } if (!map.put(srcIndex, destIndex++)) { return false; } } // Apply the map for (TypeDefWithId& typeDef : metadata_->types) { typeDef.renumber(map); } } } for (GlobalDesc& global : moduleEnv_->globals) { if (global.isConstant()) { continue; } uint32_t width = global.isIndirect() ? sizeof(void*) : SizeOf(global.type()); uint32_t globalDataOffset; if (!allocateGlobalBytes(width, width, &globalDataOffset)) { return false; } global.setOffset(globalDataOffset); } // Accumulate all exported functions: // - explicitly marked as such; // - implicitly exported by being an element of function tables; // - implicitly exported by being the start function; // The FuncExportVector stored in Metadata needs to be sorted (to allow // O(log(n)) lookup at runtime) and deduplicated. Use a vector with invalid // entries for every single function, that we'll fill as we go through the // exports, and in which we'll remove invalid entries after the fact. static_assert(((uint64_t(MaxFuncs) << 1) | 1) < uint64_t(UINT32_MAX), "bit packing won't work in ExportedFunc"); class ExportedFunc { uint32_t value; public: ExportedFunc() : value(UINT32_MAX) {} ExportedFunc(uint32_t index, bool isExplicit) : value((index << 1) | (isExplicit ? 1 : 0)) {} uint32_t index() const { return value >> 1; } bool isExplicit() const { return value & 0x1; } bool operator<(const ExportedFunc& other) const { return index() < other.index(); } bool operator==(const ExportedFunc& other) const { return index() == other.index(); } bool isInvalid() const { return value == UINT32_MAX; } void mergeExplicit(bool explicitBit) { if (!isExplicit() && explicitBit) { value |= 0x1; } } }; Vector exportedFuncs; if (!exportedFuncs.resize(moduleEnv_->numFuncs())) { return false; } auto addOrMerge = [&exportedFuncs](ExportedFunc newEntry) { uint32_t index = newEntry.index(); if (exportedFuncs[index].isInvalid()) { exportedFuncs[index] = newEntry; } else { exportedFuncs[index].mergeExplicit(newEntry.isExplicit()); } }; for (const Export& exp : moduleEnv_->exports) { if (exp.kind() == DefinitionKind::Function) { addOrMerge(ExportedFunc(exp.funcIndex(), true)); } } if (moduleEnv_->startFuncIndex) { addOrMerge(ExportedFunc(*moduleEnv_->startFuncIndex, true)); } for (const ElemSegment* seg : moduleEnv_->elemSegments) { // For now, the segments always carry function indices regardless of the // segment's declared element type; this works because the only legal // element types are funcref and externref and the only legal values are // functions and null. We always add functions in segments as exported // functions, regardless of the segment's type. In the future, if we make // the representation of AnyRef segments different, we will have to consider // function values in those segments specially. bool isAsmJS = seg->active() && moduleEnv_->tables[seg->tableIndex].isAsmJS; if (!isAsmJS) { for (uint32_t funcIndex : seg->elemFuncIndices) { if (funcIndex != NullFuncIndex) { addOrMerge(ExportedFunc(funcIndex, false)); } } } } for (const GlobalDesc& global : moduleEnv_->globals) { if (global.isVariable() && global.initExpr().kind() == InitExpr::Kind::RefFunc) { addOrMerge(ExportedFunc(global.initExpr().refFuncIndex(), false)); } } auto* newEnd = std::remove_if(exportedFuncs.begin(), exportedFuncs.end(), [](const ExportedFunc& exp) { return exp.isInvalid(); }); exportedFuncs.erase(newEnd, exportedFuncs.end()); if (!metadataTier_->funcExports.reserve(exportedFuncs.length())) { return false; } for (const ExportedFunc& funcIndex : exportedFuncs) { FuncType funcType; if (!funcType.clone(*moduleEnv_->funcs[funcIndex.index()].type)) { return false; } metadataTier_->funcExports.infallibleEmplaceBack( std::move(funcType), funcIndex.index(), funcIndex.isExplicit()); } // Determine whether parallel or sequential compilation is to be used and // initialize the CompileTasks that will be used in either mode. GlobalHelperThreadState& threads = HelperThreadState(); MOZ_ASSERT(threads.threadCount > 1); uint32_t numTasks; if (CanUseExtraThreads() && threads.cpuCount > 1) { parallel_ = true; numTasks = 2 * threads.maxWasmCompilationThreads(); } else { numTasks = 1; } if (!tasks_.initCapacity(numTasks)) { return false; } for (size_t i = 0; i < numTasks; i++) { tasks_.infallibleEmplaceBack(*moduleEnv_, *compilerEnv_, taskState_, COMPILATION_LIFO_DEFAULT_CHUNK_SIZE, telemetrySender); } if (!freeTasks_.reserve(numTasks)) { return false; } for (size_t i = 0; i < numTasks; i++) { freeTasks_.infallibleAppend(&tasks_[i]); } // Fill in function stubs for each import so that imported functions can be // used in all the places that normal function definitions can (table // elements, export calls, etc). CompiledCode& importCode = tasks_[0].output; MOZ_ASSERT(importCode.empty()); if (!GenerateImportFunctions(*moduleEnv_, metadataTier_->funcImports, &importCode)) { return false; } if (!linkCompiledCode(importCode)) { return false; } importCode.clear(); return true; } bool ModuleGenerator::funcIsCompiled(uint32_t funcIndex) const { return metadataTier_->funcToCodeRange[funcIndex] != BAD_CODE_RANGE; } const CodeRange& ModuleGenerator::funcCodeRange(uint32_t funcIndex) const { MOZ_ASSERT(funcIsCompiled(funcIndex)); const CodeRange& cr = metadataTier_->codeRanges[metadataTier_->funcToCodeRange[funcIndex]]; MOZ_ASSERT(cr.isFunction()); return cr; } static bool InRange(uint32_t caller, uint32_t callee) { // We assume JumpImmediateRange is defined conservatively enough that the // slight difference between 'caller' (which is really the return address // offset) and the actual base of the relative displacement computation // isn't significant. uint32_t range = std::min(JitOptions.jumpThreshold, JumpImmediateRange); if (caller < callee) { return callee - caller < range; } return caller - callee < range; } typedef HashMap, SystemAllocPolicy> OffsetMap; typedef EnumeratedArray> TrapMaybeOffsetArray; bool ModuleGenerator::linkCallSites() { masm_.haltingAlign(CodeAlignment); // Create far jumps for calls that have relative offsets that may otherwise // go out of range. This method is called both between function bodies (at a // frequency determined by the ISA's jump range) and once at the very end of // a module's codegen after all possible calls/traps have been emitted. OffsetMap existingCallFarJumps; for (; lastPatchedCallSite_ < metadataTier_->callSites.length(); lastPatchedCallSite_++) { const CallSite& callSite = metadataTier_->callSites[lastPatchedCallSite_]; const CallSiteTarget& target = callSiteTargets_[lastPatchedCallSite_]; uint32_t callerOffset = callSite.returnAddressOffset(); switch (callSite.kind()) { case CallSiteDesc::Dynamic: case CallSiteDesc::Symbolic: break; case CallSiteDesc::Func: { if (funcIsCompiled(target.funcIndex())) { uint32_t calleeOffset = funcCodeRange(target.funcIndex()).funcUncheckedCallEntry(); if (InRange(callerOffset, calleeOffset)) { masm_.patchCall(callerOffset, calleeOffset); break; } } OffsetMap::AddPtr p = existingCallFarJumps.lookupForAdd(target.funcIndex()); if (!p) { Offsets offsets; offsets.begin = masm_.currentOffset(); if (!callFarJumps_.emplaceBack(target.funcIndex(), masm_.farJumpWithPatch())) { return false; } offsets.end = masm_.currentOffset(); if (masm_.oom()) { return false; } if (!metadataTier_->codeRanges.emplaceBack(CodeRange::FarJumpIsland, offsets)) { return false; } if (!existingCallFarJumps.add(p, target.funcIndex(), offsets.begin)) { return false; } } masm_.patchCall(callerOffset, p->value()); break; } case CallSiteDesc::Breakpoint: case CallSiteDesc::EnterFrame: case CallSiteDesc::LeaveFrame: { Uint32Vector& jumps = metadataTier_->debugTrapFarJumpOffsets; if (jumps.empty() || !InRange(jumps.back(), callerOffset)) { Offsets offsets; offsets.begin = masm_.currentOffset(); CodeOffset jumpOffset = masm_.farJumpWithPatch(); offsets.end = masm_.currentOffset(); if (masm_.oom()) { return false; } if (!metadataTier_->codeRanges.emplaceBack(CodeRange::FarJumpIsland, offsets)) { return false; } if (!debugTrapFarJumps_.emplaceBack(jumpOffset)) { return false; } if (!jumps.emplaceBack(offsets.begin)) { return false; } } break; } } } masm_.flushBuffer(); return !masm_.oom(); } void ModuleGenerator::noteCodeRange(uint32_t codeRangeIndex, const CodeRange& codeRange) { switch (codeRange.kind()) { case CodeRange::Function: MOZ_ASSERT(metadataTier_->funcToCodeRange[codeRange.funcIndex()] == BAD_CODE_RANGE); metadataTier_->funcToCodeRange[codeRange.funcIndex()] = codeRangeIndex; break; case CodeRange::InterpEntry: metadataTier_->lookupFuncExport(codeRange.funcIndex()) .initEagerInterpEntryOffset(codeRange.begin()); break; case CodeRange::JitEntry: // Nothing to do: jit entries are linked in the jump tables. break; case CodeRange::ImportJitExit: metadataTier_->funcImports[codeRange.funcIndex()].initJitExitOffset( codeRange.begin()); break; case CodeRange::ImportInterpExit: metadataTier_->funcImports[codeRange.funcIndex()].initInterpExitOffset( codeRange.begin()); break; case CodeRange::DebugTrap: MOZ_ASSERT(!debugTrapCodeOffset_); debugTrapCodeOffset_ = codeRange.begin(); break; case CodeRange::TrapExit: MOZ_ASSERT(!linkData_->trapOffset); linkData_->trapOffset = codeRange.begin(); break; case CodeRange::Throw: // Jumped to by other stubs, so nothing to do. break; case CodeRange::FarJumpIsland: case CodeRange::BuiltinThunk: MOZ_CRASH("Unexpected CodeRange kind"); } } template static bool AppendForEach(Vec* dstVec, const Vec& srcVec, Op op) { if (!dstVec->growByUninitialized(srcVec.length())) { return false; } using T = typename Vec::ElementType; const T* src = srcVec.begin(); T* dstBegin = dstVec->begin(); T* dstEnd = dstVec->end(); T* dstStart = dstEnd - srcVec.length(); for (T* dst = dstStart; dst != dstEnd; dst++, src++) { new (dst) T(*src); op(dst - dstBegin, dst); } return true; } bool ModuleGenerator::linkCompiledCode(CompiledCode& code) { // Before merging in new code, if calls in a prior code range might go out of // range, insert far jumps to extend the range. if (!InRange(startOfUnpatchedCallsites_, masm_.size() + code.bytes.length())) { startOfUnpatchedCallsites_ = masm_.size(); if (!linkCallSites()) { return false; } } // All code offsets in 'code' must be incremented by their position in the // overall module when the code was appended. masm_.haltingAlign(CodeAlignment); const size_t offsetInModule = masm_.size(); if (!masm_.appendRawCode(code.bytes.begin(), code.bytes.length())) { return false; } auto codeRangeOp = [=](uint32_t codeRangeIndex, CodeRange* codeRange) { codeRange->offsetBy(offsetInModule); noteCodeRange(codeRangeIndex, *codeRange); }; if (!AppendForEach(&metadataTier_->codeRanges, code.codeRanges, codeRangeOp)) { return false; } auto callSiteOp = [=](uint32_t, CallSite* cs) { cs->offsetBy(offsetInModule); }; if (!AppendForEach(&metadataTier_->callSites, code.callSites, callSiteOp)) { return false; } if (!callSiteTargets_.appendAll(code.callSiteTargets)) { return false; } for (Trap trap : MakeEnumeratedRange(Trap::Limit)) { auto trapSiteOp = [=](uint32_t, TrapSite* ts) { ts->offsetBy(offsetInModule); }; if (!AppendForEach(&metadataTier_->trapSites[trap], code.trapSites[trap], trapSiteOp)) { return false; } } for (const SymbolicAccess& access : code.symbolicAccesses) { uint32_t patchAt = offsetInModule + access.patchAt.offset(); if (!linkData_->symbolicLinks[access.target].append(patchAt)) { return false; } } for (const CodeLabel& codeLabel : code.codeLabels) { LinkData::InternalLink link; link.patchAtOffset = offsetInModule + codeLabel.patchAt().offset(); link.targetOffset = offsetInModule + codeLabel.target().offset(); #ifdef JS_CODELABEL_LINKMODE link.mode = codeLabel.linkMode(); #endif if (!linkData_->internalLinks.append(link)) { return false; } } for (size_t i = 0; i < code.stackMaps.length(); i++) { StackMaps::Maplet maplet = code.stackMaps.move(i); maplet.offsetBy(offsetInModule); if (!metadataTier_->stackMaps.add(maplet)) { // This function is now the only owner of maplet.map, so we'd better // free it right now. maplet.map->destroy(); return false; } } return true; } static bool ExecuteCompileTask(CompileTask* task, UniqueChars* error) { MOZ_ASSERT(task->lifo.isEmpty()); MOZ_ASSERT(task->output.empty()); #ifdef ENABLE_SPIDERMONKEY_TELEMETRY int64_t startTime = PRMJ_Now(); int compileTimeTelemetryID; #endif switch (task->compilerEnv.tier()) { case Tier::Optimized: switch (task->compilerEnv.optimizedBackend()) { case OptimizedBackend::Cranelift: if (!CraneliftCompileFunctions(task->moduleEnv, task->compilerEnv, task->lifo, task->inputs, &task->output, error)) { return false; } #ifdef ENABLE_SPIDERMONKEY_TELEMETRY compileTimeTelemetryID = JS_TELEMETRY_WASM_COMPILE_TIME_CRANELIFT_US; #endif break; case OptimizedBackend::Ion: if (!IonCompileFunctions(task->moduleEnv, task->compilerEnv, task->lifo, task->inputs, &task->output, error)) { return false; } #ifdef ENABLE_SPIDERMONKEY_TELEMETRY compileTimeTelemetryID = JS_TELEMETRY_WASM_COMPILE_TIME_ION_US; #endif break; } break; case Tier::Baseline: if (!BaselineCompileFunctions(task->moduleEnv, task->compilerEnv, task->lifo, task->inputs, &task->output, error)) { return false; } #ifdef ENABLE_SPIDERMONKEY_TELEMETRY compileTimeTelemetryID = JS_TELEMETRY_WASM_COMPILE_TIME_BASELINE_US; #endif break; } #ifdef ENABLE_SPIDERMONKEY_TELEMETRY int64_t endTime = PRMJ_Now(); int64_t compileTimeMicros = endTime - startTime; task->telemetrySender.addTelemetry(compileTimeTelemetryID, compileTimeMicros); #endif MOZ_ASSERT(task->lifo.isEmpty()); MOZ_ASSERT(task->inputs.length() == task->output.codeRanges.length()); task->inputs.clear(); return true; } void CompileTask::runHelperThreadTask(AutoLockHelperThreadState& lock) { TraceLoggerThread* logger = TraceLoggerForCurrentThread(); AutoTraceLog logCompile(logger, TraceLogger_WasmCompilation); UniqueChars error; bool ok; { AutoUnlockHelperThreadState unlock(lock); ok = ExecuteCompileTask(this, &error); } // Don't release the lock between updating our state and returning from this // method. if (!ok || !state.finished().append(this)) { state.numFailed()++; if (!state.errorMessage()) { state.errorMessage() = std::move(error); } } state.condVar().notify_one(); /* failed or finished */ } bool ModuleGenerator::locallyCompileCurrentTask() { if (!ExecuteCompileTask(currentTask_, error_)) { return false; } if (!finishTask(currentTask_)) { return false; } currentTask_ = nullptr; batchedBytecode_ = 0; return true; } bool ModuleGenerator::finishTask(CompileTask* task) { masm_.haltingAlign(CodeAlignment); if (!linkCompiledCode(task->output)) { return false; } task->output.clear(); MOZ_ASSERT(task->inputs.empty()); MOZ_ASSERT(task->output.empty()); MOZ_ASSERT(task->lifo.isEmpty()); freeTasks_.infallibleAppend(task); return true; } bool ModuleGenerator::launchBatchCompile() { MOZ_ASSERT(currentTask_); if (cancelled_ && *cancelled_) { return false; } if (!parallel_) { return locallyCompileCurrentTask(); } if (!StartOffThreadWasmCompile(currentTask_, mode())) { return false; } outstanding_++; currentTask_ = nullptr; batchedBytecode_ = 0; return true; } bool ModuleGenerator::finishOutstandingTask() { MOZ_ASSERT(parallel_); CompileTask* task = nullptr; { AutoLockHelperThreadState lock; while (true) { MOZ_ASSERT(outstanding_ > 0); if (taskState_.numFailed() > 0) { return false; } if (!taskState_.finished().empty()) { outstanding_--; task = taskState_.finished().popCopy(); break; } taskState_.condVar().wait(lock); /* failed or finished */ } } // Call outside of the compilation lock. return finishTask(task); } bool ModuleGenerator::compileFuncDef(uint32_t funcIndex, uint32_t lineOrBytecode, const uint8_t* begin, const uint8_t* end, Uint32Vector&& lineNums) { MOZ_ASSERT(!finishedFuncDefs_); MOZ_ASSERT(funcIndex < moduleEnv_->numFuncs()); uint32_t threshold; switch (tier()) { case Tier::Baseline: threshold = JitOptions.wasmBatchBaselineThreshold; break; case Tier::Optimized: switch (compilerEnv_->optimizedBackend()) { case OptimizedBackend::Ion: threshold = JitOptions.wasmBatchIonThreshold; break; case OptimizedBackend::Cranelift: threshold = JitOptions.wasmBatchCraneliftThreshold; break; default: MOZ_CRASH("Invalid optimizedBackend value"); } break; default: MOZ_CRASH("Invalid tier value"); break; } uint32_t funcBytecodeLength = end - begin; // Do not go over the threshold if we can avoid it: spin off the compilation // before appending the function if we would go over. (Very large single // functions may still exceed the threshold but this is fine; it'll be very // uncommon and is in any case safely handled by the MacroAssembler's buffer // limit logic.) if (currentTask_ && currentTask_->inputs.length() && batchedBytecode_ + funcBytecodeLength > threshold) { if (!launchBatchCompile()) { return false; } } if (!currentTask_) { if (freeTasks_.empty() && !finishOutstandingTask()) { return false; } currentTask_ = freeTasks_.popCopy(); } if (!currentTask_->inputs.emplaceBack(funcIndex, lineOrBytecode, begin, end, std::move(lineNums))) { return false; } batchedBytecode_ += funcBytecodeLength; MOZ_ASSERT(batchedBytecode_ <= MaxCodeSectionBytes); return true; } bool ModuleGenerator::finishFuncDefs() { MOZ_ASSERT(!finishedFuncDefs_); if (currentTask_ && !locallyCompileCurrentTask()) { return false; } finishedFuncDefs_ = true; return true; } bool ModuleGenerator::finishCodegen() { // Now that all functions and stubs are generated and their CodeRanges // known, patch all calls (which can emit far jumps) and far jumps. Linking // can emit tiny far-jump stubs, so there is an ordering dependency here. if (!linkCallSites()) { return false; } for (CallFarJump far : callFarJumps_) { masm_.patchFarJump(far.jump, funcCodeRange(far.funcIndex).funcUncheckedCallEntry()); } for (CodeOffset farJump : debugTrapFarJumps_) { masm_.patchFarJump(farJump, debugTrapCodeOffset_); } // None of the linking or far-jump operations should emit masm metadata. MOZ_ASSERT(masm_.callSites().empty()); MOZ_ASSERT(masm_.callSiteTargets().empty()); MOZ_ASSERT(masm_.trapSites().empty()); MOZ_ASSERT(masm_.symbolicAccesses().empty()); MOZ_ASSERT(masm_.codeLabels().empty()); masm_.finish(); return !masm_.oom(); } bool ModuleGenerator::finishMetadataTier() { // The stack maps aren't yet sorted. Do so now, since we'll need to // binary-search them at GC time. metadataTier_->stackMaps.sort(); #ifdef DEBUG // Check that the stack map contains no duplicates, since that could lead to // ambiguities about stack slot pointerness. uint8_t* previousNextInsnAddr = nullptr; for (size_t i = 0; i < metadataTier_->stackMaps.length(); i++) { const StackMaps::Maplet& maplet = metadataTier_->stackMaps.get(i); MOZ_ASSERT_IF(i > 0, uintptr_t(maplet.nextInsnAddr) > uintptr_t(previousNextInsnAddr)); previousNextInsnAddr = maplet.nextInsnAddr; } // Assert all sorted metadata is sorted. uint32_t last = 0; for (const CodeRange& codeRange : metadataTier_->codeRanges) { MOZ_ASSERT(codeRange.begin() >= last); last = codeRange.end(); } last = 0; for (const CallSite& callSite : metadataTier_->callSites) { MOZ_ASSERT(callSite.returnAddressOffset() >= last); last = callSite.returnAddressOffset(); } for (Trap trap : MakeEnumeratedRange(Trap::Limit)) { last = 0; for (const TrapSite& trapSite : metadataTier_->trapSites[trap]) { MOZ_ASSERT(trapSite.pcOffset >= last); last = trapSite.pcOffset; } } last = 0; for (uint32_t debugTrapFarJumpOffset : metadataTier_->debugTrapFarJumpOffsets) { MOZ_ASSERT(debugTrapFarJumpOffset >= last); last = debugTrapFarJumpOffset; } #endif // These Vectors can get large and the excess capacity can be significant, // so realloc them down to size. metadataTier_->funcToCodeRange.shrinkStorageToFit(); metadataTier_->codeRanges.shrinkStorageToFit(); metadataTier_->callSites.shrinkStorageToFit(); metadataTier_->trapSites.shrinkStorageToFit(); metadataTier_->debugTrapFarJumpOffsets.shrinkStorageToFit(); for (Trap trap : MakeEnumeratedRange(Trap::Limit)) { metadataTier_->trapSites[trap].shrinkStorageToFit(); } return true; } UniqueCodeTier ModuleGenerator::finishCodeTier() { MOZ_ASSERT(finishedFuncDefs_); while (outstanding_ > 0) { if (!finishOutstandingTask()) { return nullptr; } } #ifdef DEBUG for (uint32_t codeRangeIndex : metadataTier_->funcToCodeRange) { MOZ_ASSERT(codeRangeIndex != BAD_CODE_RANGE); } #endif // Now that all imports/exports are known, we can generate a special // CompiledCode containing stubs. CompiledCode& stubCode = tasks_[0].output; MOZ_ASSERT(stubCode.empty()); if (!GenerateStubs(*moduleEnv_, metadataTier_->funcImports, metadataTier_->funcExports, &stubCode)) { return nullptr; } if (!linkCompiledCode(stubCode)) { return nullptr; } // Finish linking and metadata. if (!finishCodegen()) { return nullptr; } if (!finishMetadataTier()) { return nullptr; } UniqueModuleSegment segment = ModuleSegment::create(tier(), masm_, *linkData_); if (!segment) { return nullptr; } metadataTier_->stackMaps.offsetBy(uintptr_t(segment->base())); #ifdef DEBUG // Check that each stack map is associated with a plausible instruction. for (size_t i = 0; i < metadataTier_->stackMaps.length(); i++) { MOZ_ASSERT(IsValidStackMapKey(compilerEnv_->debugEnabled(), metadataTier_->stackMaps.get(i).nextInsnAddr), "wasm stack map does not reference a valid insn"); } #endif return js::MakeUnique(std::move(metadataTier_), std::move(segment)); } SharedMetadata ModuleGenerator::finishMetadata(const Bytes& bytecode) { // Finish initialization of Metadata, which is only needed for constructing // the initial Module, not for tier-2 compilation. MOZ_ASSERT(mode() != CompileMode::Tier2); // Copy over data from the ModuleEnvironment. metadata_->memoryUsage = moduleEnv_->memoryUsage; metadata_->minMemoryLength = moduleEnv_->minMemoryLength; metadata_->maxMemoryLength = moduleEnv_->maxMemoryLength; metadata_->startFuncIndex = moduleEnv_->startFuncIndex; metadata_->tables = std::move(moduleEnv_->tables); metadata_->globals = std::move(moduleEnv_->globals); #ifdef ENABLE_WASM_EXCEPTIONS metadata_->events = std::move(moduleEnv_->events); #endif metadata_->nameCustomSectionIndex = moduleEnv_->nameCustomSectionIndex; metadata_->moduleName = moduleEnv_->moduleName; metadata_->funcNames = std::move(moduleEnv_->funcNames); metadata_->omitsBoundsChecks = moduleEnv_->hugeMemoryEnabled(); metadata_->v128Enabled = moduleEnv_->v128Enabled(); metadata_->usesDuplicateImports = moduleEnv_->usesDuplicateImports; // Copy over additional debug information. if (compilerEnv_->debugEnabled()) { metadata_->debugEnabled = true; const size_t numFuncs = moduleEnv_->funcs.length(); if (!metadata_->debugFuncArgTypes.resize(numFuncs)) { return nullptr; } if (!metadata_->debugFuncReturnTypes.resize(numFuncs)) { return nullptr; } for (size_t i = 0; i < numFuncs; i++) { if (!metadata_->debugFuncArgTypes[i].appendAll( moduleEnv_->funcs[i].type->args())) { return nullptr; } if (!metadata_->debugFuncReturnTypes[i].appendAll( moduleEnv_->funcs[i].type->results())) { return nullptr; } } static_assert(sizeof(ModuleHash) <= sizeof(mozilla::SHA1Sum::Hash), "The ModuleHash size shall not exceed the SHA1 hash size."); mozilla::SHA1Sum::Hash hash; mozilla::SHA1Sum sha1Sum; sha1Sum.update(bytecode.begin(), bytecode.length()); sha1Sum.finish(hash); memcpy(metadata_->debugHash, hash, sizeof(ModuleHash)); } MOZ_ASSERT_IF(moduleEnv_->nameCustomSectionIndex, !!metadata_->namePayload); // Metadata shouldn't be mutably modified after finishMetadata(). SharedMetadata metadata = metadata_; metadata_ = nullptr; return metadata; } SharedModule ModuleGenerator::finishModule( const ShareableBytes& bytecode, JS::OptimizedEncodingListener* maybeTier2Listener) { MOZ_ASSERT(mode() == CompileMode::Once || mode() == CompileMode::Tier1); UniqueCodeTier codeTier = finishCodeTier(); if (!codeTier) { return nullptr; } JumpTables jumpTables; if (!jumpTables.init(mode(), codeTier->segment(), codeTier->metadata().codeRanges)) { return nullptr; } // Copy over data from the Bytecode, which is going away at the end of // compilation. DataSegmentVector dataSegments; if (!dataSegments.reserve(moduleEnv_->dataSegments.length())) { return nullptr; } for (const DataSegmentEnv& srcSeg : moduleEnv_->dataSegments) { MutableDataSegment dstSeg = js_new(srcSeg); if (!dstSeg) { return nullptr; } if (!dstSeg->bytes.append(bytecode.begin() + srcSeg.bytecodeOffset, srcSeg.length)) { return nullptr; } dataSegments.infallibleAppend(std::move(dstSeg)); } CustomSectionVector customSections; if (!customSections.reserve(moduleEnv_->customSections.length())) { return nullptr; } for (const CustomSectionEnv& srcSec : moduleEnv_->customSections) { CustomSection sec; if (!sec.name.append(bytecode.begin() + srcSec.nameOffset, srcSec.nameLength)) { return nullptr; } MutableBytes payload = js_new(); if (!payload) { return nullptr; } if (!payload->append(bytecode.begin() + srcSec.payloadOffset, srcSec.payloadLength)) { return nullptr; } sec.payload = std::move(payload); customSections.infallibleAppend(std::move(sec)); } if (moduleEnv_->nameCustomSectionIndex) { metadata_->namePayload = customSections[*moduleEnv_->nameCustomSectionIndex].payload; } SharedMetadata metadata = finishMetadata(bytecode.bytes); if (!metadata) { return nullptr; } MutableCode code = js_new(std::move(codeTier), *metadata, std::move(jumpTables)); if (!code || !code->initialize(*linkData_)) { return nullptr; } // See Module debugCodeClaimed_ comments for why we need to make a separate // debug copy. UniqueBytes debugUnlinkedCode; UniqueLinkData debugLinkData; const ShareableBytes* debugBytecode = nullptr; if (compilerEnv_->debugEnabled()) { MOZ_ASSERT(mode() == CompileMode::Once); MOZ_ASSERT(tier() == Tier::Debug); debugUnlinkedCode = js::MakeUnique(); if (!debugUnlinkedCode || !debugUnlinkedCode->resize(masm_.bytesNeeded())) { return nullptr; } masm_.executableCopy(debugUnlinkedCode->begin()); debugLinkData = std::move(linkData_); debugBytecode = &bytecode; } // All the components are finished, so create the complete Module and start // tier-2 compilation if requested. MutableModule module = js_new( *code, std::move(moduleEnv_->imports), std::move(moduleEnv_->exports), std::move(dataSegments), std::move(moduleEnv_->elemSegments), std::move(customSections), std::move(debugUnlinkedCode), std::move(debugLinkData), debugBytecode); if (!module) { return nullptr; } if (mode() == CompileMode::Tier1) { module->startTier2(*compileArgs_, bytecode, maybeTier2Listener, telemetrySender_); } else if (tier() == Tier::Serialized && maybeTier2Listener) { module->serialize(*linkData_, *maybeTier2Listener); } return module; } bool ModuleGenerator::finishTier2(const Module& module) { MOZ_ASSERT(mode() == CompileMode::Tier2); MOZ_ASSERT(tier() == Tier::Optimized); MOZ_ASSERT(!compilerEnv_->debugEnabled()); if (cancelled_ && *cancelled_) { return false; } UniqueCodeTier codeTier = finishCodeTier(); if (!codeTier) { return false; } if (MOZ_UNLIKELY(JitOptions.wasmDelayTier2)) { // Introduce an artificial delay when testing wasmDelayTier2, since we // want to exercise both tier1 and tier2 code in this case. std::this_thread::sleep_for(std::chrono::milliseconds(500)); } return module.finishTier2(*linkData_, std::move(codeTier)); } size_t CompiledCode::sizeOfExcludingThis( mozilla::MallocSizeOf mallocSizeOf) const { size_t trapSitesSize = 0; for (const TrapSiteVector& vec : trapSites) { trapSitesSize += vec.sizeOfExcludingThis(mallocSizeOf); } return bytes.sizeOfExcludingThis(mallocSizeOf) + codeRanges.sizeOfExcludingThis(mallocSizeOf) + callSites.sizeOfExcludingThis(mallocSizeOf) + callSiteTargets.sizeOfExcludingThis(mallocSizeOf) + trapSitesSize + symbolicAccesses.sizeOfExcludingThis(mallocSizeOf) + codeLabels.sizeOfExcludingThis(mallocSizeOf); } size_t CompileTask::sizeOfExcludingThis( mozilla::MallocSizeOf mallocSizeOf) const { return lifo.sizeOfExcludingThis(mallocSizeOf) + inputs.sizeOfExcludingThis(mallocSizeOf) + output.sizeOfExcludingThis(mallocSizeOf); }