/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// By this point, the jitcode global table should be empty.
MOZ_ASSERT_IF(jitcodeGlobalTable_, jitcodeGlobalTable_->empty());
js_delete(jitcodeGlobalTable_.ref());
if (!generateBaselineICFallbackCode(cx)) { returnfalse;
}
jitcodeGlobalTable_ = cx->new_<JitcodeGlobalTable>(); if (!jitcodeGlobalTable_) { returnfalse;
}
if (!JitOptions.disableJitHints) {
jitHintsMap_ = cx->new_<JitHintsMap>(); if (!jitHintsMap_) { returnfalse;
}
}
if (!GenerateBaselineInterpreter(cx, baselineInterpreter_)) { returnfalse;
}
// Initialize the jitCodeRaw of the Runtime's canonical SelfHostedLazyScript // to point to the interpreter trampoline.
cx->runtime()->selfHostedLazyScript.ref().jitCodeRaw_ =
interpreterStub().value;
// JitRuntime code stubs are shared across compartments and have to // be allocated in the atoms zone.
mozilla::Maybe<AutoAllocInAtomsZone> az; if (!cx->zone()->isAtomsZone()) {
az.emplace(cx);
}
debugTrapHandlers_[kind] = generateDebugTrapHandler(cx, kind); return debugTrapHandlers_[kind];
}
JitRuntime::IonCompileTaskList& JitRuntime::ionLazyLinkList(JSRuntime* rt) {
MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt), "Should only be mutated by the main thread."); return ionLazyLinkList_.ref();
}
void JitRuntime::ionLazyLinkListRemove(JSRuntime* rt,
jit::IonCompileTask* task) {
MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt), "Should only be mutated by the main thread.");
MOZ_ASSERT(rt == task->script()->runtimeFromMainThread());
MOZ_ASSERT(ionLazyLinkListSize_ > 0);
void JitRuntime::ionLazyLinkListAdd(JSRuntime* rt, jit::IonCompileTask* task) {
MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt), "Should only be mutated by the main thread.");
MOZ_ASSERT(rt == task->script()->runtimeFromMainThread());
ionLazyLinkList(rt).insertFront(task);
ionLazyLinkListSize_++;
}
// Record Ion compile time in glean. if (mozilla::TimeDuration compileTime = codegen->getCompilationTime()) {
cx->metrics().ION_COMPILE_TIME(compileTime);
}
void jit::LinkIonScript(JSContext* cx, HandleScript calleeScript) { // Get the pending IonCompileTask from the script.
MOZ_ASSERT(calleeScript->hasBaselineScript());
IonCompileTask* task =
calleeScript->baselineScript()->pendingIonCompileTask();
calleeScript->baselineScript()->removePendingIonCompileTask(cx->runtime(),
calleeScript);
// Remove from pending.
cx->runtime()->jitRuntime()->ionLazyLinkListRemove(cx->runtime(), task);
{
gc::AutoSuppressGC suppressGC(cx); if (!LinkBackgroundCodeGen(cx, task)) { // Silently ignore OOM during code generation. The assembly code // doesn't have code to handle it after linking happened. So it's // not OK to throw a catchable exception from there.
cx->clearPendingException();
}
}
/* static */ void JitRuntime::TraceAtomZoneRoots(JSTracer* trc) { // Shared stubs are allocated in the atoms zone, so do not iterate // them after the atoms heap after it has been "finished." if (trc->runtime()->atomsAreFinished()) { return;
}
Zone* zone = trc->runtime()->atomsZone(); for (auto i = zone->cellIterUnsafe<JitCode>(); !i.done(); i.next()) {
JitCode* code = i;
TraceRoot(trc, &code, "wrapper");
}
}
// Let the script warm up again before attempting another compile.
script->resetWarmUpCounterToDelayIonCompilation();
JitScript* jitScript = script->maybeJitScript(); if (!jitScript) { return;
}
auto addPendingInvalidation = [&invalid](const IonScriptKey& ionScriptKey) {
AutoEnterOOMUnsafeRegion oomUnsafe; if (!invalid.append(ionScriptKey)) { // BUG 1536159: For diagnostics, compute the size of the failed // allocation. This presumes the vector growth strategy is to double. This // is only used for crash reporting so not a problem if we get it wrong.
size_t allocSize = 2 * sizeof(IonScriptKey) * invalid.capacity();
oomUnsafe.crash(allocSize, "Could not update IonScriptKeyVector");
}
};
// Trigger invalidation of the IonScript. if (jitScript->hasIonScript()) {
IonScriptKey ionScriptKey(script, jitScript->ionScript()->compilationId());
addPendingInvalidation(ionScriptKey);
}
// Trigger invalidation of any callers inlining this script. auto* inlinedCompilations =
script->zone()->jitZone()->maybeInlinedCompilations(script); if (inlinedCompilations) { for (constauto& ionScriptKey : *inlinedCompilations) {
addPendingInvalidation(ionScriptKey);
}
script->zone()->jitZone()->removeInlinedCompilations(script);
}
}
IonScript* IonScriptKey::maybeIonScriptToInvalidate() const { // This must be called either on the main thread or when sweeping WeakCaches // off-thread.
MOZ_ASSERT(CurrentThreadIsMainThread() || CurrentThreadIsGCSweeping());
#ifdef DEBUG // Make sure this is not called under CodeGenerator::link (before the // corresponding IonScript is created). auto* jitZone = script_->zoneFromAnyThread()->jitZone();
MOZ_ASSERT_IF(jitZone->currentCompilationId(),
jitZone->currentCompilationId().ref() != id_); #endif
if (!script_->hasIonScript() ||
script_->ionScript()->compilationId() != id_) { return nullptr;
}
return script_->ionScript();
}
bool IonScriptKey::traceWeak(JSTracer* trc) { // Sweep the IonScriptKey if either the script is dead or the IonScript has // been invalidated.
if (!TraceManuallyBarrieredWeakEdge(trc, &script_, "IonScriptKey::script")) { returnfalse;
}
void JitZone::traceScriptTableRoots(JSTracer* trc) { // Trace the table used to hold interpreter entry code generated with // --emit-interpreter-entry. if (interpreterEntryMap) {
interpreterEntryMap->trace(trc);
}
}
void JitZone::finishScriptTableRoots() { // Clear out the interpreter entry map before the final gc. if (interpreterEntryMap) {
interpreterEntryMap->clear();
interpreterEntryMap.reset();
}
}
void JitCodeHeader::init(JitCode* jitCode) { // As long as JitCode isn't moveable, we can avoid tracing this and // mutating executable data.
MOZ_ASSERT(!gc::IsMovableKind(gc::AllocKind::JITCODE));
jitCode_ = jitCode;
}
template <AllowGC allowGC>
JitCode* JitCode::New(JSContext* cx, uint8_t* code, uint32_t totalSize,
uint32_t headerSize, ExecutablePool* pool,
CodeKind kind) {
uint32_t bufferSize = totalSize - headerSize;
JitCode* codeObj =
cx->newCell<JitCode, allowGC>(code, bufferSize, headerSize, pool, kind); if (!codeObj) { // The caller already allocated `totalSize` bytes of executable memory.
pool->release(totalSize, kind); return nullptr;
}
void JitCode::copyFrom(MacroAssembler& masm) { // Store the JitCode pointer in the JitCodeHeader so we can recover the // gcthing from relocation tables.
JitCodeHeader::FromExecutable(raw())->init(this);
void JitCode::traceChildren(JSTracer* trc) { // Note that we cannot mark invalidated scripts, since we've basically // corrupted the code stream by injecting bailouts. if (invalidated()) { return;
}
void JitCode::finalize(JS::GCContext* gcx) { // If this jitcode had a bytecode map, either the entry has been removed // from the table, or it has been detached (jitcode_ set to null) because // the profiler buffer still references it. #ifdef DEBUG
JSRuntime* rt = gcx->runtime(); if (hasBytecodeMap_) {
MOZ_ASSERT(rt->jitRuntime()->hasJitcodeGlobalTable()); auto* entry = rt->jitRuntime()->getJitcodeGlobalTable()->lookup(raw());
MOZ_ASSERT(!entry || !entry->hasJitcode());
} #endif
#ifdef MOZ_VTUNE
vtune::UnmarkCode(this); #endif
MOZ_ASSERT(pool_);
// With W^X JIT code, reprotecting memory for each JitCode instance is // slow, so we record the ranges and poison them later all at once. It's // safe to ignore OOM here, it just means we won't poison the code. if (gcx->appendJitPoisonRange(JitPoisonRange(pool_, raw() - headerSize_,
headerSize_ + bufferSize_))) {
pool_->addRef();
}
setHeaderPtr(nullptr);
// Verify the hardcoded sizes in header are accurate.
static_assert(SizeOf_OsiIndex == sizeof(OsiIndex), "IonScript has wrong size for OsiIndex");
static_assert(SizeOf_SafepointIndex == sizeof(SafepointIndex), "IonScript has wrong size for SafepointIndex");
// Update the codeRaw_ field in the ICs now that we know the code address. for (size_t i = 0; i < numICs(); i++) {
getICFromIndex(i).resetCodeRaw(this);
}
}
size_t minEntry = 0;
size_t maxEntry = numSafepointIndices() - 1;
uint32_t min = table[minEntry].displacement();
uint32_t max = table[maxEntry].displacement();
// Raise if the element is not in the list.
MOZ_RELEASE_ASSERT(min <= disp && disp <= max);
// Approximate the location of the FrameInfo.
size_t guess = (disp - min) * (maxEntry - minEntry) / (max - min) + minEntry;
uint32_t guessDisp = table[guess].displacement();
if (table[guess].displacement() == disp) { return &table[guess];
}
// Doing a linear scan from the guess should be more efficient in case of // small group which are equally distributed on the code. // // such as: <... ... ... ... . ... ...> if (guessDisp > disp) { while (--guess >= minEntry) {
guessDisp = table[guess].displacement();
MOZ_ASSERT(guessDisp >= disp); if (guessDisp == disp) { return &table[guess];
}
}
} else { while (++guess <= maxEntry) {
guessDisp = table[guess].displacement();
MOZ_ASSERT(guessDisp <= disp); if (guessDisp == disp) { return &table[guess];
}
}
}
MOZ_CRASH("displacement not found.");
}
const OsiIndex* IonScript::getOsiIndex(uint32_t disp) const { const OsiIndex* end = osiIndices() + numOsiIndices(); for (const OsiIndex* it = osiIndices(); it != end; ++it) { if (it->returnPointDisplacement() == disp) { return it;
}
}
MOZ_CRASH("Failed to find OSI point return address");
}
const OsiIndex* IonScript::getOsiIndex(uint8_t* retAddr) const {
JitSpew(JitSpew_IonInvalidate, "IonScript %p has method %p raw %p",
(void*)this, (void*)method(), method()->raw());
void IonScript::Destroy(JS::GCContext* gcx, IonScript* script) { // Destroy the HeapPtrs to ensure there are no pointers into the IonScript's // nursery objects list or constants list in the store buffer. Because this // can be called during sweeping when discarding JIT code, we have to lock the // store buffer when we find a pointer that's (still) in the nursery.
mozilla::Maybe<gc::AutoLockSweepingLock> lock; for (size_t i = 0, len = script->numNurseryObjects(); i < len; i++) {
JSObject* obj = script->nurseryObjects()[i]; if (lock.isNothing() && IsInsideNursery(obj)) {
lock.emplace(gcx->runtimeFromAnyThread());
}
script->nurseryObjects()[i].~HeapPtr<JSObject*>();
} for (size_t i = 0, len = script->numConstants(); i < len; i++) {
Value v = script->getConstant(i); if (lock.isNothing() && v.isGCThing() && IsInsideNursery(v.toGCThing())) {
lock.emplace(gcx->runtimeFromAnyThread());
}
script->getConstant(i).~HeapPtr<Value>();
}
// This allocation is tracked by JSScript::setIonScriptImpl.
gcx->deleteUntracked(script);
}
if (mir->shouldCancel("Fold Empty Blocks")) { returnfalse;
}
}
// Remove trivially dead resume point operands before folding tests, so the // latter pass can optimize more aggressively. if (!mir->compilingWasm()) { if (!EliminateTriviallyDeadResumePointOperands(mir, graph)) { returnfalse;
}
mir->spewPass("Eliminate trivially dead resume point operands");
AssertBasicGraphCoherency(graph);
if (mir->shouldCancel("Eliminate trivially dead resume point operands")) { returnfalse;
}
}
{ if (!FoldTests(graph)) { returnfalse;
}
mir->spewPass("Fold Tests");
AssertBasicGraphCoherency(graph);
if (mir->shouldCancel("Fold Tests")) { returnfalse;
}
}
{ if (!SplitCriticalEdges(graph)) { returnfalse;
}
mir->spewPass("Split Critical Edges");
AssertGraphCoherency(graph);
if (mir->shouldCancel("Split Critical Edges")) { returnfalse;
}
}
if (mir->shouldCancel("Renumber Blocks")) { returnfalse;
}
}
{ if (!BuildDominatorTree(mir, graph)) { returnfalse;
} // No spew: graph not changed.
if (mir->shouldCancel("Dominator Tree")) { returnfalse;
}
}
{ // Aggressive phi elimination must occur before any code elimination. If the // script contains a try-statement, we only compiled the try block and not // the catch or finally blocks, so in this case it's also invalid to use // aggressive phi elimination.
Observability observability = graph.hasTryBlock()
? ConservativeObservability
: AggressiveObservability; if (!EliminatePhis(mir, graph, observability)) { returnfalse;
}
mir->spewPass("Eliminate phis");
AssertGraphCoherency(graph);
if (mir->shouldCancel("Eliminate phis")) { returnfalse;
}
if (!BuildPhiReverseMapping(graph)) { returnfalse;
}
AssertExtendedGraphCoherency(graph); // No spew: graph not changed.
if (mir->shouldCancel("Phi reverse mapping")) { returnfalse;
}
}
if (!JitOptions.disableRecoverIns &&
mir->optimizationInfo().scalarReplacementEnabled() &&
!JitOptions.disableObjectKeysScalarReplacement) {
JitSpew(JitSpew_Escape, "\n"); if (!ReplaceObjectKeys(mir, graph)) { returnfalse;
}
mir->spewPass("Replace ObjectKeys");
AssertGraphCoherency(graph);
if (mir->shouldCancel("Replace ObjectKeys")) { returnfalse;
}
}
if (!mir->compilingWasm() && !JitOptions.disableIteratorIndices) { if (!OptimizeIteratorIndices(mir, graph)) { returnfalse;
}
mir->spewPass("Iterator Indices");
AssertGraphCoherency(graph);
if (mir->shouldCancel("Iterator Indices")) { returnfalse;
}
}
if (!JitOptions.disableRecoverIns &&
mir->optimizationInfo().scalarReplacementEnabled()) {
JitSpew(JitSpew_Escape, "\n"); if (!ScalarReplacement(mir, graph)) { returnfalse;
}
mir->spewPass("Scalar Replacement");
AssertGraphCoherency(graph);
if (mir->shouldCancel("Scalar Replacement")) { returnfalse;
}
}
if (!mir->compilingWasm()) { if (!ApplyTypeInformation(mir, graph)) { returnfalse;
}
mir->spewPass("Apply types");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Apply types")) { returnfalse;
}
}
if (mir->compilingWasm()) { if (!TrackWasmRefTypes(graph)) { returnfalse;
}
mir->spewPass("Track Wasm ref types");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Track Wasm ref types")) { returnfalse;
}
}
if (mir->optimizationInfo().amaEnabled()) {
AlignmentMaskAnalysis ama(graph); if (!ama.analyze()) { returnfalse;
}
mir->spewPass("Alignment Mask Analysis");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Alignment Mask Analysis")) { returnfalse;
}
}
ValueNumberer gvn(mir, graph);
// Alias analysis is required for LICM and GVN so that we don't move // loads across stores. We also use alias information when removing // redundant shapeguards. if (mir->optimizationInfo().licmEnabled() ||
mir->optimizationInfo().gvnEnabled() ||
mir->optimizationInfo().eliminateRedundantShapeGuardsEnabled()) {
{
AliasAnalysis analysis(mir, graph);
JitSpew(JitSpew_Alias, "\n"); if (!analysis.analyze()) { returnfalse;
}
if (mir->shouldCancel("Alias analysis")) { returnfalse;
}
}
if (!mir->compilingWasm()) { // Eliminating dead resume point operands requires basic block // instructions to be numbered. Reuse the numbering computed during // alias analysis. if (!EliminateDeadResumePointOperands(mir, graph)) { returnfalse;
}
mir->spewPass("Eliminate dead resume point operands");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Eliminate dead resume point operands")) { returnfalse;
}
}
}
if (mir->compilingWasm()) { if (!OptimizeWasmCasts(graph)) { returnfalse;
}
mir->spewPass("Optimize Wasm tests and casts");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Optimize Wasm tests and casts")) { returnfalse;
}
}
if (mir->optimizationInfo().gvnEnabled()) {
JitSpew(JitSpew_GVN, "\n"); if (!gvn.run(ValueNumberer::UpdateAliasAnalysis)) { returnfalse;
}
mir->spewPass("GVN");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("GVN")) { returnfalse;
}
}
if (mir->branchHintingEnabled()) {
JitSpew(JitSpew_BranchHint, "\n"); if (!BranchHinting(mir, graph)) { returnfalse;
}
mir->spewPass("BranchHinting");
AssertBasicGraphCoherency(graph);
if (mir->shouldCancel("BranchHinting")) { returnfalse;
}
}
// LICM can hoist instructions from conditional branches and // trigger bailouts. Disable it if bailing out of a hoisted // instruction has previously invalidated this script. if (mir->licmEnabled()) {
JitSpew(JitSpew_LICM, "\n"); if (!LICM(mir, graph)) { returnfalse;
}
mir->spewPass("LICM");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("LICM")) { returnfalse;
}
}
RangeAnalysis r(mir, graph); if (mir->optimizationInfo().rangeAnalysisEnabled()) {
JitSpew(JitSpew_Range, "\n"); if (!r.addBetaNodes()) { returnfalse;
}
mir->spewPass("Beta");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("RA Beta")) { returnfalse;
}
if (!r.analyze() || !r.addRangeAssertions()) { returnfalse;
}
mir->spewPass("Range Analysis");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Range Analysis")) { returnfalse;
}
if (!r.removeBetaNodes()) { returnfalse;
}
mir->spewPass("De-Beta");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("RA De-Beta")) { returnfalse;
}
if (mir->optimizationInfo().gvnEnabled()) { bool shouldRunUCE = false; if (!r.prepareForUCE(&shouldRunUCE)) { returnfalse;
}
mir->spewPass("RA check UCE");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("RA check UCE")) { returnfalse;
}
if (shouldRunUCE) { if (!gvn.run(ValueNumberer::DontUpdateAliasAnalysis)) { returnfalse;
}
mir->spewPass("UCE After RA");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("UCE After RA")) { returnfalse;
}
}
}
if (mir->optimizationInfo().autoTruncateEnabled()) { if (!r.truncate()) { returnfalse;
}
mir->spewPass("Truncate Doubles");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Truncate Doubles")) { returnfalse;
}
}
}
if (!JitOptions.disableRecoverIns) {
JitSpew(JitSpew_Sink, "\n"); if (!Sink(mir, graph)) { returnfalse;
}
mir->spewPass("Sink");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Sink")) { returnfalse;
}
}
if (!JitOptions.disableRecoverIns &&
mir->optimizationInfo().rangeAnalysisEnabled()) {
JitSpew(JitSpew_Range, "\n"); if (!r.removeUnnecessaryBitops()) { returnfalse;
}
mir->spewPass("Remove Unnecessary Bitops");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Remove Unnecessary Bitops")) { returnfalse;
}
}
{
JitSpew(JitSpew_FLAC, "\n"); if (!FoldLinearArithConstants(mir, graph)) { returnfalse;
}
mir->spewPass("Fold Linear Arithmetic Constants");
AssertBasicGraphCoherency(graph);
if (mir->shouldCancel("Fold Linear Arithmetic Constants")) { returnfalse;
}
}
// EAA, but only for wasm; it appears to be of minimal benefit for JS inputs. if (mir->compilingWasm() && mir->optimizationInfo().eaaEnabled()) {
EffectiveAddressAnalysis eaa(mir, graph);
JitSpew(JitSpew_EAA, "\n"); if (!eaa.analyze()) { returnfalse;
}
mir->spewPass("Effective Address Analysis");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Effective Address Analysis")) { returnfalse;
}
}
// BCE marks bounds checks as dead, so do BCE before DCE. if (mir->compilingWasm()) {
JitSpew(JitSpew_WasmBCE, "\n"); if (!EliminateBoundsChecks(mir, graph)) { returnfalse;
}
mir->spewPass("Redundant Bounds Check Elimination");
AssertGraphCoherency(graph);
if (mir->shouldCancel("BCE")) { returnfalse;
}
}
{ if (!EliminateDeadCode(mir, graph)) { returnfalse;
}
mir->spewPass("DCE");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("DCE")) { returnfalse;
}
}
if (!JitOptions.disableMarkLoadsUsedAsPropertyKeys && !mir->compilingWasm()) {
JitSpew(JitSpew_MarkLoadsUsedAsPropertyKeys, "\n"); if (!MarkLoadsUsedAsPropertyKeys(graph)) { returnfalse;
} if (mir->shouldCancel("MarkLoadsUsedAsPropertyKeys")) { returnfalse;
}
}
if (mir->optimizationInfo().instructionReorderingEnabled() &&
!mir->outerInfo().hadReorderingBailout()) { if (!ReorderInstructions(mir, graph)) { returnfalse;
}
mir->spewPass("Reordering");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Reordering")) { returnfalse;
}
}
// Make loops contiguous. We do this after GVN/UCE and range analysis, // which can remove CFG edges, exposing more blocks that can be moved.
{ if (!MakeLoopsContiguous(graph)) { returnfalse;
}
mir->spewPass("Make loops contiguous");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Make loops contiguous")) { returnfalse;
}
}
AssertExtendedGraphCoherency(graph, /* underValueNumberer = */ false, /* force = */ true);
// Unroll and/or peel loops if (mir->compilingWasm() && JS::Prefs::wasm_unroll_loops()) { bool loopsChanged; if (!UnrollLoops(mir, graph, &loopsChanged)) { returnfalse;
}
mir->spewPass("Unroll loops");
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Unroll loops")) { returnfalse;
}
if (loopsChanged) { // Rerun GVN in the hope that unrolling exposed more optimization // opportunities. if (!gvn.run(ValueNumberer::DontUpdateAliasAnalysis)) { returnfalse;
}
if (!EliminatePhis(mir, graph, ConservativeObservability)) { returnfalse;
}
AssertExtendedGraphCoherency(graph);
// And tidy up any empty blocks. bool blocksFolded; if (!FoldEmptyBlocks(graph, &blocksFolded)) { returnfalse;
} if (blocksFolded) { // Redo the dominator tree.
ClearDominatorTree(graph); if (!BuildDominatorTree(mir, graph)) { returnfalse;
}
}
AssertExtendedGraphCoherency(graph);
if (mir->shouldCancel("Rerun GVN after loop unrolling")) { returnfalse;
}
}
}
// Remove unreachable blocks created by MBasicBlock::NewFakeLoopPredecessor // to ensure every loop header has two predecessors. (This only happens due // to OSR.) After this point, it is no longer possible to build the // dominator tree. if (!mir->compilingWasm() && graph.osrBlock()) {
graph.removeFakeLoopPredecessors();
mir->spewPass("Remove fake loop predecessors");
AssertGraphCoherency(graph);
if (mir->shouldCancel("Remove fake loop predecessors")) { returnfalse;
}
}
// Passes after this point must not move instructions; these analyses // depend on knowing the final order in which instructions will execute.
if (mir->optimizationInfo().edgeCaseAnalysisEnabled()) {
EdgeCaseAnalysis edgeCaseAnalysis(mir, graph); if (!edgeCaseAnalysis.analyzeLate()) { returnfalse;
}
mir->spewPass("Edge Case Analysis (Late)");
AssertGraphCoherency(graph);
if (mir->shouldCancel("Edge Case Analysis (Late)")) { returnfalse;
}
}
if (mir->optimizationInfo().eliminateRedundantChecksEnabled()) { // Note: check elimination has to run after all other passes that move // instructions. Since check uses are replaced with the actual index, // code motion after this pass could incorrectly move a load or store // before its bounds check. if (!EliminateRedundantChecks(graph)) { returnfalse;
}
mir->spewPass("Bounds Check Elimination");
AssertGraphCoherency(graph);
if (mir->shouldCancel("Bounds Check Elimination")) { returnfalse;
}
}
if (mir->optimizationInfo().eliminateRedundantShapeGuardsEnabled()) { if (!EliminateRedundantShapeGuards(graph)) { returnfalse;
}
mir->spewPass("Shape Guard Elimination");
AssertGraphCoherency(graph);
if (mir->shouldCancel("Shape Guard Elimination")) { returnfalse;
}
}
// Run the GC Barrier Elimination pass after instruction reordering, to // ensure we don't move instructions that can trigger GC between stores we // optimize here. if (mir->optimizationInfo().eliminateRedundantGCBarriersEnabled()) { if (!EliminateRedundantGCBarriers(graph)) { returnfalse;
}
mir->spewPass("GC Barrier Elimination");
AssertGraphCoherency(graph);
if (mir->shouldCancel("GC Barrier Elimination")) { returnfalse;
}
}
if (!mir->compilingWasm() && !mir->outerInfo().hadUnboxFoldingBailout()) { if (!FoldLoadsWithUnbox(mir, graph)) { returnfalse;
}
mir->spewPass("FoldLoadsWithUnbox");
AssertGraphCoherency(graph);
if (mir->shouldCancel("FoldLoadsWithUnbox")) { returnfalse;
}
}
if (!mir->compilingWasm()) { if (!AddKeepAliveInstructions(graph)) { returnfalse;
}
mir->spewPass("Add KeepAlive Instructions");
AssertGraphCoherency(graph);
if (mir->shouldCancel("Add KeepAlive Instructions")) { returnfalse;
}
}
AssertGraphCoherency(graph, /* force = */ true);
if (JitSpewEnabled(JitSpew_MIRExpressions)) {
JitSpew(JitSpew_MIRExpressions, "\n");
AutoJitSpewMessage msg(JitSpew_MIRExpressions);
DumpMIRExpressions(msg.printer(), graph, mir->outerInfo(), "BeforeLIR (== result of OptimizeMIR)");
}
if (!codegen->generate(snapshot)) { return nullptr;
}
return codegen.release();
}
CodeGenerator* CompileBackEnd(MIRGenerator* mir, WarpSnapshot* snapshot) { // Everything in CompileBackEnd can potentially run on a helper thread.
AutoEnterIonBackend enter;
AutoSpewEndFunction spewEndFunction(mir);
mozilla::TimeStamp compileStartTime = mozilla::TimeStamp::Now();
UniquePtr<LifoAlloc> JitRuntime::tryReuseIonLifoAlloc() { // Try to reuse the LifoAlloc of a finished Ion compilation task for a new // Ion compilation. If there are multiple tasks, we pick the one with the // largest LifoAlloc.
for (size_t i = 0, len = batch.length(); i < len; i++) {
IonCompileTask* task = batch[i]; if (task->alloc().lifoAlloc()->isHuge()) { // Ignore 'huge' LifoAllocs. This avoids keeping a lot of memory alive and // also avoids freeing all LifoAlloc memory (instead of reusing it) in // freeAllIfHugeAndUnused. continue;
}
size_t taskSize = task->alloc().lifoAlloc()->computedSizeOfExcludingThis(); if (!bestTask || taskSize >= bestTaskSize) {
bestTask = task;
bestTaskIndex = i;
bestTaskSize = taskSize;
}
}
if (bestTask) {
batch.erase(&batch[bestTaskIndex]); return FreeIonCompileTaskAndReuseLifoAlloc(bestTask);
}
if (osrPc) {
script->jitScript()->setHadIonOSR();
}
AbortReasonOr<WarpSnapshot*> result = CreateWarpSnapshot(cx, mirGen, script); if (result.isErr()) { return result.unwrapErr();
}
WarpSnapshot* snapshot = result.unwrap();
// If possible, compile the script off thread. if (options.offThreadCompilationAvailable()) {
JitSpew(JitSpew_IonSyncLogs, "Can't log script %s:%u:%u" ". (Compiled on background thread.)",
script->filename(), script->lineno(),
script->column().oneOriginValue());
// The allocator and associated data will be destroyed after being // processed in the finishedOffThreadCompilations list.
(void)alloc.release();
clearDependencies.release();
return AbortReason::NoAbort;
}
bool succeeded = false;
{
gc::AutoSuppressGC suppressGC(cx);
JitContext jctx(cx);
UniquePtr<CodeGenerator> codegen(CompileBackEnd(mirGen, snapshot)); if (!codegen) {
JitSpew(JitSpew_IonAbort, "Failed during back-end compilation."); if (cx->isExceptionPending()) { return AbortReason::Error;
} return AbortReason::Disable;
}
// Baseline has the same limit for the number of actual arguments, so if we // entered Baseline we can also enter Ion.
MOZ_ASSERT_IF(frame->isFunctionFrame(),
!TooManyActualArguments(frame->numActualArgs()));
// The number of formal arguments is checked in CanIonCompileScript. The // Baseline JIT shouldn't attempt to tier up if that returns false.
MOZ_ASSERT_IF(frame->isFunctionFrame(),
!TooManyFormalArguments(frame->numFormalArgs()));
}
if (script->isForEval()) { // Eval frames are not yet supported. Fixing this will require adding // support for the eval frame's environment chain, also for bailouts. // Additionally, JSOp::GlobalOrEvalDeclInstantiation in WarpBuilder // currently doesn't support eval scripts. See bug 1996190.
JitSpew(JitSpew_IonAbort, "eval script");
script->disableIon(); returnfalse;
}
if (script->isAsync() && script->isModule()) { // Async modules are not supported (bug 1996189).
JitSpew(JitSpew_IonAbort, "async module");
script->disableIon(); returnfalse;
}
if (script->hasNonSyntacticScope() && !script->function()) { // Support functions with a non-syntactic global scope but not other // scripts. For global scripts, WarpBuilder currently uses the global // object as scope chain, and this is not valid when the script has a // non-syntactic global scope.
JitSpew(JitSpew_IonAbort, "has non-syntactic global scope");
script->disableIon(); returnfalse;
}
if (script->function() &&
TooManyFormalArguments(script->function()->nargs())) {
JitSpew(JitSpew_IonAbort, "too many formal arguments");
script->disableIon(); returnfalse;
}
if (ScriptIsTooLarge(cx, script)) {
script->disableIon(); returnfalse;
}
if (reason == AbortReason::Disable) { return Method_CantCompile;
}
if (reason == AbortReason::Alloc) {
ReportOutOfMemory(cx); return Method_Error;
}
// Compilation succeeded or we invalidated right away or an inlining/alloc // abort if (script->hasIonScript()) { return Method_Compiled;
} return Method_Skipped;
}
} // namespace jit
} // namespace js
bool jit::OffThreadCompilationAvailable(JSContext* cx) { // Even if off thread compilation is enabled, compilation must still occur // on the main thread in some cases. // // Require cpuCount > 1 so that Ion compilation jobs and active-thread // execution are not competing for the same resources. return cx->runtime()->canUseOffthreadIonCompilation() &&
GetHelperThreadCPUCount() > 1 && CanUseExtraThreads();
}
// Skip if the script has been disabled. if (!script->canIonCompile()) { return Method_Skipped;
}
// Skip if the script is being compiled off thread. if (script->isIonCompilingOffThread()) { return Method_Skipped;
}
if (state.isInvoke()) {
InvokeState& invoke = *state.asInvoke();
if (TooManyActualArguments(invoke.args().length())) {
JitSpew(JitSpew_IonAbort, "too many actual args");
ForbidCompilation(cx, script); return Method_CantCompile;
}
}
// If --ion-eager is used, compile with Baseline first, so that we // can directly enter IonMonkey. if (JitOptions.eagerIonCompilation() && !script->hasBaselineScript()) {
MethodStatus status =
CanEnterBaselineMethod<BaselineTier::Compiler>(cx, state); if (status != Method_Compiled) { return status;
} // Bytecode analysis may forbid compilation for a script. if (!script->canIonCompile()) { return Method_CantCompile;
}
}
if (!script->hasBaselineScript()) { return Method_Skipped;
}
if (script->baselineScript()->hasPendingIonCompileTask()) {
LinkIonScript(cx, script); if (script->hasIonScript()) { return Method_Compiled;
}
}
// Attempt compilation. Returns Method_Compiled if already compiled.
MethodStatus status = Compile(cx, script, frame, nullptr); if (status != Method_Compiled) { if (status == Method_CantCompile) {
ForbidCompilation(cx, script);
} return status;
}
return Method_Compiled;
}
// Decide if a transition from baseline execution to Ion code should occur. // May compile or recompile the target JSScript. static MethodStatus BaselineCanEnterAtBranch(JSContext* cx, HandleScript script,
BaselineFrame* osrFrame,
jsbytecode* pc) {
AssertBaselineFrameCanEnterIon(cx, osrFrame);
MOZ_ASSERT((JSOp)*pc == JSOp::LoopHead);
// Optionally ignore on user request. if (!JitOptions.osr) { return Method_Skipped;
}
// Check if the jitcode still needs to get linked and do this // to have a valid IonScript. if (script->baselineScript()->hasPendingIonCompileTask()) {
LinkIonScript(cx, script);
}
// By default a recompilation doesn't happen on osr mismatch. // Decide if we want to force a recompilation if this happens too much. if (script->hasIonScript()) { if (pc == script->ionScript()->osrPc()) { return Method_Compiled;
}
// Attempt compilation. // - Returns Method_Compiled if the right ionscript is present // (Meaning it was present or a sequantial compile finished) // - Returns Method_Skipped if pc doesn't match // (This means a background thread compilation with that pc could have // started or not.)
MethodStatus status = Compile(cx, script, osrFrame, pc); if (status != Method_Compiled) { if (status == Method_CantCompile) {
ForbidCompilation(cx, script);
} return status;
}
// Return the compilation was skipped when the osr pc wasn't adjusted. // This can happen when there was still an IonScript available and a // background compilation started, but hasn't finished yet. // Or when we didn't force a recompile. if (script->hasIonScript() && pc != script->ionScript()->osrPc()) { return Method_Skipped;
}
// The Baseline JIT code checks for Ion disabled or compiling off-thread.
MOZ_ASSERT(script->canIonCompile());
MOZ_ASSERT(!script->isIonCompilingOffThread());
// If Ion script exists, but PC is not at a loop entry, then Ion will be // entered for this script at an appropriate LOOPENTRY or the next time this // function is called. if (script->hasIonScript() && !isLoopHead) {
JitSpew(JitSpew_BaselineOSR, "IonScript exists, but not at loop entry!"); // TODO: ASSERT that a ion-script-already-exists checker stub doesn't exist. // TODO: Clear all optimized stubs. // TODO: Add a ion-script-already-exists checker stub. return true;
}
// Ensure that Ion-compiled code is available.
JitSpew(JitSpew_BaselineOSR, "WarmUpCounter for %s:%u:%u reached %d at pc %p, trying to switch to " "Ion!",
script->filename(), script->lineno(),
script->column().oneOriginValue(), (int)script->getWarmUpCount(),
(void*)pc);
MethodStatus stat; if (isLoopHead) {
JitSpew(JitSpew_BaselineOSR, " Compile at loop head!");
stat = BaselineCanEnterAtBranch(cx, script, frame, pc);
} elseif (frame->isFunctionFrame()) {
JitSpew(JitSpew_BaselineOSR, " Compile function from top for later entry!");
stat = BaselineCanEnterAtEntry(cx, script, frame);
} else { return true;
}
if (stat == Method_Error) {
JitSpew(JitSpew_BaselineOSR, " Compile with Ion errored!"); returnfalse;
}
IonOsrTempData* info = new (buf) IonOsrTempData();
info->jitcode = jitcode;
// Copy the BaselineFrame + local/stack Values to the buffer. Arguments and // |this| are not copied but left on the stack: the Baseline and Ion frame // share the same frame prefix and Ion won't clobber these values. Note // that info->baselineFrame will point to the *end* of the frame data, like // the frame pointer register in baseline frames.
uint8_t* frameStart =
(uint8_t*)info + AlignBytes(ionOsrTempDataSpace, sizeof(Value));
info->baselineFrame = frameStart + frameSpace;
// Prepare the temporary heap copy of the fake InterpreterFrame and actual // args list.
JitSpew(JitSpew_BaselineOSR, "Got jitcode. Preparing for OSR into ion.");
IonOsrTempData* info = PrepareOsrTempData(cx, frame, frameSize, jitcode); if (!info) { returnfalse;
}
#ifdef JS_JITSPEW switch (frame.type()) { case FrameType::Exit:
JitSpew(JitSpew_IonInvalidate, "#%zu exit frame @ %p", frameno,
frame.fp()); break; case FrameType::BaselineJS: case FrameType::IonJS: case FrameType::Bailout: {
MOZ_ASSERT(frame.isScripted()); constchar* type = "Unknown"; if (frame.isIonJS()) {
type = "Optimized";
} elseif (frame.isBaselineJS()) {
type = "Baseline";
} elseif (frame.isBailoutJS()) {
type = "Bailing";
}
JSScript* script = frame.maybeForwardedScript();
JitSpew(JitSpew_IonInvalidate, "#%zu %s JS frame @ %p, %s:%u:%u (fun: %p, script: %p, pc %p)",
frameno, type, frame.fp(), script->maybeForwardedFilename(),
script->lineno(), script->column().oneOriginValue(),
frame.maybeCallee(), script, frame.resumePCinCurrentFrame()); break;
} case FrameType::BaselineStub:
JitSpew(JitSpew_IonInvalidate, "#%zu baseline stub frame @ %p", frameno,
frame.fp()); break; case FrameType::BaselineInterpreterEntry:
JitSpew(JitSpew_IonInvalidate, "#%zu baseline interpreter entry frame @ %p", frameno,
frame.fp()); break; case FrameType::TrampolineNative:
JitSpew(JitSpew_IonInvalidate, "#%zu TrampolineNative frame @ %p",
frameno, frame.fp()); break; case FrameType::IonICCall:
JitSpew(JitSpew_IonInvalidate, "#%zu ion IC call frame @ %p", frameno,
frame.fp()); break; case FrameType::CppToJSJit:
JitSpew(JitSpew_IonInvalidate, "#%zu entry frame @ %p", frameno,
frame.fp()); break; case FrameType::WasmToJSJit:
JitSpew(JitSpew_IonInvalidate, "#%zu wasm frames @ %p", frameno,
frame.fp()); break;
} #endif// JS_JITSPEW
if (!frame.isIonScripted()) { continue;
}
// See if the frame has already been invalidated. if (frame.checkInvalidation()) { continue;
}
JSScript* script = frame.maybeForwardedScript(); if (!script->hasIonScript()) { continue;
}
if (!invalidateAll && !script->ionScript()->invalidated()) { continue;
}
IonScript* ionScript = script->ionScript();
// Purge ICs before we mark this script as invalidated. This will // prevent lastJump_ from appearing to be a bogus pointer, just // in case anyone tries to read it.
ionScript->purgeICs(script->zone());
// This frame needs to be invalidated. We do the following: // // 1. Increment the reference counter to keep the ionScript alive // for the invalidation bailout or for the exception handler. // 2. Determine safepoint that corresponds to the current call. // 3. From safepoint, get distance to the OSI-patchable offset. // 4. From the IonScript, determine the distance between the // call-patchable offset and the invalidation epilogue. // 5. Patch the OSI point with a call-relative to the // invalidation epilogue. // // The code generator ensures that there's enough space for us // to patch in a call-relative operation at each invalidation // point. // // Note: you can't simplify this mechanism to "just patch the // instruction immediately after the call" because things may // need to move into a well-defined register state (using move // instructions after the call) in to capture an appropriate // snapshot after the call occurs.
ionScript->incrementInvalidationCount();
JitCode* ionCode = ionScript->method();
// We're about to remove edges from the JSScript to GC things embedded in // the JitCode. Perform a barrier to let the GC know about those edges.
PreWriteBarrier(script->zone(), ionCode, [](JSTracer* trc, JitCode* code) {
code->traceChildren(trc);
});
ionCode->setInvalidated();
// Don't adjust OSI points in a bailout path. if (frame.isBailoutJS()) { continue;
}
// Write the delta (from the return address offset to the // IonScript pointer embedded into the invalidation epilogue) // where the safepointed call instruction used to be. We rely on // the call sequence causing the safepoint being >= the size of // a uint32, which is checked during safepoint index // construction.
AutoWritableJitCode awjc(ionCode); const SafepointIndex* si =
ionScript->getSafepointIndex(frame.resumePCinCurrentFrame());
CodeLocationLabel dataLabelToMunge(frame.resumePCinCurrentFrame());
ptrdiff_t delta = ionScript->invalidateEpilogueDataOffset() -
(frame.resumePCinCurrentFrame() - ionCode->raw());
Assembler::PatchWrite_Imm32(dataLabelToMunge, Imm32(delta));
void jit::InvalidateAll(JS::GCContext* gcx, Zone* zone) { // The caller should previously have cancelled off thread compilation.
MOZ_ASSERT(!HasOffThreadIonCompile(zone)); if (zone->isAtomsZone()) { return;
}
JSContext* cx = TlsContext.get(); for (JitActivationIterator iter(cx); !iter.done(); ++iter) { if (iter->compartment()->zone() == zone) {
JitSpew(JitSpew_IonInvalidate, "Invalidating all frames for GC");
InvalidateActivation(gcx, iter, true);
}
}
}
staticvoid ClearIonScriptAfterInvalidation(JSContext* cx, JSScript* script,
IonScript* ionScript, bool resetUses) { // Null out the JitScript's IonScript pointer. The caller is responsible for // destroying the IonScript using the invalidation count mechanism.
DebugOnly<IonScript*> clearedIonScript =
script->jitScript()->clearIonScript(cx->gcContext(), script);
MOZ_ASSERT(clearedIonScript == ionScript);
// Wait for the scripts to get warm again before doing another // compile, unless we are recompiling *because* a script got hot // (resetUses is false). if (resetUses) {
script->resetWarmUpCounterToDelayIonCompilation();
}
}
// Add an invalidation reference to all invalidated IonScripts to indicate // to the traversal which frames have been invalidated.
size_t numInvalidations = 0; for (constauto& ionScriptKey : invalid) {
JSScript* script = ionScriptKey.script(); if (cancelOffThread) {
CancelOffThreadIonCompile(script);
}
IonScript* ionScript = ionScriptKey.maybeIonScriptToInvalidate(); if (!ionScript) { continue;
}
// Keep the ion script alive during the invalidation and flag this // ionScript as being invalidated. This increment is removed by the // loop after the calls to InvalidateActivation.
ionScript->incrementInvalidationCount();
numInvalidations++;
}
if (!numInvalidations) {
JitSpew(JitSpew_IonInvalidate, " No IonScript invalidation."); return;
}
// Drop the references added above. If a script was never active, its // IonScript will be immediately destroyed. Otherwise, it will be held live // until its last invalidated frame is destroyed. for (constauto& ionScriptKey : invalid) {
IonScript* ionScript = ionScriptKey.maybeIonScriptToInvalidate(); if (!ionScript) { continue;
}
if (ionScript->invalidationCount() == 1) { // decrementInvalidationCount will destroy the IonScript so null out // jitScript->ionScript_ now. We don't want to do this unconditionally // because maybeIonScriptToInvalidate depends on script->ionScript() (we // would leak the IonScript if |invalid| contains duplicates).
ClearIonScriptAfterInvalidation(cx, ionScriptKey.script(), ionScript,
resetUses);
}
// Make sure we didn't leak references by invalidating the same IonScript // multiple times in the above loop.
MOZ_ASSERT(!numInvalidations);
// Finally, null out jitScript->ionScript_ for IonScripts that are still on // the stack. for (constauto& ionScriptKey : invalid) { if (IonScript* ionScript = ionScriptKey.maybeIonScriptToInvalidate()) {
ClearIonScriptAfterInvalidation(cx, ionScriptKey.script(), ionScript,
resetUses);
}
}
}
void jit::IonScript::invalidate(JSContext* cx, JSScript* script, bool resetUses, constchar* reason) { // Note: we could short circuit here if we already invalidated this // IonScript, but jit::Invalidate also cancels off-thread compilations of // |script|.
MOZ_RELEASE_ASSERT(invalidated() || script->ionScript() == this);
// IonScriptKeyVector has inline space for at least one element.
IonScriptKeyVector list;
MOZ_RELEASE_ASSERT(list.reserve(1));
list.infallibleEmplaceBack(script, compilationId());
// Ignore the event on allocation failure. if (buf) {
cx->runtime()->geckoProfiler().markEvent("Invalidate", buf.get());
}
}
// IonScriptKeyVector has inline space for at least one element.
IonScriptKeyVector scripts;
MOZ_ASSERT(script->hasIonScript());
MOZ_RELEASE_ASSERT(scripts.reserve(1));
scripts.infallibleEmplaceBack(script, script->ionScript()->compilationId());
// In all cases, null out jitScript->ionScript_ to avoid re-entry.
IonScript* ion = script->jitScript()->clearIonScript(gcx, script);
// If this script has Ion code on the stack, invalidated() will return // true. In this case we have to wait until destroying it. if (!ion->invalidated()) {
jit::IonScript::Destroy(gcx, ion);
}
}
void jit::ForbidCompilation(JSContext* cx, JSScript* script) {
JitSpew(JitSpew_IonAbort, "Disabling Ion compilation of script %s:%u:%u",
script->filename(), script->lineno(),
script->column().oneOriginValue());
CancelOffThreadIonCompile(script);
if (script->hasIonScript()) {
Invalidate(cx, script, false);
}
script->disableIon();
}
size_t jit::SizeOfIonData(JSScript* script,
mozilla::MallocSizeOf mallocSizeOf) {
size_t result = 0;
if (script->hasIonScript()) {
result += script->ionScript()->sizeOfIncludingThis(mallocSizeOf);
}
return result;
}
// If you change these, please also change the comment in TempAllocator. /* static */ const size_t TempAllocator::BallastSize = 16 * 1024; /* static */ const size_t TempAllocator::PreferredLifoChunkSize = 32 * 1024;
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.37 Sekunden
(vorverarbeitet am 2026-08-25)
¤
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.