diff --git a/js/src/jit-test/tests/ion/pgo-bug1259476.js b/js/src/jit-test/tests/ion/pgo-bug1259476.js new file mode 100644 index 000000000..be02f6898 --- /dev/null +++ b/js/src/jit-test/tests/ion/pgo-bug1259476.js @@ -0,0 +1,17 @@ +/* This is supposed to be jit-test --ion-pgo=on */ + +try { + x = evalcx(''); + x.__proto__ = 0; +} catch (e) {} +(function() { + for (var i = 0; i < 1; ++i) { + if (i % 5 == 0) { + for (let z of[0, 0, new Boolean(false), new Boolean(false), + new Boolean(false), new Boolean(false)]) { + this.x; + } + } + } +})() + diff --git a/js/src/jit/BacktrackingAllocator.cpp b/js/src/jit/BacktrackingAllocator.cpp index 85ddab248..14a35bea2 100644 --- a/js/src/jit/BacktrackingAllocator.cpp +++ b/js/src/jit/BacktrackingAllocator.cpp @@ -391,6 +391,9 @@ BacktrackingAllocator::init() LBlock* block = graph.getBlock(i); for (LInstructionIterator ins = block->begin(); ins != block->end(); ins++) { + if (mir->shouldCancel("Create data structures (inner loop 1)")) + return false; + for (size_t j = 0; j < ins->numDefs(); j++) { LDefinition* def = ins->getDef(j); if (def->isBogusTemp()) @@ -828,7 +831,7 @@ BacktrackingAllocator::go() return false; QueueItem item = allocationQueue.removeHighest(); - if (!processBundle(item.bundle)) + if (!processBundle(mir, item.bundle)) return false; } JitSpew(JitSpew_RegAlloc, "Main allocation loop complete"); @@ -1221,7 +1224,7 @@ BacktrackingAllocator::tryAllocateNonFixed(LiveBundle* bundle, } bool -BacktrackingAllocator::processBundle(LiveBundle* bundle) +BacktrackingAllocator::processBundle(MIRGenerator* mir, LiveBundle* bundle) { if (JitSpewEnabled(JitSpew_RegAlloc)) { JitSpew(JitSpew_RegAlloc, "Allocating %s [priority %lu] [weight %lu]", @@ -1256,6 +1259,9 @@ BacktrackingAllocator::processBundle(LiveBundle* bundle) bool fixed; LiveBundleVector conflicting; for (size_t attempt = 0;; attempt++) { + if (mir->shouldCancel("Backtracking Allocation (processBundle loop)")) + return false; + if (canAllocate) { bool success = false; fixed = false; @@ -1719,7 +1725,7 @@ BacktrackingAllocator::resolveControlFlow() for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { VirtualRegister& reg = vregs[i]; - if (mir->shouldCancel("Backtracking Resolve Control Flow (vreg loop)")) + if (mir->shouldCancel("Backtracking Resolve Control Flow (vreg outer loop)")) return false; if (!alloc().ensureBallast()) @@ -1728,6 +1734,9 @@ BacktrackingAllocator::resolveControlFlow() for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; ) { LiveRange* range = LiveRange::get(*iter); + if (mir->shouldCancel("Backtracking Resolve Control Flow (vreg inner loop)")) + return false; + // Remove ranges which will never be used. if (deadRange(range)) { reg.removeRangeAndIncrement(iter); diff --git a/js/src/jit/BacktrackingAllocator.h b/js/src/jit/BacktrackingAllocator.h index 20df6179c..8cff8e23d 100644 --- a/js/src/jit/BacktrackingAllocator.h +++ b/js/src/jit/BacktrackingAllocator.h @@ -663,7 +663,7 @@ class BacktrackingAllocator : protected RegisterAllocator bool* success, bool* pfixed, LiveBundleVector& conflicting); bool tryAllocateNonFixed(LiveBundle* bundle, Requirement requirement, Requirement hint, bool* success, bool* pfixed, LiveBundleVector& conflicting); - bool processBundle(LiveBundle* bundle); + bool processBundle(MIRGenerator* mir, LiveBundle* bundle); bool computeRequirement(LiveBundle* bundle, Requirement *prequirement, Requirement *phint); bool tryAllocateRegister(PhysicalRegister& r, LiveBundle* bundle, bool* success, bool* pfixed, LiveBundleVector& conflicting); diff --git a/js/src/jit/CompileInfo.h b/js/src/jit/CompileInfo.h index 72b480ddd..0a1699a8a 100644 --- a/js/src/jit/CompileInfo.h +++ b/js/src/jit/CompileInfo.h @@ -480,6 +480,7 @@ class CompileInfo // the frame is active on the stack. This implies that these definitions // would have to be executed and that they cannot be removed even if they // are unused. +#if(0) bool isObservableSlot(uint32_t slot) const { if (isObservableFrameSlot(slot)) return true; @@ -489,6 +490,17 @@ class CompileInfo return false; } +#else // bug 1342016 attachment 8849618 for our older code + inline bool isObservableSlot(uint32_t slot) const { + if (slot >= firstLocalSlot()) + return false; + + if (slot < firstArgSlot()) + return isObservableFrameSlot(slot); + + return isObservableArgumentSlot(slot); + } +#endif bool isObservableFrameSlot(uint32_t slot) const { if (!funMaybeLazy()) diff --git a/js/src/jit/EdgeCaseAnalysis.cpp b/js/src/jit/EdgeCaseAnalysis.cpp index 51a30fb03..b7d73973a 100644 --- a/js/src/jit/EdgeCaseAnalysis.cpp +++ b/js/src/jit/EdgeCaseAnalysis.cpp @@ -24,9 +24,10 @@ EdgeCaseAnalysis::analyzeLate() uint32_t nextId = 0; for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) { - if (mir->shouldCancel("Analyze Late (first loop)")) - return false; for (MDefinitionIterator iter(*block); iter; iter++) { + if (mir->shouldCancel("Analyze Late (first loop)")) + return false; + iter->setId(nextId++); iter->analyzeEdgeCasesForward(); } @@ -34,10 +35,12 @@ EdgeCaseAnalysis::analyzeLate() } for (PostorderIterator block(graph.poBegin()); block != graph.poEnd(); block++) { - if (mir->shouldCancel("Analyze Late (second loop)")) - return false; - for (MInstructionReverseIterator riter(block->rbegin()); riter != block->rend(); riter++) + for (MInstructionReverseIterator riter(block->rbegin()); riter != block->rend(); riter++) { + if (mir->shouldCancel("Analyze Late (second loop)")) + return false; + riter->analyzeEdgeCasesBackward(); + } } return true; diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index c741248ff..caf8a3602 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -1506,17 +1506,20 @@ OptimizeMIR(MIRGenerator* mir) else logger = TraceLoggerForCurrentThread(); + if (mir->shouldCancel("Start")) + return false; + if (!mir->compilingAsmJS()) { - if (!MakeMRegExpHoistable(graph)) + if (!MakeMRegExpHoistable(mir, graph)) + return false; + + if (mir->shouldCancel("Make MRegExp Hoistable")) return false; } gs.spewPass("BuildSSA"); AssertBasicGraphCoherency(graph); - if (mir->shouldCancel("Start")) - return false; - if (!JitOptions.disablePgo && !mir->compilingAsmJS()) { AutoTraceLog log(logger, TraceLogger_PruneUnusedBranches); if (!PruneUnusedBranches(mir, graph)) diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 789565e5f..0fe5b38f8 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -30,7 +30,8 @@ using mozilla::DebugOnly; typedef Vector MPhiVector; static bool -FlagPhiInputsAsHavingRemovedUses(MBasicBlock* block, MBasicBlock* succ, MPhiVector& worklist) +FlagPhiInputsAsHavingRemovedUses(MIRGenerator* mir, MBasicBlock* block, MBasicBlock* succ, + MPhiVector& worklist) { // When removing an edge between 2 blocks, we might remove the ability of // later phases to figure out that the uses of a Phi should be considered as @@ -103,6 +104,9 @@ FlagPhiInputsAsHavingRemovedUses(MBasicBlock* block, MBasicBlock* succ, MPhiVect for (; it != end; it++) { MPhi* phi = *it; + if (mir->shouldCancel("FlagPhiInputsAsHavingRemovedUses outer loop")) + return false; + // We are looking to mark the Phi inputs which are used across the edge // between the |block| and its successor |succ|. MDefinition* def = phi->getOperand(predIndex); @@ -120,9 +124,23 @@ FlagPhiInputsAsHavingRemovedUses(MBasicBlock* block, MBasicBlock* succ, MPhiVect bool isUsed = false; for (size_t idx = 0; !isUsed && idx < worklist.length(); idx++) { phi = worklist[idx]; + + if (mir->shouldCancel("FlagPhiInputsAsHavingRemovedUses inner loop 1")) + return false; + + if (phi->isUseRemoved() || phi->isImplicitlyUsed()) { + // The phi is implicitly used. + isUsed = true; + break; + } + MUseIterator usesEnd(phi->usesEnd()); for (MUseIterator use(phi->usesBegin()); use != usesEnd; use++) { MNode* consumer = (*use)->consumer(); + + if (mir->shouldCancel("FlagPhiInputsAsHavingRemovedUses inner loop 2")) + return false; + if (consumer->isResumePoint()) { MResumePoint* rp = consumer->toResumePoint(); if (rp->isObservableOperand(*use)) { @@ -144,12 +162,6 @@ FlagPhiInputsAsHavingRemovedUses(MBasicBlock* block, MBasicBlock* succ, MPhiVect if (phi->isInWorklist()) continue; - if (phi->isUseRemoved() || phi->isImplicitlyUsed()) { - // The phi is implicitly used. - isUsed = true; - break; - } - phi->setInWorklist(); if (!worklist.append(phi)) return false; @@ -177,11 +189,16 @@ FlagPhiInputsAsHavingRemovedUses(MBasicBlock* block, MBasicBlock* succ, MPhiVect } static bool -FlagAllOperandsAsHavingRemovedUses(MBasicBlock* block) +FlagAllOperandsAsHavingRemovedUses(MIRGenerator* mir, MBasicBlock* block) { + const CompileInfo& info = block->info(); + // Flag all instructions operands as having removed uses. MInstructionIterator end = block->end(); for (MInstructionIterator it = block->begin(); it != end; it++) { + if (mir->shouldCancel("FlagAllOperandsAsHavingRemovedUses loop 1")) + return false; + MInstruction* ins = *it; for (size_t i = 0, e = ins->numOperands(); i < e; i++) ins->getOperand(i)->setUseRemovedUnchecked(); @@ -191,9 +208,17 @@ FlagAllOperandsAsHavingRemovedUses(MBasicBlock* block) // Note: no need to iterate over the caller's of the resume point as // this is the same as the entry resume point. for (size_t i = 0, e = rp->numOperands(); i < e; i++) { +#if(0) // bug 1329901, bug 1342016 + if (mir->shouldCancel("FlagAllOperandsAsHavingRemovedUses inner loop")) + return false; + if (!rp->isObservableOperand(i)) continue; rp->getOperand(i)->setUseRemovedUnchecked(); +#else + if (info.isObservableSlot(i)) + rp->getOperand(i)->setUseRemovedUnchecked(); +#endif } } } @@ -201,10 +226,18 @@ FlagAllOperandsAsHavingRemovedUses(MBasicBlock* block) // Flag observable operands of the entry resume point as having removed uses. MResumePoint* rp = block->entryResumePoint(); while (rp) { + if (mir->shouldCancel("FlagAllOperandsAsHavingRemovedUses loop 2")) + return false; + for (size_t i = 0, e = rp->numOperands(); i < e; i++) { +#if(0) // bug 1329901, bug 1342016 if (!rp->isObservableOperand(i)) continue; rp->getOperand(i)->setUseRemovedUnchecked(); +#else + if (info.isObservableSlot(i)) + rp->getOperand(i)->setUseRemovedUnchecked(); +#endif } rp = rp->caller(); } @@ -212,7 +245,10 @@ FlagAllOperandsAsHavingRemovedUses(MBasicBlock* block) // Flag Phi inputs of the successors has having removed uses. MPhiVector worklist; for (size_t i = 0, e = block->numSuccessors(); i < e; i++) { - if (!FlagPhiInputsAsHavingRemovedUses(block, block->getSuccessor(i), worklist)) + if (mir->shouldCancel("FlagAllOperandsAsHavingRemovedUses loop 3")) + return false; + + if (!FlagPhiInputsAsHavingRemovedUses(mir, block, block->getSuccessor(i), worklist)) return false; } @@ -261,6 +297,9 @@ jit::PruneUnusedBranches(MIRGenerator* mir, MIRGraph& graph) // unreachable if all predecessors are flagged as bailing or unreachable. bool someUnreachable = false; for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) { + if (mir->shouldCancel("Prune unused branches (main loop)")) + return false; + JitSpew(JitSpew_Prune, "Investigate Block %d:", block->id()); // Do not touch entry basic blocks. @@ -274,6 +313,9 @@ jit::PruneUnusedBranches(MIRGenerator* mir, MIRGraph& graph) size_t numPred = block->numPredecessors(); size_t i = 0; for (; i < numPred; i++) { + if (mir->shouldCancel("Prune unused branches (inner loop 1)")) + return false; + MBasicBlock* pred = block->getPredecessor(i); // The backedge is visited after the loop header, but if the loop @@ -304,6 +346,9 @@ jit::PruneUnusedBranches(MIRGenerator* mir, MIRGraph& graph) size_t predCount = 0; bool isLoopExit = false; while (p--) { + if (mir->shouldCancel("Prune unused branches (inner loop 2)")) + return false; + MBasicBlock* pred = block->getPredecessor(p); if (pred->getHitState() == MBasicBlock::HitState::Count) predCount += pred->getHitCount(); @@ -369,16 +414,22 @@ jit::PruneUnusedBranches(MIRGenerator* mir, MIRGraph& graph) // As we are going to remove edges and basic block, we have to mark // instructions which would be needed by baseline if we were to bailout. for (PostorderIterator it(graph.poBegin()); it != graph.poEnd();) { + if (mir->shouldCancel("Prune unused branches (marking loop)")) + return false; + MBasicBlock* block = *it++; if (!block->isMarked() && !block->unreachable()) continue; - FlagAllOperandsAsHavingRemovedUses(block); + FlagAllOperandsAsHavingRemovedUses(mir, block); } // Remove the blocks in post-order such that consumers are visited before // the predecessors, the only exception being the Phi nodes of loop headers. for (PostorderIterator it(graph.poBegin()); it != graph.poEnd();) { + if (mir->shouldCancel("Prune unused branches (removal loop)")) + return false; + MBasicBlock* block = *it++; if (!block->isMarked() && !block->unreachable()) continue; @@ -1071,13 +1122,13 @@ jit::EliminatePhis(MIRGenerator* mir, MIRGraph& graph, // Add all observable phis to a worklist. We use the "in worklist" bit to // mean "this phi is live". for (PostorderIterator block = graph.poBegin(); block != graph.poEnd(); block++) { - if (mir->shouldCancel("Eliminate Phis (populate loop)")) - return false; - MPhiIterator iter = block->phisBegin(); while (iter != block->phisEnd()) { MPhi* phi = *iter++; + if (mir->shouldCancel("Eliminate Phis (populate loop)")) + return false; + // Flag all as unused, only observable phis would be marked as used // when processed by the work list. phi->setUnused(); @@ -1357,6 +1408,9 @@ TypeAnalyzer::specializePhis() return false; for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd(); phi++) { + if (mir->shouldCancel("Specialize Phis (inner loop)")) + return false; + bool hasInputsWithEmptyTypes; MIRType type = GuessPhiType(*phi, &hasInputsWithEmptyTypes); phi->specialize(type); @@ -1739,10 +1793,10 @@ bool TypeAnalyzer::graphContainsFloat32() { for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); ++block) { - if (mir->shouldCancel("Ensure Float32 commutativity - Graph contains Float32")) - return false; - for (MDefinitionIterator def(*block); def; def++) { + if (mir->shouldCancel("Ensure Float32 commutativity - Graph contains Float32")) + return false; + if (def->type() == MIRType_Float32) return true; } @@ -1822,7 +1876,7 @@ jit::ApplyTypeInformation(MIRGenerator* mir, MIRGraph& graph) } bool -jit::MakeMRegExpHoistable(MIRGraph& graph) +jit::MakeMRegExpHoistable(MIRGenerator* mir, MIRGraph& graph) { // If we are compiling try blocks, regular expressions may be observable // from catch blocks (which Ion does not compile). For now just disable the @@ -1831,7 +1885,13 @@ jit::MakeMRegExpHoistable(MIRGraph& graph) return true; for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) { + if (mir->shouldCancel("MakeMRegExpHoistable outer loop")) + return false; + for (MDefinitionIterator iter(*block); iter; iter++) { + if (mir->shouldCancel("MakeMRegExpHoistable inner loop")) + return false; + if (!iter->isRegExp()) continue; @@ -1840,6 +1900,9 @@ jit::MakeMRegExpHoistable(MIRGraph& graph) // Test if MRegExp is hoistable by looking at all uses. bool hoistable = true; for (MUseIterator i = regexp->usesBegin(); i != regexp->usesEnd(); i++) { + if (mir->shouldCancel("IsRegExpHoistable inner loop")) + return false; + // Ignore resume points. At this point all uses are listed. // No DCE or GVN or something has happened. if (i->consumer()->isResumePoint()) @@ -1939,7 +2002,7 @@ jit::RemoveUnmarkedBlocks(MIRGenerator* mir, MIRGraph& graph, uint32_t numMarked if (!block->isMarked()) continue; - FlagAllOperandsAsHavingRemovedUses(block); + FlagAllOperandsAsHavingRemovedUses(mir, block); } // Find unmarked blocks and remove them. diff --git a/js/src/jit/IonAnalysis.h b/js/src/jit/IonAnalysis.h index ae8eb6f20..c7726d269 100644 --- a/js/src/jit/IonAnalysis.h +++ b/js/src/jit/IonAnalysis.h @@ -54,7 +54,7 @@ bool ApplyTypeInformation(MIRGenerator* mir, MIRGraph& graph); bool -MakeMRegExpHoistable(MIRGraph& graph); +MakeMRegExpHoistable(MIRGenerator* mir, MIRGraph& graph); bool RenumberBlocks(MIRGraph& graph); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 2ef59b496..615766b0e 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -158,7 +158,9 @@ IonBuilder::IonBuilder(JSContext* analysisContext, CompileCompartment* comp, failedShapeGuard_(info->script()->failedShapeGuard()), failedLexicalCheck_(info->script()->failedLexicalCheck()), nonStringIteration_(false), - lazyArguments_(nullptr), +#ifdef DEBUG + hasLazyArguments_(false), +#endif inlineCallInfo_(nullptr), maybeFallbackFunctionGetter_(nullptr) { @@ -908,11 +910,11 @@ IonBuilder::build() ins->setResumePoint(entryRpCopy); } +#ifdef DEBUG // lazyArguments should never be accessed in |argsObjAliasesFormals| scripts. - if (info().hasArguments() && !info().argsObjAliasesFormals()) { - lazyArguments_ = MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS)); - current->add(lazyArguments_); - } + if (info().hasArguments() && !info().argsObjAliasesFormals()) + hasLazyArguments_ = true; +#endif insertRecompileCheck(); @@ -1080,10 +1082,10 @@ IonBuilder::buildInline(IonBuilder* callerBuilder, MResumePoint* callerResumePoi // +2 for the scope chain and |this|, maybe another +1 for arguments object slot. MOZ_ASSERT(current->entryResumePoint()->stackDepth() == info().totalSlots()); - if (script_->argumentsHasVarBinding()) { - lazyArguments_ = MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS)); - current->add(lazyArguments_); - } +#ifdef DEBUG + if (script_->argumentsHasVarBinding()) + hasLazyArguments_ = true; +#endif insertRecompileCheck(); @@ -1349,10 +1351,14 @@ IonBuilder::addOsrValueTypeBarrier(uint32_t slot, MInstruction** def_, } case MIRType_MagicOptimizedArguments: - MOZ_ASSERT(lazyArguments_); - osrBlock->rewriteSlot(slot, lazyArguments_); - def = lazyArguments_; + { + MOZ_ASSERT(hasLazyArguments_); + MConstant* lazyArg = MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS)); + osrBlock->insertBefore(osrBlock->lastIns(), lazyArg); + osrBlock->rewriteSlot(slot, lazyArg); + def = lazyArg; break; + } default: break; @@ -10195,8 +10201,11 @@ IonBuilder::jsop_arguments() current->push(current->argumentsObject()); return true; } - MOZ_ASSERT(lazyArguments_); - current->push(lazyArguments_); + + MOZ_ASSERT(hasLazyArguments_); + MConstant* lazyArg = MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS)); + current->add(lazyArg); + current->push(lazyArg); return true; } diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 534a0d427..a41b3ae01 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -1196,9 +1196,10 @@ class IonBuilder // Has an iterator other than 'for in'. bool nonStringIteration_; - // If this script can use a lazy arguments object, it will be pre-created - // here. - MInstruction* lazyArguments_; +#ifdef DEBUG + // If this script uses the lazy arguments object. + bool hasLazyArguments_; +#endif // If this is an inline builder, the call info for the builder. const CallInfo* inlineCallInfo_;