usingnamespace js; usingnamespace js::jit; usingnamespace js::wasm; using mozilla::Atomic; using mozilla::BinarySearch; using mozilla::BinarySearchIf; using mozilla::DebugOnly; using mozilla::MakeEnumeratedRange; using mozilla::MallocSizeOf; using mozilla::Maybe;
MOZ_RELEASE_ASSERT(codeLength == RoundupExecutableCodePageSize(codeLength)); void* p = AllocateExecutableMemory(codeLength, ProtectionSetting::Writable,
MemCheckKind::MakeUndefined);
// If the allocation failed and the embedding gives us a last-ditch attempt // to purge all memory (which, in gecko, does a purging GC/CC/GC), do that // then retry the allocation. if (!p && allowLastDitchGC) { if (OnLargeAllocationFailure) {
OnLargeAllocationFailure();
p = AllocateExecutableMemory(codeLength, ProtectionSetting::Writable,
MemCheckKind::MakeUndefined);
}
}
if (!p) { return nullptr;
}
// Construct AutoMarkJitCodeWritableForThread after allocating memory, to // ensure it's not nested (OnLargeAllocationFailure can trigger GC).
writable.emplace();
// We account for the bytes allocated in WasmModuleObject::create, where we // have the necessary JSContext. return UniqueCodeBytes((uint8_t*)p, FreeCode(codeLength));
}
bool CodeSource::copyAndLink(jit::AutoMarkJitCodeWritableForThread& writable,
uint8_t* codeStart) const { // Copy the machine code over if (masm_) {
masm_->executableCopy(codeStart);
} else {
memcpy(codeStart, bytes_, length_);
}
// Use link data if we have it, or else fall back to basic linking using the // MacroAssembler. if (linkData_) { return StaticallyLink(writable, codeStart, *linkData_, code_);
}
// We must always have link data if we're coming from raw bytes.
MOZ_ASSERT(masm_); // If we didn't provide link data, then we shouldn't have provided the code // object.
MOZ_ASSERT(!code_);
PatchDebugSymbolicAccesses(codeStart, *masm_); for (const CodeLabel& label : masm_->codeLabels()) {
Assembler::Bind(codeStart, label);
} returntrue;
}
size_t CodeSegment::AllocationAlignment() { // If we are write-protecting code, all new code allocations must be rounded // to the system page size. if (JitOptions.writeProtectCode) { return gc::SystemPageSize();
}
// Otherwise we can just use the standard JIT code alignment. return jit::CodeAlignment;
}
// When allocating a single stub to a page, we should not always place the stub // at the beginning of the page as the stubs will tend to thrash the icache by // creating conflicts (everything ends up in the same cache set). Instead, // locate stubs at different line offsets up to 3/4 the system page size (the // code allocation quantum). // // This may be called on background threads, hence the atomic. static uint32_t RandomPaddingForCodeLength(uint32_t codeLength) { // The counter serves only to spread the code out, it has no other meaning and // can wrap around. static mozilla::Atomic<uint32_t, mozilla::MemoryOrdering::ReleaseAcquire>
counter(0); // We assume that the icache line size is 64 bytes, which is close to // universally true. const size_t cacheLineSize = 64; const size_t systemPageSize = gc::SystemPageSize();
// If we're not write-protecting code, then we do not need to add any padding if (!JitOptions.writeProtectCode) { return0;
}
// Don't add more than 3/4 of a page of padding
size_t maxPadBytes = ((systemPageSize * 3) / 4);
size_t maxPadLines = maxPadBytes / cacheLineSize;
// If code length is close to a page boundary, avoid pushing it to a new page
size_t remainingBytesInPage =
AlignBytes(codeLength, systemPageSize) - codeLength;
size_t remainingLinesInPage = remainingBytesInPage / cacheLineSize;
// Limit padding to the smallest of the above
size_t padLinesAvailable = std::min(maxPadLines, remainingLinesInPage);
// Don't add any padding if none is available if (padLinesAvailable == 0) { return0;
}
uint32_t random = counter++;
uint32_t padding = (random % padLinesAvailable) * cacheLineSize; // "adding on the padding area doesn't change the total number of pages // required"
MOZ_ASSERT(AlignBytes(codeLength + padding, systemPageSize) ==
AlignBytes(codeLength, systemPageSize)); return padding;
}
// If we have a pool of segments, try to find one that has enough space. We // just check the last segment in the pool for simplicity.
SharedCodeSegment segment; if (segmentPool && !segmentPool->empty() &&
segmentPool->back()->hasSpace(*allocationLength)) {
segment = segmentPool->back();
} else {
uint32_t newSegmentCapacity =
RoundupExecutableCodePageSize(*allocationLength);
segment =
CodeSegment::create(writable, newSegmentCapacity, allowLastDitchGC); if (!segment) { return nullptr;
} if (segmentPool && !segmentPool->append(segment)) { return nullptr;
}
}
// Claim space in the segment we found or created
uint8_t* allocationStart = nullptr;
segment->claimSpace(*allocationLength, &allocationStart);
*codeStart = allocationStart + paddingLength;
if (!writable) {
writable.emplace();
} if (!codeSource.copyAndLink(*writable, *codeStart)) { return nullptr;
}
// Clear the padding between the end of the code and the end of the // allocation.
uint8_t* allocationEnd = allocationStart + *allocationLength;
uint8_t* codeEnd = *codeStart + codeLength;
MOZ_ASSERT(codeEnd <= allocationEnd);
size_t paddingAfterCode = allocationEnd - codeEnd; // The swept code pattern is guaranteed to crash if it is ever executed.
memset(codeEnd, JS_SWEPT_CODE_PATTERN, paddingAfterCode);
// Optimized compilation finishes on a background thread, so we must make sure // to flush the icaches of all the executing threads. // Reprotect the whole region to avoid having separate RW and RX mappings. if (*allocationLength != 0 &&
!ExecutableAllocator::makeExecutableAndFlushICache(allocationStart,
*allocationLength)) { return nullptr;
}
// Everything after this point must be guaranteed to succeed. A failure after // this point can leave things in an inconsistent state, and be observed if we // retry to create a lazy stub.
uint32_t codeRangeIndex = 0; for (uint32_t funcExportIndex : funcExportIndices) { const FuncExport& fe = funcExports[funcExportIndex]; const FuncType& funcType = codeMeta_->getFuncType(fe.funcIndex());
const FuncExport& fe = tierCodeBlock.funcExports[funcExportIndex]; const FuncType& funcType = codeMeta_->getFuncType(fe.funcIndex());
// We created one or two stubs, depending on the function type.
uint32_t funcEntryRanges = funcType.canHaveJitEntry() ? 2 : 1;
MOZ_ASSERT(codeRanges.length() >= funcEntryRanges);
// The first created range is the interp entry const CodeRange& interpRange =
codeRanges[codeRanges.length() - funcEntryRanges];
MOZ_ASSERT(interpRange.isInterpEntry());
*interpEntry = block.base() + interpRange.begin();
// The second created range is the jit entry if (funcType.canHaveJitEntry()) { const CodeRange& jitRange =
codeRanges[codeRanges.length() - funcEntryRanges + 1];
MOZ_ASSERT(jitRange.isJitEntry());
jumpTables_.setJitEntry(jitRange.funcIndex(),
block.base() + jitRange.begin());
} returntrue;
}
// Try to get or create the interpreter entry. if (tryGetOrCreate()) { returntrue;
}
// The allocation failed. Release the lock and try a last-ditch GC before // retrying, to avoid a mutex ordering violation between WasmCodeProtected // and GlobalHelperThreadState. if (!OnLargeAllocationFailure) { returnfalse;
}
OnLargeAllocationFailure();
// Try again. We need to redo the lookup too in the case that someone is // racing with us. return tryGetOrCreate();
}
void runHelperThreadTask(AutoLockHelperThreadState& locked) override { if (!cancelled_) {
AutoUnlockHelperThreadState unlock(locked);
// In the case `!success && !cancelled_`, compilation has failed // and this function will be stuck in state TierUpState::Requested // forever.
UniqueChars error;
UniqueCharsVector warnings; bool success = CompilePartialTier2(*code_, funcIndex_, &error, &warnings,
&cancelled_);
ReportTier2ResultsOffThread(
cancelled_, success, mozilla::Some(funcIndex_),
code_->codeMeta().scriptedCaller(), error, warnings);
}
// The task is finished, release it.
js_delete(this);
}
// The caller must call tryClaimTierUp() before. bool Code::requestTierUp(uint32_t funcIndex) const { // Note: this runs on the requesting (wasm-running) thread, not on a // compilation-helper thread.
MOZ_ASSERT(funcStates_[funcIndex - codeMeta_->numFuncImports].tierUpState ==
TierUpState::Requested);
auto task =
js::MakeUnique<Module::PartialTier2CompileTaskImpl>(*this, funcIndex); if (!task) { // Effect is (I think), if we OOM here, the request is ignored. // See bug 1911060. returnfalse;
}
bool Code::finishTier2(UniqueCodeBlock tier2CodeBlock,
UniqueLinkData tier2LinkData, const CompileAndLinkStats& tier2Stats) const {
MOZ_RELEASE_ASSERT(mode_ == CompileMode::EagerTiering ||
mode_ == CompileMode::LazyTiering);
MOZ_RELEASE_ASSERT(hasCompleteTier2_ == false &&
tier2CodeBlock->tier() == Tier::Optimized); // Acquire the write guard before we start mutating anything. We hold this // for the minimum amount of time necessary.
CodeBlock* tier2CodePointer;
{ auto guard = data_.writeLock();
// Record the tier2 stats.
guard->tier2Stats.merge(tier2Stats);
// Borrow the tier2 pointer before moving it into the block vector. This // ensures we maintain the invariant that completeTier2_ is never read if // hasCompleteTier2_ is false.
tier2CodePointer = tier2CodeBlock.get();
// Publish this code to the process wide map. if (!addCodeBlock(guard, std::move(tier2CodeBlock),
std::move(tier2LinkData))) { returnfalse;
}
// Before we can make tier-2 live, we need to compile tier2 versions of any // extant tier1 lazy stubs (otherwise, tiering would break the assumption // that any extant exported wasm function has had a lazy entry stub already // compiled for it). // // Also see doc block for stubs in WasmJS.cpp.
Maybe<size_t> stub2Index; if (!createTier2LazyEntryStubs(guard, *tier2CodePointer, &stub2Index)) { returnfalse;
}
// Initializing the code above will have flushed the icache for all cores. // However, there could still be stale data in the execution pipeline of // other cores on some platforms. Force an execution context flush on all // threads to fix this before we commit the code. // // This is safe due to the check in `PlatformCanTier` in WasmCompile.cpp
jit::FlushExecutionContextForAllThreads();
// Now that we can't fail or otherwise abort tier2, make it live. if (mode_ == CompileMode::EagerTiering) {
completeTier2_ = tier2CodePointer;
hasCompleteTier2_ = true;
// We don't need to update funcStates, because we're doing eager tiering
MOZ_ASSERT(!funcStates_.get());
} else { for (const CodeRange& cr : tier2CodePointer->codeRanges) { if (!cr.isFunction()) { continue;
}
FuncState& state =
funcStates_.get()[cr.funcIndex() - codeMeta_->numFuncImports];
state.bestTier = tier2CodePointer;
state.tierUpState = TierUpState::Finished;
}
}
// Update jump vectors with pointers to tier-2 lazy entry stubs, if any. if (stub2Index) { const CodeBlock& block = *guard->blocks[*stub2Index]; for (const CodeRange& cr : block.codeRanges) { if (!cr.isJitEntry()) { continue;
}
jumpTables_.setJitEntry(cr.funcIndex(), block.base() + cr.begin());
}
}
}
// And we update the jump vectors with pointers to tier-2 functions and eager // stubs. Callers will continue to invoke tier-1 code until, suddenly, they // will invoke tier-2 code. This is benign.
uint8_t* base = tier2CodePointer->base(); for (const CodeRange& cr : tier2CodePointer->codeRanges) { // These are racy writes that we just want to be visible, atomically, // eventually. All hardware we care about will do this right. But // we depend on the compiler not splitting the stores hidden inside the // set*Entry functions. if (cr.isFunction()) {
jumpTables_.setTieringEntry(cr.funcIndex(), base + cr.funcTierEntry());
} elseif (cr.isJitEntry()) {
jumpTables_.setJitEntry(cr.funcIndex(), base + cr.begin());
}
} returntrue;
}
bool Code::addCodeBlock(const WriteGuard& guard, UniqueCodeBlock block,
UniqueLinkData maybeLinkData) const { // Don't bother saving the link data if the block won't be serialized if (maybeLinkData && !block->isSerializable()) {
maybeLinkData = nullptr;
}
// If anything fails here, be careful to reset our state back so that we are // not in an inconsistent state. if (!blockPtr->initialize(*this, codeBlockIndex)) { returnfalse;
}
if (!blockMap_.insert(blockPtr)) { // We don't need to deinitialize the blockPtr, because that will be // automatically handled by its destructor. returnfalse;
}
// Try to allocate the code segment. if (SharedCodeSegment segment = tryAllocate()) { return segment;
}
// The allocation failed. Release the lock and try a last-ditch GC before // retrying, to avoid a mutex ordering violation between WasmCodeProtected // and GlobalHelperThreadState. if (!allowLastDitchGC || !OnLargeAllocationFailure) { return nullptr;
}
// In the case of tiering, RegisterCodeBlock() immediately makes this code // block live to access from other threads executing the containing // module. So only call once the CodeBlock is fully initialized. if (!RegisterCodeBlock(this)) { returnfalse;
}
// This bool is only used by the destructor which cannot be called racily // and so it is not a problem to mutate it after RegisterCodeBlock().
MOZ_ASSERT(!unregisterOnDestroy_);
unregisterOnDestroy_ = true;
// We only ever have ion or baseline spewers, and they correspond with our // code block kind.
MOZ_ASSERT(ionSpewers.empty() || baselineSpewers.empty());
MOZ_ASSERT_IF(kind == CodeBlockKind::BaselineTier, ionSpewers.empty());
MOZ_ASSERT_IF(kind == CodeBlockKind::OptimizedTier, baselineSpewers.empty()); bool hasSpewers = !ionSpewers.empty() || !baselineSpewers.empty();
// Save the collected Ion perf spewers with their IR/source information. for (FuncIonPerfSpewer& funcIonSpewer : ionSpewers) { const CodeRange& codeRange = this->codeRange(funcIonSpewer.funcIndex);
UniqueChars desc = DescribeCodeRangeForProfiler(
codeMeta, codeTailMeta, codeMetaForAsmJS, codeRange, kind); if (!desc) { return;
}
uintptr_t start = uintptr_t(base() + codeRange.begin());
uintptr_t size = codeRange.end() - codeRange.begin();
funcIonSpewer.spewer.saveWasmProfile(start, size, desc);
}
// Save the collected baseline perf spewers with their IR/source information. for (FuncBaselinePerfSpewer& funcBaselineSpewer : baselineSpewers) { const CodeRange& codeRange = this->codeRange(funcBaselineSpewer.funcIndex);
UniqueChars desc = DescribeCodeRangeForProfiler(
codeMeta, codeTailMeta, codeMetaForAsmJS, codeRange, kind); if (!desc) { return;
}
uintptr_t start = uintptr_t(base() + codeRange.begin());
uintptr_t size = codeRange.end() - codeRange.begin();
funcBaselineSpewer.spewer.saveProfile(start, size, desc);
}
// Save the rest of the code ranges. for (const CodeRange& codeRange : codeRanges) { if (!codeRange.hasFuncIndex()) { continue;
}
// Skip functions when they have corresponding spewers, as they will have // already handled the function. if (codeRange.isFunction() && hasSpewers) { continue;
}
const StackMap* CodeBlock::lookupStackMap(uint8_t* pc) const { // We need to subtract the offset from the beginning of the codeblock.
uint32_t offsetInCodeBlock = pc - base(); return stackMaps.lookup(offsetInCodeBlock);
}
// We find the first hit (there may be multiple) to obtain the innermost // handler, which is why we cannot binary search here. for (constauto& tryNote : tryNotes) { if (tryNote.offsetWithinTryBody(target)) { return &tryNote;
}
}
const CodeRangeUnwindInfo* CodeBlock::lookupUnwindInfo(void* pc) const {
uint32_t target = ((uint8_t*)pc) - base();
size_t match; const CodeRangeUnwindInfo* info = nullptr; if (BinarySearch(UnwindInfoPCOffset(codeRangeUnwindInfos), 0,
codeRangeUnwindInfos.length(), target, &match)) {
info = &codeRangeUnwindInfos[match];
} else { // Exact match is not found, using insertion point to get the previous // info entry; skip if info is outside of codeRangeUnwindInfos. if (match == 0) return nullptr; if (match == codeRangeUnwindInfos.length()) {
MOZ_ASSERT(
codeRangeUnwindInfos[codeRangeUnwindInfos.length() - 1].unwindHow() ==
CodeRangeUnwindInfo::Normal); return nullptr;
}
info = &codeRangeUnwindInfos[match - 1];
} return info->unwindHow() == CodeRangeUnwindInfo::Normal ? nullptr : info;
}
bool JumpTables::initialize(CompileMode mode, const CodeMetadata& codeMeta, const CodeBlock& sharedStubs, const CodeBlock& tier1) {
static_assert(JSScript::offsetOfJitCodeRaw() == 0, "wasm fast jit entry is at (void*) jit[funcIndex]");
mode_ = mode;
numFuncs_ = codeMeta.numFuncs();
if (mode_ != CompileMode::Once) {
tiering_ = TablePointer(js_pod_calloc<void*>(numFuncs_)); if (!tiering_) { returnfalse;
}
}
// The number of jit entries is overestimated, but it is simpler when // filling/looking up the jit entries and safe (worst case we'll crash // because of a null deref when trying to call the jit entry of an // unexported function).
jit_ = TablePointer(js_pod_calloc<void*>(numFuncs_)); if (!jit_) { returnfalse;
}
const CodeBlock& Code::completeTierCodeBlock(Tier tier) const { switch (tier) { case Tier::Baseline: if (completeTier1_->tier() == Tier::Baseline) {
MOZ_ASSERT(completeTier1_->initialized()); return *completeTier1_;
}
MOZ_CRASH("No code segment at this tier"); case Tier::Optimized: if (completeTier1_->tier() == Tier::Optimized) {
MOZ_ASSERT(completeTier1_->initialized()); return *completeTier1_;
} // It is incorrect to ask for the optimized tier without there being such // a tier and the tier having been committed. The guard here could // instead be `if (hasCompleteTier2_) ... ` but codeBlock(t) should not be // called in contexts where that test is necessary.
MOZ_RELEASE_ASSERT(hasCompleteTier2_);
MOZ_ASSERT(completeTier2_->initialized()); return *completeTier2_;
}
MOZ_CRASH();
}
void Code::clearLinkData() const { auto guard = data_.writeLock(); for (UniqueLinkData& linkData : guard->blocksLinkData) {
linkData = nullptr;
}
}
// When enabled, generate profiling labels for every name in funcNames_ that is // the name of some Function CodeRange. This involves malloc() so do it now // since, once we start sampling, we'll be in a signal-handing context where we // cannot malloc. void Code::ensureProfilingLabels(bool profilingEnabled) const { auto labels = profilingLabels_.lock();
if (!profilingEnabled) {
labels->clear(); return;
}
if (!labels->empty()) { return;
}
// Any tier will do, we only need tier-invariant data that are incidentally // stored with the code ranges. const CodeBlock& sharedStubsCodeBlock = sharedStubs(); const CodeBlock& tier1CodeBlock = completeTierCodeBlock(stableCompleteTier());
// Ignore any OOM failures, nothing we can do about it
(void)appendProfilingLabels(labels, sharedStubsCodeBlock);
(void)appendProfilingLabels(labels, tier1CodeBlock);
}
// Iterate over the Code Ranges and accumulate all pieces of code.
size_t code_size = 0; for (const CodeRange& codeRange : codeBlock.codeRanges) { if (!codeRange.isFunction()) { continue;
}
code_size += codeRange.end() - codeRange.begin();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.