/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "jit/IonAnalysis.h" #include "mozilla/SizePrintfMacros.h" #include "jit/AliasAnalysis.h" #include "jit/BaselineInspector.h" #include "jit/BaselineJIT.h" #include "jit/FlowAliasAnalysis.h" #include "jit/Ion.h" #include "jit/IonBuilder.h" #include "jit/IonOptimizationLevels.h" #include "jit/LIR.h" #include "jit/Lowering.h" #include "jit/MIRGraph.h" #include "vm/RegExpObject.h" #include "vm/SelfHosting.h" #include "jsobjinlines.h" #include "jsopcodeinlines.h" #include "jsscriptinlines.h" #include "jit/shared/Lowering-shared-inl.h" using namespace js; using namespace js::jit; using mozilla::DebugOnly; typedef Vector MPhiVector; static bool 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 // a use of all its inputs. Thus we need to mark the Phi inputs as having // removed uses iff the phi has any uses. // // // +--------------------+ +---------------------+ // |12 MFoo 6 | |32 MBar 5 | // | | | | // | ... | | ... | // | | | | // |25 MGoto Block 4 | |43 MGoto Block 4 | // +--------------------+ +---------------------+ // | | // | | | // | | | // | +-----X------------------------+ // | Edge | // | Removed | // | | // | +------------v-----------+ // | |50 MPhi 12 32 | // | | | // | | ... | // | | | // | |70 MReturn 50 | // | +------------------------+ // | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // | // v // // ^ +--------------------+ +---------------------+ // /!\ |12 MConst opt-out | |32 MBar 5 | // '---' | | | | // | ... | | ... | // |78 MBail | | | // |80 MUnreachable | |43 MGoto Block 4 | // +--------------------+ +---------------------+ // | // | // | // +---------------+ // | // | // | // +------------v-----------+ // |50 MPhi 32 | // | | // | ... | // | | // |70 MReturn 50 | // +------------------------+ // // // If the inputs of the Phi are not flagged as having removed uses, then // later compilation phase might optimize them out. The problem is that a // bailout will use this value and give it back to baseline, which will then // use the OptimizedOut magic value in a computation. // Conservative upper limit for the number of Phi instructions which are // visited while looking for uses. const size_t conservativeUsesLimit = 128; MOZ_ASSERT(worklist.empty()); size_t predIndex = succ->getPredecessorIndex(block); MPhiIterator end = succ->phisEnd(); MPhiIterator it = succ->phisBegin(); 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); if (def->isUseRemoved()) continue; phi->setInWorklist(); if (!worklist.append(phi)) return false; // Fill the work list with all the Phi nodes uses until we reach either: // - A resume point which uses the Phi as an observable operand. // - An explicit use of the Phi instruction. // - An implicit use of the Phi instruction. 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)) { // The phi is observable via a resume point operand. isUsed = true; break; } continue; } MDefinition* cdef = consumer->toDefinition(); if (!cdef->isPhi()) { // The phi is explicitly used. isUsed = true; break; } phi = cdef->toPhi(); if (phi->isInWorklist()) continue; phi->setInWorklist(); if (!worklist.append(phi)) return false; } // Use a conservative upper bound to avoid iterating too many times // on very large graphs. if (idx >= conservativeUsesLimit) { isUsed = true; break; } } if (isUsed) def->setUseRemoved(); // Remove all the InWorklist flags. while (!worklist.empty()) { phi = worklist.popCopy(); phi->setNotInWorklist(); } } return true; } static bool FlagAllOperandsAsHavingRemovedUses(MIRGenerator* mir, MBasicBlock* block) { // 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(); // Flag observable resume point operands as having removed uses. if (MResumePoint* rp = ins->resumePoint()) { // 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 (mir->shouldCancel("FlagAllOperandsAsHavingRemovedUses inner loop")) return false; if (!rp->isObservableOperand(i)) continue; rp->getOperand(i)->setUseRemovedUnchecked(); } } } // 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 (!rp->isObservableOperand(i)) continue; rp->getOperand(i)->setUseRemovedUnchecked(); } rp = rp->caller(); } // Flag Phi inputs of the successors has having removed uses. MPhiVector worklist; for (size_t i = 0, e = block->numSuccessors(); i < e; i++) { if (mir->shouldCancel("FlagAllOperandsAsHavingRemovedUses loop 3")) return false; if (!FlagPhiInputsAsHavingRemovedUses(mir, block, block->getSuccessor(i), worklist)) return false; } return true; } static void RemoveFromSuccessors(MBasicBlock* block) { // Remove this block from its successors. size_t numSucc = block->numSuccessors(); while (numSucc--) { MBasicBlock* succ = block->getSuccessor(numSucc); if (succ->isDead()) continue; JitSpew(JitSpew_Prune, "Remove block edge %d -> %d.", block->id(), succ->id()); succ->removePredecessor(block); } } static void ConvertToBailingBlock(TempAllocator& alloc, MBasicBlock* block) { // Add a bailout instruction. MBail* bail = MBail::New(alloc, Bailout_FirstExecution); MInstruction* bailPoint = block->safeInsertTop(); block->insertBefore(block->safeInsertTop(), bail); // Discard all remaining instructions. MInstructionIterator clearStart = block->begin(bailPoint); block->discardAllInstructionsStartingAt(clearStart); if (block->outerResumePoint()) block->clearOuterResumePoint(); // And replace the last instruction by the unreachable control instruction. block->end(MUnreachable::New(alloc)); } bool jit::PruneUnusedBranches(MIRGenerator* mir, MIRGraph& graph) { MOZ_ASSERT(!mir->compilingWasm(), "wasm compilation has no code coverage support."); // We do a reverse-post-order traversal, marking basic blocks when the block // have to be converted into bailing blocks, and flagging block as // 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()); JitSpewIndent indent(JitSpew_Prune); // Do not touch entry basic blocks. if (*block == graph.osrBlock() || *block == graph.entryBlock()) { JitSpew(JitSpew_Prune, "Block %d is an entry point.", block->id()); continue; } // Compute if all the predecessors of this block are either bailling out // or are already flagged as unreachable. bool isUnreachable = true; bool isLoopHeader = block->isLoopHeader(); 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 // header is unreachable, then we can assume that the backedge would // be unreachable too. if (isLoopHeader && pred == block->backedge()) continue; // Break if any of the predecessor can continue in this block. if (!pred->isMarked() && !pred->unreachable()) { isUnreachable = false; break; } } // Compute if the block should bailout, based on the trivial heuristic // which is that if the block never got visited before, then it is // likely to not be visited after. bool shouldBailout = block->getHitState() == MBasicBlock::HitState::Count && block->getHitCount() == 0; // Check if the predecessors got accessed a large number of times in // comparisons of the current block, in order to know if our attempt at // removing this block is not premature. if (!isUnreachable && shouldBailout) { size_t p = numPred; size_t predCount = 0; size_t numSuccessorsOfPreds = 1; 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(); isLoopExit |= pred->isLoopHeader() && pred->backedge() != *block; numSuccessorsOfPreds += pred->numSuccessors() - 1; } // Iterate over the approximated set of dominated blocks and count // the number of instructions which are dominated. Note that this // approximation has issues with OSR blocks, but this should not be // a big deal. size_t numDominatedInst = 0; size_t numEffectfulInst = 0; int numInOutEdges = block->numPredecessors(); size_t branchSpan = 0; ReversePostorderIterator it(block); do { if (mir->shouldCancel("Prune unused branches (inner loop 3)")) return false; // Iterate over dominated blocks, and visit exit blocks as well. numInOutEdges -= it->numPredecessors(); if (numInOutEdges < 0) break; numInOutEdges += it->numSuccessors(); // Collect information about the instructions within the block. for (MDefinitionIterator def(*it); def; def++) { numDominatedInst++; if (def->isEffectful()) numEffectfulInst++; } it++; branchSpan++; } while(numInOutEdges > 0 && it != graph.rpoEnd()); // The goal of branch pruning is to remove branches which are // preventing other optimization, while keeping branches which would // be costly if we were to bailout. The following heuristics are // made to prevent bailouts in branches when we estimate that the // confidence is not enough to compensate for the cost of a bailout. // // 1. Confidence for removal varies with the number of hit counts // of the predecessor. The reason being that the likelyhood of // taking this branch is decreasing with the number of hit // counts of the predecessor. // // 2. Confidence for removal varies with the number of dominated // instructions. The reason being that the complexity of the // branch increases with the number of instructions, thus // working against other optimizations. // // 3. Confidence for removal varies with the span of the // branch. The reason being that a branch that spans over a // large set of blocks is likely to remove optimization // opportunity as it prevents instructions from the other // branches to dominate the blocks which are after. // // 4. Confidence for removal varies with the number of effectful // instructions. The reason being that an effectful instruction // can remove optimization opportunities based on Scalar // Replacement, and based on Alias Analysis. // // The following converts various units in some form of arbitrary // score, such that we can compare it to a threshold. size_t score = 0; MOZ_ASSERT(numSuccessorsOfPreds >= 1); score += predCount * JitOptions.branchPruningHitCountFactor / numSuccessorsOfPreds; score += numDominatedInst * JitOptions.branchPruningInstFactor; score += branchSpan * JitOptions.branchPruningBlockSpanFactor; score += numEffectfulInst * JitOptions.branchPruningEffectfulInstFactor; if (score < JitOptions.branchPruningThreshold) shouldBailout = false; // If the predecessors do not have enough hit counts, keep the // branch, until we recompile this function later, with more // information. if (predCount / numSuccessorsOfPreds < 50) shouldBailout = false; // There is only a single successors to the predecessors, thus the // decision should be taken as part of the previous block // investigation, and this block should be unreachable. if (numSuccessorsOfPreds == 1) shouldBailout = false; // If this is the exit block of a loop, then keep this basic // block. This heuristic is useful as a bailout is often much more // costly than a simple exit sequence. if (isLoopExit) shouldBailout = false; // Interpreters are often implemented as a table switch within a for // loop. What might happen is that the interpreter heats up in a // subset of instructions, but might need other instructions for the // rest of the evaluation. if (numSuccessorsOfPreds > 8) shouldBailout = false; JitSpew(JitSpew_Prune, "info: block %d," " predCount: %" PRIuSIZE ", domInst: %" PRIuSIZE ", span: %" PRIuSIZE ", effectful: %" PRIuSIZE ", " " isLoopExit: %s, numSuccessorsOfPred: %" PRIuSIZE "." " (score: %" PRIuSIZE ", shouldBailout: %s)", block->id(), predCount, numDominatedInst, branchSpan, numEffectfulInst, isLoopExit ? "true" : "false", numSuccessorsOfPreds, score, shouldBailout ? "true" : "false"); } // Continue to the next basic block if the current basic block can // remain unchanged. if (!isUnreachable && !shouldBailout) continue; someUnreachable = true; if (isUnreachable) { JitSpew(JitSpew_Prune, "Mark block %d as unreachable.", block->id()); block->setUnreachable(); // If the block is unreachable, then there is no need to convert it // to a bailing block. } else if (shouldBailout) { JitSpew(JitSpew_Prune, "Mark block %d as bailing block.", block->id()); block->markUnchecked(); } // When removing a loop header, we should ensure that its backedge is // removed first, otherwise this triggers an assertion in // removePredecessorsWithoutPhiOperands. if (block->isLoopHeader()) { JitSpew(JitSpew_Prune, "Mark block %d as bailing block. (loop backedge)", block->backedge()->id()); block->backedge()->markUnchecked(); } } // Returns early if nothing changed. if (!someUnreachable) return true; JitSpew(JitSpew_Prune, "Convert basic block to bailing blocks, and remove unreachable blocks:"); JitSpewIndent indent(JitSpew_Prune); // 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(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; JitSpew(JitSpew_Prune, "Remove / Replace block %d.", block->id()); JitSpewIndent indent(JitSpew_Prune); // As we are going to replace/remove the last instruction, we first have // to remove this block from the predecessor list of its successors. RemoveFromSuccessors(block); // Convert the current basic block to a bailing block which ends with an // Unreachable control instruction. if (block->isMarked()) { JitSpew(JitSpew_Prune, "Convert Block %d to a bailing block.", block->id()); if (!graph.alloc().ensureBallast()) return false; ConvertToBailingBlock(graph.alloc(), block); block->unmark(); } // Remove all instructions. if (block->unreachable()) { JitSpew(JitSpew_Prune, "Remove Block %d.", block->id()); JitSpewIndent indent(JitSpew_Prune); graph.removeBlock(block); } } return true; } static bool SplitCriticalEdgesForBlock(MIRGraph& graph, MBasicBlock* block) { if (block->numSuccessors() < 2) return true; for (size_t i = 0; i < block->numSuccessors(); i++) { MBasicBlock* target = block->getSuccessor(i); if (target->numPredecessors() < 2) continue; // Create a simple new block which contains a goto and which split the // edge between block and target. MBasicBlock* split = MBasicBlock::NewSplitEdge(graph, block, i, target); if (!split) return false; } return true; } // A critical edge is an edge which is neither its successor's only predecessor // nor its predecessor's only successor. Critical edges must be split to // prevent copy-insertion and code motion from affecting other edges. bool jit::SplitCriticalEdges(MIRGraph& graph) { for (MBasicBlockIterator iter(graph.begin()); iter != graph.end(); iter++) { MBasicBlock* block = *iter; if (!SplitCriticalEdgesForBlock(graph, block)) return false; } return true; } bool jit::IsUint32Type(const MDefinition* def) { if (def->isBeta()) def = def->getOperand(0); if (def->type() != MIRType::Int32) return false; return def->isUrsh() && def->getOperand(1)->isConstant() && def->getOperand(1)->toConstant()->type() == MIRType::Int32 && def->getOperand(1)->toConstant()->toInt32() == 0; } // Return whether a block simply computes the specified constant value. static bool BlockComputesConstant(MBasicBlock* block, MDefinition* value, bool* constBool) { // Look for values with no uses. This is used to eliminate constant // computing blocks in condition statements, and the phi which used to // consume the constant has already been removed. if (value->hasUses()) return false; if (!value->isConstant() || value->block() != block) return false; if (!block->phisEmpty()) return false; for (MInstructionIterator iter = block->begin(); iter != block->end(); ++iter) { if (*iter != value || !iter->isGoto()) return false; } return value->toConstant()->valueToBoolean(constBool); } // Find phis that are redudant: // // 1) phi(a, a) // can get replaced by a // // 2) phi(filtertypeset(a, type1), filtertypeset(a, type1)) // equals filtertypeset(a, type1) // // 3) phi(a, filtertypeset(a, type1)) // equals filtertypeset(a, type1 union type(a)) // equals filtertypeset(a, type(a)) // equals a // // 4) phi(filtertypeset(a, type1), filtertypeset(a, type2)) // equals filtertypeset(a, type1 union type2) // // This is the special case. We can only replace this with 'a' iif // type(a) == type1 union type2. Since optimizations could have // happened based on a more specific phi type. static bool IsPhiRedudantFilter(MPhi* phi) { // Handle (1) and (2) if (phi->operandIfRedundant()) return true; // Handle (3) bool onlyFilters = false; MDefinition* a = phi->getOperand(0); if (a->isFilterTypeSet()) { a = a->toFilterTypeSet()->input(); onlyFilters = true; } for (size_t i = 1; i < phi->numOperands(); i++) { MDefinition* operand = phi->getOperand(i); if (operand == a) { onlyFilters = false; continue; } if (operand->isFilterTypeSet() && operand->toFilterTypeSet()->input() == a) continue; return false; } if (!onlyFilters) return true; // Handle (4) MOZ_ASSERT(onlyFilters); return EqualTypes(a->type(), a->resultTypeSet(), phi->type(), phi->resultTypeSet()); } // Determine whether phiBlock/testBlock simply compute a phi and perform a // test on it. static bool BlockIsSingleTest(MBasicBlock* phiBlock, MBasicBlock* testBlock, MPhi** pphi, MTest** ptest) { *pphi = nullptr; *ptest = nullptr; if (phiBlock != testBlock) { MOZ_ASSERT(phiBlock->numSuccessors() == 1 && phiBlock->getSuccessor(0) == testBlock); if (!phiBlock->begin()->isGoto()) return false; } MInstruction* ins = *testBlock->begin(); if (!ins->isTest()) return false; MTest* test = ins->toTest(); if (!test->input()->isPhi()) return false; MPhi* phi = test->input()->toPhi(); if (phi->block() != phiBlock) return false; for (MUseIterator iter = phi->usesBegin(); iter != phi->usesEnd(); ++iter) { MUse* use = *iter; if (use->consumer() == test) continue; if (use->consumer()->isResumePoint()) { MBasicBlock* useBlock = use->consumer()->block(); if (useBlock == phiBlock || useBlock == testBlock) continue; } return false; } for (MPhiIterator iter = phiBlock->phisBegin(); iter != phiBlock->phisEnd(); ++iter) { if (*iter == phi) continue; if (IsPhiRedudantFilter(*iter)) continue; return false; } if (phiBlock != testBlock && !testBlock->phisEmpty()) return false; *pphi = phi; *ptest = test; return true; } // Change block so that it ends in a goto to the specific target block. // existingPred is an existing predecessor of the block. static void UpdateGotoSuccessor(TempAllocator& alloc, MBasicBlock* block, MBasicBlock* target, MBasicBlock* existingPred) { MInstruction* ins = block->lastIns(); MOZ_ASSERT(ins->isGoto()); ins->toGoto()->target()->removePredecessor(block); block->discardLastIns(); MGoto* newGoto = MGoto::New(alloc, target); block->end(newGoto); target->addPredecessorSameInputsAs(block, existingPred); } // Change block so that it ends in a test of the specified value, going to // either ifTrue or ifFalse. existingPred is an existing predecessor of ifTrue // or ifFalse with the same values incoming to ifTrue/ifFalse as block. // existingPred is not required to be a predecessor of ifTrue/ifFalse if block // already ends in a test going to that block on a true/false result. static void UpdateTestSuccessors(TempAllocator& alloc, MBasicBlock* block, MDefinition* value, MBasicBlock* ifTrue, MBasicBlock* ifFalse, MBasicBlock* existingPred) { MInstruction* ins = block->lastIns(); if (ins->isTest()) { MTest* test = ins->toTest(); MOZ_ASSERT(test->input() == value); if (ifTrue != test->ifTrue()) { test->ifTrue()->removePredecessor(block); ifTrue->addPredecessorSameInputsAs(block, existingPred); MOZ_ASSERT(test->ifTrue() == test->getSuccessor(0)); test->replaceSuccessor(0, ifTrue); } if (ifFalse != test->ifFalse()) { test->ifFalse()->removePredecessor(block); ifFalse->addPredecessorSameInputsAs(block, existingPred); MOZ_ASSERT(test->ifFalse() == test->getSuccessor(1)); test->replaceSuccessor(1, ifFalse); } return; } MOZ_ASSERT(ins->isGoto()); ins->toGoto()->target()->removePredecessor(block); block->discardLastIns(); MTest* test = MTest::New(alloc, value, ifTrue, ifFalse); block->end(test); ifTrue->addPredecessorSameInputsAs(block, existingPred); ifFalse->addPredecessorSameInputsAs(block, existingPred); } static bool MaybeFoldConditionBlock(MIRGraph& graph, MBasicBlock* initialBlock) { // Optimize the MIR graph to improve the code generated for conditional // operations. A test like 'if (a ? b : c)' normally requires four blocks, // with a phi for the intermediate value. This can be improved to use three // blocks with no phi value, and if either b or c is constant, // e.g. 'if (a ? b : 0)', then the block associated with that constant // can be eliminated. /* * Look for a diamond pattern: * * initialBlock * / \ * trueBranch falseBranch * \ / * phiBlock * | * testBlock * * Where phiBlock contains a single phi combining values pushed onto the * stack by trueBranch and falseBranch, and testBlock contains a test on * that phi. phiBlock and testBlock may be the same block; generated code * will use different blocks if the (?:) op is in an inlined function. */ MInstruction* ins = initialBlock->lastIns(); if (!ins->isTest()) return true; MTest* initialTest = ins->toTest(); MBasicBlock* trueBranch = initialTest->ifTrue(); if (trueBranch->numPredecessors() != 1 || trueBranch->numSuccessors() != 1) return true; MBasicBlock* falseBranch = initialTest->ifFalse(); if (falseBranch->numPredecessors() != 1 || falseBranch->numSuccessors() != 1) return true; MBasicBlock* phiBlock = trueBranch->getSuccessor(0); if (phiBlock != falseBranch->getSuccessor(0)) return true; if (phiBlock->numPredecessors() != 2) return true; if (initialBlock->isLoopBackedge() || trueBranch->isLoopBackedge() || falseBranch->isLoopBackedge()) return true; MBasicBlock* testBlock = phiBlock; if (testBlock->numSuccessors() == 1) { if (testBlock->isLoopBackedge()) return true; testBlock = testBlock->getSuccessor(0); if (testBlock->numPredecessors() != 1) return true; } // Make sure the test block does not have any outgoing loop backedges. if (!SplitCriticalEdgesForBlock(graph, testBlock)) return false; MPhi* phi; MTest* finalTest; if (!BlockIsSingleTest(phiBlock, testBlock, &phi, &finalTest)) return true; MDefinition* trueResult = phi->getOperand(phiBlock->indexForPredecessor(trueBranch)); MDefinition* falseResult = phi->getOperand(phiBlock->indexForPredecessor(falseBranch)); // OK, we found the desired pattern, now transform the graph. // Patch up phis that filter their input. for (MPhiIterator iter = phiBlock->phisBegin(); iter != phiBlock->phisEnd(); ++iter) { if (*iter == phi) continue; MOZ_ASSERT(IsPhiRedudantFilter(*iter)); MDefinition* redundant = (*iter)->operandIfRedundant(); if (!redundant) { redundant = (*iter)->getOperand(0); if (redundant->isFilterTypeSet()) redundant = redundant->toFilterTypeSet()->input(); } (*iter)->replaceAllUsesWith(redundant); } // Remove the phi from phiBlock. phiBlock->discardPhi(*phiBlock->phisBegin()); // If either trueBranch or falseBranch just computes a constant for the // test, determine the block that branch will end up jumping to and eliminate // the branch. Otherwise, change the end of the block to a test that jumps // directly to successors of testBlock, rather than to testBlock itself. MBasicBlock* trueTarget = trueBranch; bool constBool; if (BlockComputesConstant(trueBranch, trueResult, &constBool)) { trueTarget = constBool ? finalTest->ifTrue() : finalTest->ifFalse(); phiBlock->removePredecessor(trueBranch); graph.removeBlock(trueBranch); } else if (initialTest->input() == trueResult) { UpdateGotoSuccessor(graph.alloc(), trueBranch, finalTest->ifTrue(), testBlock); } else { UpdateTestSuccessors(graph.alloc(), trueBranch, trueResult, finalTest->ifTrue(), finalTest->ifFalse(), testBlock); } MBasicBlock* falseTarget = falseBranch; if (BlockComputesConstant(falseBranch, falseResult, &constBool)) { falseTarget = constBool ? finalTest->ifTrue() : finalTest->ifFalse(); phiBlock->removePredecessor(falseBranch); graph.removeBlock(falseBranch); } else if (initialTest->input() == falseResult) { UpdateGotoSuccessor(graph.alloc(), falseBranch, finalTest->ifFalse(), testBlock); } else { UpdateTestSuccessors(graph.alloc(), falseBranch, falseResult, finalTest->ifTrue(), finalTest->ifFalse(), testBlock); } // Short circuit the initial test to skip any constant branch eliminated above. UpdateTestSuccessors(graph.alloc(), initialBlock, initialTest->input(), trueTarget, falseTarget, testBlock); // Remove phiBlock, if different from testBlock. if (phiBlock != testBlock) { testBlock->removePredecessor(phiBlock); graph.removeBlock(phiBlock); } // Remove testBlock itself. finalTest->ifTrue()->removePredecessor(testBlock); finalTest->ifFalse()->removePredecessor(testBlock); graph.removeBlock(testBlock); return true; } bool jit::FoldTests(MIRGraph& graph) { for (MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) { if (!MaybeFoldConditionBlock(graph, *block)) return false; } return true; } static void EliminateTriviallyDeadResumePointOperands(MIRGraph& graph, MResumePoint* rp) { // If we will pop the top of the stack immediately after resuming, // then don't preserve the top value in the resume point. if (rp->mode() != MResumePoint::ResumeAt || *rp->pc() != JSOP_POP) return; size_t top = rp->stackDepth() - 1; MOZ_ASSERT(!rp->isObservableOperand(top)); MDefinition* def = rp->getOperand(top); if (def->isConstant()) return; MConstant* constant = rp->block()->optimizedOutConstant(graph.alloc()); rp->replaceOperand(top, constant); } // Operands to a resume point which are dead at the point of the resume can be // replaced with a magic value. This analysis supports limited detection of // dead operands, pruning those which are defined in the resume point's basic // block and have no uses outside the block or at points later than the resume // point. // // This is intended to ensure that extra resume points within a basic block // will not artificially extend the lifetimes of any SSA values. This could // otherwise occur if the new resume point captured a value which is created // between the old and new resume point and is dead at the new resume point. bool jit::EliminateDeadResumePointOperands(MIRGenerator* mir, MIRGraph& graph) { // If we are compiling try blocks, locals and arguments may be observable // from catch or finally blocks (which Ion does not compile). For now just // disable the pass in this case. if (graph.hasTryBlock()) return true; for (PostorderIterator block = graph.poBegin(); block != graph.poEnd(); block++) { if (mir->shouldCancel("Eliminate Dead Resume Point Operands (main loop)")) return false; if (MResumePoint* rp = block->entryResumePoint()) EliminateTriviallyDeadResumePointOperands(graph, rp); // The logic below can get confused on infinite loops. if (block->isLoopHeader() && block->backedge() == *block) continue; for (MInstructionIterator ins = block->begin(); ins != block->end(); ins++) { if (MResumePoint* rp = ins->resumePoint()) EliminateTriviallyDeadResumePointOperands(graph, rp); // No benefit to replacing constant operands with other constants. if (ins->isConstant()) continue; // Scanning uses does not give us sufficient information to tell // where instructions that are involved in box/unbox operations or // parameter passing might be live. Rewriting uses of these terms // in resume points may affect the interpreter's behavior. Rather // than doing a more sophisticated analysis, just ignore these. if (ins->isUnbox() || ins->isParameter() || ins->isTypeBarrier() || ins->isComputeThis() || ins->isFilterTypeSet()) { continue; } // Early intermediate values captured by resume points, such as // TypedObject, ArrayState and its allocation, may be legitimately // dead in Ion code, but are still needed if we bail out. They can // recover on bailout. if (ins->isNewDerivedTypedObject() || ins->isRecoveredOnBailout()) { MOZ_ASSERT(ins->canRecoverOnBailout()); continue; } // If the instruction's behavior has been constant folded into a // separate instruction, we can't determine precisely where the // instruction becomes dead and can't eliminate its uses. if (ins->isImplicitlyUsed() || ins->isUseRemoved()) continue; // Check if this instruction's result is only used within the // current block, and keep track of its last use in a definition // (not resume point). This requires the instructions in the block // to be numbered, ensured by running this immediately after alias // analysis. uint32_t maxDefinition = 0; for (MUseIterator uses(ins->usesBegin()); uses != ins->usesEnd(); uses++) { MNode* consumer = uses->consumer(); if (consumer->isResumePoint()) { // If the instruction's is captured by one of the resume point, then // it might be observed indirectly while the frame is live on the // stack, so it has to be computed. MResumePoint* resume = consumer->toResumePoint(); if (resume->isObservableOperand(*uses)) { maxDefinition = UINT32_MAX; break; } continue; } MDefinition* def = consumer->toDefinition(); if (def->block() != *block || def->isBox() || def->isPhi()) { maxDefinition = UINT32_MAX; break; } maxDefinition = Max(maxDefinition, def->id()); } if (maxDefinition == UINT32_MAX) continue; // Walk the uses a second time, removing any in resume points after // the last use in a definition. for (MUseIterator uses(ins->usesBegin()); uses != ins->usesEnd(); ) { MUse* use = *uses++; if (use->consumer()->isDefinition()) continue; MResumePoint* mrp = use->consumer()->toResumePoint(); if (mrp->block() != *block || !mrp->instruction() || mrp->instruction() == *ins || mrp->instruction()->id() <= maxDefinition) { continue; } if (!graph.alloc().ensureBallast()) return false; // Store an optimized out magic value in place of all dead // resume point operands. Making any such substitution can in // general alter the interpreter's behavior, even though the // code is dead, as the interpreter will still execute opcodes // whose effects cannot be observed. If the magic value value // were to flow to, say, a dead property access the // interpreter could throw an exception; we avoid this problem // by removing dead operands before removing dead code. MConstant* constant = MConstant::New(graph.alloc(), MagicValue(JS_OPTIMIZED_OUT)); block->insertBefore(*(block->begin()), constant); use->replaceProducer(constant); } } } return true; } // Test whether |def| would be needed if it had no uses. bool js::jit::DeadIfUnused(const MDefinition* def) { return !def->isEffectful() && (!def->isGuard() || def->block() == def->block()->graph().osrBlock()) && !def->isGuardRangeBailouts() && !def->isControlInstruction() && (!def->isInstruction() || !def->toInstruction()->resumePoint()); } // Test whether |def| may be safely discarded, due to being dead or due to being // located in a basic block which has itself been marked for discarding. bool js::jit::IsDiscardable(const MDefinition* def) { return !def->hasUses() && (DeadIfUnused(def) || def->block()->isMarked()); } // Instructions are useless if they are unused and have no side effects. // This pass eliminates useless instructions. // The graph itself is unchanged. bool jit::EliminateDeadCode(MIRGenerator* mir, MIRGraph& graph) { // Traverse in postorder so that we hit uses before definitions. // Traverse instruction list backwards for the same reason. for (PostorderIterator block = graph.poBegin(); block != graph.poEnd(); block++) { if (mir->shouldCancel("Eliminate Dead Code (main loop)")) return false; // Remove unused instructions. for (MInstructionReverseIterator iter = block->rbegin(); iter != block->rend(); ) { MInstruction* inst = *iter++; if (js::jit::IsDiscardable(inst)) { block->discard(inst); } } } return true; } static inline bool IsPhiObservable(MPhi* phi, Observability observe) { // If the phi has uses which are not reflected in SSA, then behavior in the // interpreter may be affected by removing the phi. if (phi->isImplicitlyUsed() || phi->isUseRemoved()) return true; // Check for uses of this phi node outside of other phi nodes. // Note that, initially, we skip reading resume points, which we // don't count as actual uses. If the only uses are resume points, // then the SSA name is never consumed by the program. However, // after optimizations have been performed, it's possible that the // actual uses in the program have been (incorrectly) optimized // away, so we must be more conservative and consider resume // points as well. for (MUseIterator iter(phi->usesBegin()); iter != phi->usesEnd(); iter++) { MNode* consumer = iter->consumer(); if (consumer->isResumePoint()) { MResumePoint* resume = consumer->toResumePoint(); if (observe == ConservativeObservability) return true; if (resume->isObservableOperand(*iter)) return true; } else { MDefinition* def = consumer->toDefinition(); if (!def->isPhi()) return true; } } return false; } // Handles cases like: // x is phi(a, x) --> a // x is phi(a, a) --> a static inline MDefinition* IsPhiRedundant(MPhi* phi) { MDefinition* first = phi->operandIfRedundant(); if (first == nullptr) return nullptr; // Propagate the ImplicitlyUsed flag if |phi| is replaced with another phi. if (phi->isImplicitlyUsed()) first->setImplicitlyUsedUnchecked(); return first; } bool jit::EliminatePhis(MIRGenerator* mir, MIRGraph& graph, Observability observe) { // Eliminates redundant or unobservable phis from the graph. A // redundant phi is something like b = phi(a, a) or b = phi(a, b), // both of which can be replaced with a. An unobservable phi is // one that whose value is never used in the program. // // Note that we must be careful not to eliminate phis representing // values that the interpreter will require later. When the graph // is first constructed, we can be more aggressive, because there // is a greater correspondence between the CFG and the bytecode. // After optimizations such as GVN have been performed, however, // the bytecode and CFG may not correspond as closely to one // another. In that case, we must be more conservative. The flag // |conservativeObservability| is used to indicate that eliminate // phis is being run after some optimizations have been performed, // and thus we should use more conservative rules about // observability. The particular danger is that we can optimize // away uses of a phi because we think they are not executable, // but the foundation for that assumption is false TI information // that will eventually be invalidated. Therefore, if // |conservativeObservability| is set, we will consider any use // from a resume point to be observable. Otherwise, we demand a // use from an actual instruction. Vector worklist; // 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++) { 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(); // If the phi is redundant, remove it here. if (MDefinition* redundant = IsPhiRedundant(phi)) { phi->justReplaceAllUsesWith(redundant); block->discardPhi(phi); continue; } // Enqueue observable Phis. if (IsPhiObservable(phi, observe)) { phi->setInWorklist(); if (!worklist.append(phi)) return false; } } } // Iteratively mark all phis reachable from live phis. while (!worklist.empty()) { if (mir->shouldCancel("Eliminate Phis (worklist)")) return false; MPhi* phi = worklist.popCopy(); MOZ_ASSERT(phi->isUnused()); phi->setNotInWorklist(); // The removal of Phis can produce newly redundant phis. if (MDefinition* redundant = IsPhiRedundant(phi)) { // Add to the worklist the used phis which are impacted. for (MUseDefIterator it(phi); it; it++) { if (it.def()->isPhi()) { MPhi* use = it.def()->toPhi(); if (!use->isUnused()) { use->setUnusedUnchecked(); use->setInWorklist(); if (!worklist.append(use)) return false; } } } phi->justReplaceAllUsesWith(redundant); } else { // Otherwise flag them as used. phi->setNotUnused(); } // The current phi is/was used, so all its operands are used. for (size_t i = 0, e = phi->numOperands(); i < e; i++) { MDefinition* in = phi->getOperand(i); if (!in->isPhi() || !in->isUnused() || in->isInWorklist()) continue; in->setInWorklist(); if (!worklist.append(in->toPhi())) return false; } } // Sweep dead phis. for (PostorderIterator block = graph.poBegin(); block != graph.poEnd(); block++) { MPhiIterator iter = block->phisBegin(); while (iter != block->phisEnd()) { MPhi* phi = *iter++; if (phi->isUnused()) { if (!phi->optimizeOutAllUses(graph.alloc())) return false; block->discardPhi(phi); } } } return true; } namespace { // The type analysis algorithm inserts conversions and box/unbox instructions // to make the IR graph well-typed for future passes. // // Phi adjustment: If a phi's inputs are all the same type, the phi is // specialized to return that type. // // Input adjustment: Each input is asked to apply conversion operations to its // inputs. This may include Box, Unbox, or other instruction-specific type // conversion operations. // class TypeAnalyzer { MIRGenerator* mir; MIRGraph& graph; Vector phiWorklist_; TempAllocator& alloc() const { return graph.alloc(); } bool addPhiToWorklist(MPhi* phi) { if (phi->isInWorklist()) return true; if (!phiWorklist_.append(phi)) return false; phi->setInWorklist(); return true; } MPhi* popPhi() { MPhi* phi = phiWorklist_.popCopy(); phi->setNotInWorklist(); return phi; } bool respecialize(MPhi* phi, MIRType type); bool propagateSpecialization(MPhi* phi); bool specializePhis(); void replaceRedundantPhi(MPhi* phi); bool adjustPhiInputs(MPhi* phi); bool adjustInputs(MDefinition* def); bool insertConversions(); bool checkFloatCoherency(); bool graphContainsFloat32(); bool markPhiConsumers(); bool markPhiProducers(); bool specializeValidFloatOps(); bool tryEmitFloatOperations(); public: TypeAnalyzer(MIRGenerator* mir, MIRGraph& graph) : mir(mir), graph(graph) { } bool analyze(); }; } /* anonymous namespace */ // Try to specialize this phi based on its non-cyclic inputs. static MIRType GuessPhiType(MPhi* phi, bool* hasInputsWithEmptyTypes) { #ifdef DEBUG // Check that different magic constants aren't flowing together. Ignore // JS_OPTIMIZED_OUT, since an operand could be legitimately optimized // away. MIRType magicType = MIRType::None; for (size_t i = 0; i < phi->numOperands(); i++) { MDefinition* in = phi->getOperand(i); if (in->type() == MIRType::MagicOptimizedArguments || in->type() == MIRType::MagicHole || in->type() == MIRType::MagicIsConstructing) { if (magicType == MIRType::None) magicType = in->type(); MOZ_ASSERT(magicType == in->type()); } } #endif *hasInputsWithEmptyTypes = false; MIRType type = MIRType::None; bool convertibleToFloat32 = false; bool hasPhiInputs = false; for (size_t i = 0, e = phi->numOperands(); i < e; i++) { MDefinition* in = phi->getOperand(i); if (in->isPhi()) { hasPhiInputs = true; if (!in->toPhi()->triedToSpecialize()) continue; if (in->type() == MIRType::None) { // The operand is a phi we tried to specialize, but we were // unable to guess its type. propagateSpecialization will // propagate the type to this phi when it becomes known. continue; } } // Ignore operands which we've never observed. if (in->resultTypeSet() && in->resultTypeSet()->empty()) { *hasInputsWithEmptyTypes = true; continue; } if (type == MIRType::None) { type = in->type(); if (in->canProduceFloat32()) convertibleToFloat32 = true; continue; } if (type != in->type()) { if (convertibleToFloat32 && in->type() == MIRType::Float32) { // If we only saw definitions that can be converted into Float32 before and // encounter a Float32 value, promote previous values to Float32 type = MIRType::Float32; } else if (IsTypeRepresentableAsDouble(type) && IsTypeRepresentableAsDouble(in->type())) { // Specialize phis with int32 and double operands as double. type = MIRType::Double; convertibleToFloat32 &= in->canProduceFloat32(); } else { return MIRType::Value; } } } if (type == MIRType::None && !hasPhiInputs) { // All inputs are non-phis with empty typesets. Use MIRType::Value // in this case, as it's impossible to get better type information. MOZ_ASSERT(*hasInputsWithEmptyTypes); type = MIRType::Value; } return type; } bool TypeAnalyzer::respecialize(MPhi* phi, MIRType type) { if (phi->type() == type) return true; phi->specialize(type); return addPhiToWorklist(phi); } bool TypeAnalyzer::propagateSpecialization(MPhi* phi) { MOZ_ASSERT(phi->type() != MIRType::None); // Verify that this specialization matches any phis depending on it. for (MUseDefIterator iter(phi); iter; iter++) { if (!iter.def()->isPhi()) continue; MPhi* use = iter.def()->toPhi(); if (!use->triedToSpecialize()) continue; if (use->type() == MIRType::None) { // We tried to specialize this phi, but were unable to guess its // type. Now that we know the type of one of its operands, we can // specialize it. if (!respecialize(use, phi->type())) return false; continue; } if (use->type() != phi->type()) { // Specialize phis with int32 that can be converted to float and float operands as floats. if ((use->type() == MIRType::Int32 && use->canProduceFloat32() && phi->type() == MIRType::Float32) || (phi->type() == MIRType::Int32 && phi->canProduceFloat32() && use->type() == MIRType::Float32)) { if (!respecialize(use, MIRType::Float32)) return false; continue; } // Specialize phis with int32 and double operands as double. if (IsTypeRepresentableAsDouble(use->type()) && IsTypeRepresentableAsDouble(phi->type())) { if (!respecialize(use, MIRType::Double)) return false; continue; } // This phi in our use chain can now no longer be specialized. if (!respecialize(use, MIRType::Value)) return false; } } return true; } bool TypeAnalyzer::specializePhis() { Vector phisWithEmptyInputTypes; for (PostorderIterator block(graph.poBegin()); block != graph.poEnd(); block++) { if (mir->shouldCancel("Specialize Phis (main loop)")) 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); if (type == MIRType::None) { // We tried to guess the type but failed because all operands are // phis we still have to visit. Set the triedToSpecialize flag but // don't propagate the type to other phis, propagateSpecialization // will do that once we know the type of one of the operands. // Edge case: when this phi has a non-phi input with an empty // typeset, it's possible for two phis to have a cyclic // dependency and they will both have MIRType::None. Specialize // such phis to MIRType::Value later on. if (hasInputsWithEmptyTypes && !phisWithEmptyInputTypes.append(*phi)) return false; continue; } if (!propagateSpecialization(*phi)) return false; } } do { while (!phiWorklist_.empty()) { if (mir->shouldCancel("Specialize Phis (worklist)")) return false; MPhi* phi = popPhi(); if (!propagateSpecialization(phi)) return false; } // When two phis have a cyclic dependency and inputs that have an empty // typeset (which are ignored by GuessPhiType), we may still have to // specialize these to MIRType::Value. while (!phisWithEmptyInputTypes.empty()) { if (mir->shouldCancel("Specialize Phis (phisWithEmptyInputTypes)")) return false; MPhi* phi = phisWithEmptyInputTypes.popCopy(); if (phi->type() == MIRType::None) { phi->specialize(MIRType::Value); if (!propagateSpecialization(phi)) return false; } } } while (!phiWorklist_.empty()); return true; } bool TypeAnalyzer::adjustPhiInputs(MPhi* phi) { MIRType phiType = phi->type(); MOZ_ASSERT(phiType != MIRType::None); // If we specialized a type that's not Value, there are 3 cases: // 1. Every input is of that type. // 2. Every observed input is of that type (i.e., some inputs haven't been executed yet). // 3. Inputs were doubles and int32s, and was specialized to double. if (phiType != MIRType::Value) { for (size_t i = 0, e = phi->numOperands(); i < e; i++) { MDefinition* in = phi->getOperand(i); if (in->type() == phiType) continue; if (!alloc().ensureBallast()) return false; if (in->isBox() && in->toBox()->input()->type() == phiType) { phi->replaceOperand(i, in->toBox()->input()); } else { MInstruction* replacement; if (phiType == MIRType::Double && IsFloatType(in->type())) { // Convert int32 operands to double. replacement = MToDouble::New(alloc(), in); } else if (phiType == MIRType::Float32) { if (in->type() == MIRType::Int32 || in->type() == MIRType::Double) { replacement = MToFloat32::New(alloc(), in); } else { // See comment below if (in->type() != MIRType::Value) { MBox* box = MBox::New(alloc(), in); in->block()->insertBefore(in->block()->lastIns(), box); in = box; } MUnbox* unbox = MUnbox::New(alloc(), in, MIRType::Double, MUnbox::Fallible); in->block()->insertBefore(in->block()->lastIns(), unbox); replacement = MToFloat32::New(alloc(), in); } } else { // If we know this branch will fail to convert to phiType, // insert a box that'll immediately fail in the fallible unbox // below. if (in->type() != MIRType::Value) { MBox* box = MBox::New(alloc(), in); in->block()->insertBefore(in->block()->lastIns(), box); in = box; } // Be optimistic and insert unboxes when the operand is a // value. replacement = MUnbox::New(alloc(), in, phiType, MUnbox::Fallible); } in->block()->insertBefore(in->block()->lastIns(), replacement); phi->replaceOperand(i, replacement); } } return true; } // Box every typed input. for (size_t i = 0, e = phi->numOperands(); i < e; i++) { MDefinition* in = phi->getOperand(i); if (in->type() == MIRType::Value) continue; // The input is being explicitly unboxed, so sneak past and grab // the original box. if (in->isUnbox() && phi->typeIncludes(in->toUnbox()->input())) in = in->toUnbox()->input(); if (in->type() != MIRType::Value) { if (!alloc().ensureBallast()) return false; MBasicBlock* pred = phi->block()->getPredecessor(i); in = AlwaysBoxAt(alloc(), pred->lastIns(), in); } phi->replaceOperand(i, in); } return true; } bool TypeAnalyzer::adjustInputs(MDefinition* def) { // Definitions such as MPhi have no type policy. if (!def->isInstruction()) return true; MInstruction* ins = def->toInstruction(); TypePolicy* policy = ins->typePolicy(); if (policy && !policy->adjustInputs(alloc(), ins)) return false; return true; } void TypeAnalyzer::replaceRedundantPhi(MPhi* phi) { MBasicBlock* block = phi->block(); js::Value v; switch (phi->type()) { case MIRType::Undefined: v = UndefinedValue(); break; case MIRType::Null: v = NullValue(); break; case MIRType::MagicOptimizedArguments: v = MagicValue(JS_OPTIMIZED_ARGUMENTS); break; case MIRType::MagicOptimizedOut: v = MagicValue(JS_OPTIMIZED_OUT); break; case MIRType::MagicUninitializedLexical: v = MagicValue(JS_UNINITIALIZED_LEXICAL); break; default: MOZ_CRASH("unexpected type"); } MConstant* c = MConstant::New(alloc(), v); // The instruction pass will insert the box block->insertBefore(*(block->begin()), c); phi->justReplaceAllUsesWith(c); } bool TypeAnalyzer::insertConversions() { // Instructions are processed in reverse postorder: all uses are defs are // seen before uses. This ensures that output adjustment (which may rewrite // inputs of uses) does not conflict with input adjustment. for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) { if (mir->shouldCancel("Insert Conversions")) return false; for (MPhiIterator iter(block->phisBegin()), end(block->phisEnd()); iter != end; ) { MPhi* phi = *iter++; if (phi->type() == MIRType::Undefined || phi->type() == MIRType::Null || phi->type() == MIRType::MagicOptimizedArguments || phi->type() == MIRType::MagicOptimizedOut || phi->type() == MIRType::MagicUninitializedLexical) { replaceRedundantPhi(phi); block->discardPhi(phi); } else { if (!adjustPhiInputs(phi)) return false; } } // AdjustInputs can add/remove/mutate instructions before and after the // current instruction. Only increment the iterator after it is finished. for (MInstructionIterator iter(block->begin()); iter != block->end(); iter++) { if (!alloc().ensureBallast()) return false; if (!adjustInputs(*iter)) return false; } } return true; } // This function tries to emit Float32 specialized operations whenever it's possible. // MIR nodes are flagged as: // - Producers, when they can create Float32 that might need to be coerced into a Double. // Loads in Float32 arrays and conversions to Float32 are producers. // - Consumers, when they can have Float32 as inputs and validate a legal use of a Float32. // Stores in Float32 arrays and conversions to Float32 are consumers. // - Float32 commutative, when using the Float32 instruction instead of the Double instruction // does not result in a compound loss of precision. This is the case for +, -, /, * with 2 // operands, for instance. However, an addition with 3 operands is not commutative anymore, // so an intermediate coercion is needed. // Except for phis, all these flags are known after Ion building, so they cannot change during // the process. // // The idea behind the algorithm is easy: whenever we can prove that a commutative operation // has only producers as inputs and consumers as uses, we can specialize the operation as a // float32 operation. Otherwise, we have to convert all float32 inputs to doubles. Even // if a lot of conversions are produced, GVN will take care of eliminating the redundant ones. // // Phis have a special status. Phis need to be flagged as producers or consumers as they can // be inputs or outputs of commutative instructions. Fortunately, producers and consumers // properties are such that we can deduce the property using all non phis inputs first (which form // an initial phi graph) and then propagate all properties from one phi to another using a // fixed point algorithm. The algorithm is ensured to terminate as each iteration has less or as // many flagged phis as the previous iteration (so the worst steady state case is all phis being // flagged as false). // // In a nutshell, the algorithm applies three passes: // 1 - Determine which phis are consumers. Each phi gets an initial value by making a global AND on // all its non-phi inputs. Then each phi propagates its value to other phis. If after propagation, // the flag value changed, we have to reapply the algorithm on all phi operands, as a phi is a // consumer if all of its uses are consumers. // 2 - Determine which phis are producers. It's the same algorithm, except that we have to reapply // the algorithm on all phi uses, as a phi is a producer if all of its operands are producers. // 3 - Go through all commutative operations and ensure their inputs are all producers and their // uses are all consumers. bool TypeAnalyzer::markPhiConsumers() { MOZ_ASSERT(phiWorklist_.empty()); // Iterate in postorder so worklist is initialized to RPO. for (PostorderIterator block(graph.poBegin()); block != graph.poEnd(); ++block) { if (mir->shouldCancel("Ensure Float32 commutativity - Consumer Phis - Initial state")) return false; for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd(); ++phi) { MOZ_ASSERT(!phi->isInWorklist()); bool canConsumeFloat32 = true; for (MUseDefIterator use(*phi); canConsumeFloat32 && use; use++) { MDefinition* usedef = use.def(); canConsumeFloat32 &= usedef->isPhi() || usedef->canConsumeFloat32(use.use()); } phi->setCanConsumeFloat32(canConsumeFloat32); if (canConsumeFloat32 && !addPhiToWorklist(*phi)) return false; } } while (!phiWorklist_.empty()) { if (mir->shouldCancel("Ensure Float32 commutativity - Consumer Phis - Fixed point")) return false; MPhi* phi = popPhi(); MOZ_ASSERT(phi->canConsumeFloat32(nullptr /* unused */)); bool validConsumer = true; for (MUseDefIterator use(phi); use; use++) { MDefinition* def = use.def(); if (def->isPhi() && !def->canConsumeFloat32(use.use())) { validConsumer = false; break; } } if (validConsumer) continue; // Propagate invalidated phis phi->setCanConsumeFloat32(false); for (size_t i = 0, e = phi->numOperands(); i < e; ++i) { MDefinition* input = phi->getOperand(i); if (input->isPhi() && !input->isInWorklist() && input->canConsumeFloat32(nullptr /* unused */)) { if (!addPhiToWorklist(input->toPhi())) return false; } } } return true; } bool TypeAnalyzer::markPhiProducers() { MOZ_ASSERT(phiWorklist_.empty()); // Iterate in reverse postorder so worklist is initialized to PO. for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); ++block) { if (mir->shouldCancel("Ensure Float32 commutativity - Producer Phis - initial state")) return false; for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd(); ++phi) { MOZ_ASSERT(!phi->isInWorklist()); bool canProduceFloat32 = true; for (size_t i = 0, e = phi->numOperands(); canProduceFloat32 && i < e; ++i) { MDefinition* input = phi->getOperand(i); canProduceFloat32 &= input->isPhi() || input->canProduceFloat32(); } phi->setCanProduceFloat32(canProduceFloat32); if (canProduceFloat32 && !addPhiToWorklist(*phi)) return false; } } while (!phiWorklist_.empty()) { if (mir->shouldCancel("Ensure Float32 commutativity - Producer Phis - Fixed point")) return false; MPhi* phi = popPhi(); MOZ_ASSERT(phi->canProduceFloat32()); bool validProducer = true; for (size_t i = 0, e = phi->numOperands(); i < e; ++i) { MDefinition* input = phi->getOperand(i); if (input->isPhi() && !input->canProduceFloat32()) { validProducer = false; break; } } if (validProducer) continue; // Propagate invalidated phis phi->setCanProduceFloat32(false); for (MUseDefIterator use(phi); use; use++) { MDefinition* def = use.def(); if (def->isPhi() && !def->isInWorklist() && def->canProduceFloat32()) { if (!addPhiToWorklist(def->toPhi())) return false; } } } return true; } bool TypeAnalyzer::specializeValidFloatOps() { for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); ++block) { if (mir->shouldCancel("Ensure Float32 commutativity - Instructions")) return false; for (MInstructionIterator ins(block->begin()); ins != block->end(); ++ins) { if (!ins->isFloat32Commutative()) continue; if (ins->type() == MIRType::Float32) continue; if (!alloc().ensureBallast()) return false; // This call will try to specialize the instruction iff all uses are consumers and // all inputs are producers. ins->trySpecializeFloat32(alloc()); } } return true; } bool TypeAnalyzer::graphContainsFloat32() { for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); ++block) { for (MDefinitionIterator def(*block); def; def++) { if (mir->shouldCancel("Ensure Float32 commutativity - Graph contains Float32")) return false; if (def->type() == MIRType::Float32) return true; } } return false; } bool TypeAnalyzer::tryEmitFloatOperations() { // Asm.js uses the ahead of time type checks to specialize operations, no need to check // them again at this point. if (mir->compilingWasm()) return true; // Check ahead of time that there is at least one definition typed as Float32, otherwise we // don't need this pass. if (!graphContainsFloat32()) return true; if (!markPhiConsumers()) return false; if (!markPhiProducers()) return false; if (!specializeValidFloatOps()) return false; return true; } bool TypeAnalyzer::checkFloatCoherency() { #ifdef DEBUG // Asserts that all Float32 instructions are flowing into Float32 consumers or specialized // operations for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); ++block) { if (mir->shouldCancel("Check Float32 coherency")) return false; for (MDefinitionIterator def(*block); def; def++) { if (def->type() != MIRType::Float32) continue; for (MUseDefIterator use(*def); use; use++) { MDefinition* consumer = use.def(); MOZ_ASSERT(consumer->isConsistentFloat32Use(use.use())); } } } #endif return true; } bool TypeAnalyzer::analyze() { if (!tryEmitFloatOperations()) return false; if (!specializePhis()) return false; if (!insertConversions()) return false; if (!checkFloatCoherency()) return false; return true; } bool jit::ApplyTypeInformation(MIRGenerator* mir, MIRGraph& graph) { TypeAnalyzer analyzer(mir, graph); if (!analyzer.analyze()) return false; return true; } // Check if `def` is only the N-th operand of `useDef`. static inline size_t IsExclusiveNthOperand(MDefinition* useDef, size_t n, MDefinition* def) { uint32_t num = useDef->numOperands(); if (n >= num || useDef->getOperand(n) != def) return false; for (uint32_t i = 0; i < num; i++) { if (i == n) continue; if (useDef->getOperand(i) == def) return false; } return true; } static size_t IsExclusiveThisArg(MCall* call, MDefinition* def) { return IsExclusiveNthOperand(call, MCall::IndexOfThis(), def); } static size_t IsExclusiveFirstArg(MCall* call, MDefinition* def) { return IsExclusiveNthOperand(call, MCall::IndexOfArgument(0), def); } static bool IsRegExpHoistableCall(MCall* call, MDefinition* def) { if (call->isConstructing()) return false; JSAtom* name; if (WrappedFunction* fun = call->getSingleTarget()) { if (!fun->isSelfHostedBuiltin()) return false; name = GetSelfHostedFunctionName(fun->rawJSFunction()); } else { MDefinition* funDef = call->getFunction(); if (funDef->isDebugCheckSelfHosted()) funDef = funDef->toDebugCheckSelfHosted()->input(); if (funDef->isTypeBarrier()) funDef = funDef->toTypeBarrier()->input(); if (!funDef->isCallGetIntrinsicValue()) return false; name = funDef->toCallGetIntrinsicValue()->name(); } // Hoistable only if the RegExp is the first argument of RegExpBuiltinExec. CompileRuntime* runtime = GetJitContext()->runtime; if (name == runtime->names().RegExpBuiltinExec || name == runtime->names().UnwrapAndCallRegExpBuiltinExec || name == runtime->names().RegExpMatcher || name == runtime->names().RegExpTester || name == runtime->names().RegExpSearcher) { return IsExclusiveFirstArg(call, def); } if (name == runtime->names().RegExp_prototype_Exec) return IsExclusiveThisArg(call, def); return false; } static bool CanCompareRegExp(MCompare* compare, MDefinition* def) { MDefinition* value; if (compare->lhs() == def) { value = compare->rhs(); } else { MOZ_ASSERT(compare->rhs() == def); value = compare->lhs(); } // Comparing two regexp that weren't cloned will give different result // than if they were cloned. if (value->mightBeType(MIRType::Object)) return false; // Make sure @@toPrimitive is not called which could notice // the difference between a not cloned/cloned regexp. JSOp op = compare->jsop(); // Strict equality comparison won't invoke @@toPrimitive. if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE) return true; if (op != JSOP_EQ && op != JSOP_NE) { // Relational comparison always invoke @@toPrimitive. MOZ_ASSERT(op == JSOP_GT || op == JSOP_GE || op == JSOP_LT || op == JSOP_LE); return false; } // Loose equality comparison can invoke @@toPrimitive. if (value->mightBeType(MIRType::Boolean) || value->mightBeType(MIRType::String) || value->mightBeType(MIRType::Int32) || value->mightBeType(MIRType::Double) || value->mightBeType(MIRType::Float32) || value->mightBeType(MIRType::Symbol)) { return false; } return true; } static inline void SetNotInWorklist(MDefinitionVector& worklist) { for (size_t i = 0; i < worklist.length(); i++) worklist[i]->setNotInWorklist(); } static bool IsRegExpHoistable(MIRGenerator* mir, MDefinition* regexp, MDefinitionVector& worklist, bool* hoistable) { MOZ_ASSERT(worklist.length() == 0); if (!worklist.append(regexp)) return false; regexp->setInWorklist(); for (size_t i = 0; i < worklist.length(); i++) { MDefinition* def = worklist[i]; if (mir->shouldCancel("IsRegExpHoistable outer loop")) return false; for (MUseIterator use = def->usesBegin(); use != def->usesEnd(); use++) { 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 (use->consumer()->isResumePoint()) continue; MDefinition* useDef = use->consumer()->toDefinition(); // Step through a few white-listed ops. if (useDef->isPhi() || useDef->isFilterTypeSet() || useDef->isGuardShape()) { if (useDef->isInWorklist()) continue; if (!worklist.append(useDef)) return false; useDef->setInWorklist(); continue; } // Instructions that doesn't invoke unknown code that may modify // RegExp instance or pass it to elsewhere. if (useDef->isRegExpMatcher() || useDef->isRegExpTester() || useDef->isRegExpSearcher()) { if (IsExclusiveNthOperand(useDef, 0, def)) continue; } else if (useDef->isLoadFixedSlot() || useDef->isTypeOf()) { continue; } else if (useDef->isCompare()) { if (CanCompareRegExp(useDef->toCompare(), def)) continue; } // Instructions that modifies `lastIndex` property. else if (useDef->isStoreFixedSlot()) { if (IsExclusiveNthOperand(useDef, 0, def)) { MStoreFixedSlot* store = useDef->toStoreFixedSlot(); if (store->slot() == RegExpObject::lastIndexSlot()) continue; } } else if (useDef->isSetPropertyCache()) { if (IsExclusiveNthOperand(useDef, 0, def)) { MSetPropertyCache* setProp = useDef->toSetPropertyCache(); if (setProp->idval()->isConstant()) { Value propIdVal = setProp->idval()->toConstant()->toJSValue(); if (propIdVal.isString()) { CompileRuntime* runtime = GetJitContext()->runtime; if (propIdVal.toString() == runtime->names().lastIndex) continue; } } } } // MCall is safe only for some known safe functions. else if (useDef->isCall()) { if (IsRegExpHoistableCall(useDef->toCall(), def)) continue; } // Everything else is unsafe. SetNotInWorklist(worklist); worklist.clear(); *hoistable = false; return true; } } SetNotInWorklist(worklist); worklist.clear(); *hoistable = true; return true; } bool 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 // pass in this case. if (graph.hasTryBlock()) return true; MDefinitionVector worklist(graph.alloc()); for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) { if (mir->shouldCancel("MakeMRegExpHoistable outer loop")) return false; for (MDefinitionIterator iter(*block); iter; iter++) { if (!*iter) MOZ_CRASH("confirm bug 1263794."); if (mir->shouldCancel("MakeMRegExpHoistable inner loop")) return false; if (!iter->isRegExp()) continue; MRegExp* regexp = iter->toRegExp(); bool hoistable = false; if (!IsRegExpHoistable(mir, regexp, worklist, &hoistable)) return false; if (!hoistable) continue; // Make MRegExp hoistable regexp->setMovable(); regexp->setDoNotClone(); // That would be incorrect for global/sticky, because lastIndex // could be wrong. Therefore setting the lastIndex to 0. That is // faster than a not movable regexp. RegExpObject* source = regexp->source(); if (source->sticky() || source->global()) { if (!graph.alloc().ensureBallast()) return false; MConstant* zero = MConstant::New(graph.alloc(), Int32Value(0)); regexp->block()->insertAfter(regexp, zero); MStoreFixedSlot* lastIndex = MStoreFixedSlot::New(graph.alloc(), regexp, RegExpObject::lastIndexSlot(), zero); regexp->block()->insertAfter(zero, lastIndex); } } } return true; } void jit::RenumberBlocks(MIRGraph& graph) { size_t id = 0; for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) block->setId(id++); } // A utility for code which deletes blocks. Renumber the remaining blocks, // recompute dominators, and optionally recompute AliasAnalysis dependencies. bool jit::AccountForCFGChanges(MIRGenerator* mir, MIRGraph& graph, bool updateAliasAnalysis, bool underValueNumberer) { // Renumber the blocks and clear out the old dominator info. size_t id = 0; for (ReversePostorderIterator i(graph.rpoBegin()), e(graph.rpoEnd()); i != e; ++i) { i->clearDominatorInfo(); i->setId(id++); } // Recompute dominator info. if (!BuildDominatorTree(graph)) return false; // If needed, update alias analysis dependencies. if (updateAliasAnalysis) { TraceLoggerThread* logger; if (GetJitContext()->onMainThread()) logger = TraceLoggerForMainThread(GetJitContext()->runtime); else logger = TraceLoggerForCurrentThread(); AutoTraceLog log(logger, TraceLogger_AliasAnalysis); if (JitOptions.disableFlowAA) { if (!AliasAnalysis(mir, graph).analyze()) return false; } else { if (!FlowAliasAnalysis(mir, graph).analyze()) return false; } } AssertExtendedGraphCoherency(graph, underValueNumberer); return true; } // Remove all blocks not marked with isMarked(). Unmark all remaining blocks. // Alias analysis dependencies may be invalid after calling this function. bool jit::RemoveUnmarkedBlocks(MIRGenerator* mir, MIRGraph& graph, uint32_t numMarkedBlocks) { if (numMarkedBlocks == graph.numBlocks()) { // If all blocks are marked, no blocks need removal. Just clear the // marks. We'll still need to update the dominator tree below though, // since we may have removed edges even if we didn't remove any blocks. graph.unmarkBlocks(); } else { // As we are going to remove edges and basic blocks, we have to mark // instructions which would be needed by baseline if we were to // bailout. for (PostorderIterator it(graph.poBegin()); it != graph.poEnd();) { MBasicBlock* block = *it++; if (block->isMarked()) continue; FlagAllOperandsAsHavingRemovedUses(mir, block); } // Find unmarked blocks and remove them. for (ReversePostorderIterator iter(graph.rpoBegin()); iter != graph.rpoEnd();) { MBasicBlock* block = *iter++; if (block->isMarked()) { block->unmark(); continue; } // The block is unreachable. Clear out the loop header flag, as // we're doing the sweep of a mark-and-sweep here, so we no longer // need to worry about whether an unmarked block is a loop or not. if (block->isLoopHeader()) block->clearLoopHeader(); for (size_t i = 0, e = block->numSuccessors(); i != e; ++i) block->getSuccessor(i)->removePredecessor(block); graph.removeBlockIncludingPhis(block); } } // Renumber the blocks and update the dominator tree. return AccountForCFGChanges(mir, graph, /*updateAliasAnalysis=*/false); } // A Simple, Fast Dominance Algorithm by Cooper et al. // Modified to support empty intersections for OSR, and in RPO. static MBasicBlock* IntersectDominators(MBasicBlock* block1, MBasicBlock* block2) { MBasicBlock* finger1 = block1; MBasicBlock* finger2 = block2; MOZ_ASSERT(finger1); MOZ_ASSERT(finger2); // In the original paper, the block ID comparisons are on the postorder index. // This implementation iterates in RPO, so the comparisons are reversed. // For this function to be called, the block must have multiple predecessors. // If a finger is then found to be self-dominating, it must therefore be // reachable from multiple roots through non-intersecting control flow. // nullptr is returned in this case, to denote an empty intersection. while (finger1->id() != finger2->id()) { while (finger1->id() > finger2->id()) { MBasicBlock* idom = finger1->immediateDominator(); if (idom == finger1) return nullptr; // Empty intersection. finger1 = idom; } while (finger2->id() > finger1->id()) { MBasicBlock* idom = finger2->immediateDominator(); if (idom == finger2) return nullptr; // Empty intersection. finger2 = idom; } } return finger1; } void jit::ClearDominatorTree(MIRGraph& graph) { for (MBasicBlockIterator iter = graph.begin(); iter != graph.end(); iter++) iter->clearDominatorInfo(); } static void ComputeImmediateDominators(MIRGraph& graph) { // The default start block is a root and therefore only self-dominates. MBasicBlock* startBlock = graph.entryBlock(); startBlock->setImmediateDominator(startBlock); // Any OSR block is a root and therefore only self-dominates. MBasicBlock* osrBlock = graph.osrBlock(); if (osrBlock) osrBlock->setImmediateDominator(osrBlock); bool changed = true; while (changed) { changed = false; ReversePostorderIterator block = graph.rpoBegin(); // For each block in RPO, intersect all dominators. for (; block != graph.rpoEnd(); block++) { // If a node has once been found to have no exclusive dominator, // it will never have an exclusive dominator, so it may be skipped. if (block->immediateDominator() == *block) continue; // A block with no predecessors is not reachable from any entry, so // it self-dominates. if (MOZ_UNLIKELY(block->numPredecessors() == 0)) { block->setImmediateDominator(*block); continue; } MBasicBlock* newIdom = block->getPredecessor(0); // Find the first common dominator. for (size_t i = 1; i < block->numPredecessors(); i++) { MBasicBlock* pred = block->getPredecessor(i); if (pred->immediateDominator() == nullptr) continue; newIdom = IntersectDominators(pred, newIdom); // If there is no common dominator, the block self-dominates. if (newIdom == nullptr) { block->setImmediateDominator(*block); changed = true; break; } } if (newIdom && block->immediateDominator() != newIdom) { block->setImmediateDominator(newIdom); changed = true; } } } #ifdef DEBUG // Assert that all blocks have dominator information. for (MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) { MOZ_ASSERT(block->immediateDominator() != nullptr); } #endif } bool jit::BuildDominatorTree(MIRGraph& graph) { ComputeImmediateDominators(graph); Vector worklist(graph.alloc()); // Traversing through the graph in post-order means that every non-phi use // of a definition is visited before the def itself. Since a def // dominates its uses, by the time we reach a particular // block, we have processed all of its dominated children, so // block->numDominated() is accurate. for (PostorderIterator i(graph.poBegin()); i != graph.poEnd(); i++) { MBasicBlock* child = *i; MBasicBlock* parent = child->immediateDominator(); // Dominance is defined such that blocks always dominate themselves. child->addNumDominated(1); // If the block only self-dominates, it has no definite parent. // Add it to the worklist as a root for pre-order traversal. // This includes all roots. Order does not matter. if (child == parent) { if (!worklist.append(child)) return false; continue; } if (!parent->addImmediatelyDominatedBlock(child)) return false; parent->addNumDominated(child->numDominated()); } #ifdef DEBUG // If compiling with OSR, many blocks will self-dominate. // Without OSR, there is only one root block which dominates all. if (!graph.osrBlock()) MOZ_ASSERT(graph.entryBlock()->numDominated() == graph.numBlocks()); #endif // Now, iterate through the dominator tree in pre-order and annotate every // block with its index in the traversal. size_t index = 0; while (!worklist.empty()) { MBasicBlock* block = worklist.popCopy(); block->setDomIndex(index); if (!worklist.append(block->immediatelyDominatedBlocksBegin(), block->immediatelyDominatedBlocksEnd())) { return false; } index++; } return true; } bool jit::BuildPhiReverseMapping(MIRGraph& graph) { // Build a mapping such that given a basic block, whose successor has one or // more phis, we can find our specific input to that phi. To make this fast // mapping work we rely on a specific property of our structured control // flow graph: For a block with phis, its predecessors each have only one // successor with phis. Consider each case: // * Blocks with less than two predecessors cannot have phis. // * Breaks. A break always has exactly one successor, and the break // catch block has exactly one predecessor for each break, as // well as a final predecessor for the actual loop exit. // * Continues. A continue always has exactly one successor, and the // continue catch block has exactly one predecessor for each // continue, as well as a final predecessor for the actual // loop continuation. The continue itself has exactly one // successor. // * An if. Each branch as exactly one predecessor. // * A switch. Each branch has exactly one predecessor. // * Loop tail. A new block is always created for the exit, and if a // break statement is present, the exit block will forward // directly to the break block. for (MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) { if (block->phisEmpty()) continue; // Assert on the above. for (size_t j = 0; j < block->numPredecessors(); j++) { MBasicBlock* pred = block->getPredecessor(j); #ifdef DEBUG size_t numSuccessorsWithPhis = 0; for (size_t k = 0; k < pred->numSuccessors(); k++) { MBasicBlock* successor = pred->getSuccessor(k); if (!successor->phisEmpty()) numSuccessorsWithPhis++; } MOZ_ASSERT(numSuccessorsWithPhis <= 1); #endif pred->setSuccessorWithPhis(*block, j); } } return true; } #ifdef DEBUG static bool CheckSuccessorImpliesPredecessor(MBasicBlock* A, MBasicBlock* B) { // Assuming B = succ(A), verify A = pred(B). for (size_t i = 0; i < B->numPredecessors(); i++) { if (A == B->getPredecessor(i)) return true; } return false; } static bool CheckPredecessorImpliesSuccessor(MBasicBlock* A, MBasicBlock* B) { // Assuming B = pred(A), verify A = succ(B). for (size_t i = 0; i < B->numSuccessors(); i++) { if (A == B->getSuccessor(i)) return true; } return false; } // If you have issues with the usesBalance assertions, then define the macro // _DEBUG_CHECK_OPERANDS_USES_BALANCE to spew information on the error output. // This output can then be processed with the following awk script to filter and // highlight which checks are missing or if there is an unexpected operand / // use. // // define _DEBUG_CHECK_OPERANDS_USES_BALANCE 1 /* $ ./js 2>stderr.log $ gawk ' /^==Check/ { context = ""; state = $2; } /^[a-z]/ { context = context "\n\t" $0; } /^==End/ { if (state == "Operand") { list[context] = list[context] - 1; } else if (state == "Use") { list[context] = list[context] + 1; } } END { for (ctx in list) { if (list[ctx] > 0) { print "Missing operand check", ctx, "\n" } if (list[ctx] < 0) { print "Missing use check", ctx, "\n" } }; }' < stderr.log */ static void CheckOperand(const MNode* consumer, const MUse* use, int32_t* usesBalance) { MOZ_ASSERT(use->hasProducer()); MDefinition* producer = use->producer(); MOZ_ASSERT(!producer->isDiscarded()); MOZ_ASSERT(producer->block() != nullptr); MOZ_ASSERT(use->consumer() == consumer); #ifdef _DEBUG_CHECK_OPERANDS_USES_BALANCE fprintf(stderr, "==Check Operand\n"); use->producer()->dump(stderr); fprintf(stderr, " index: %" PRIuSIZE "\n", use->consumer()->indexOf(use)); use->consumer()->dump(stderr); fprintf(stderr, "==End\n"); #endif --*usesBalance; } static void CheckUse(const MDefinition* producer, const MUse* use, int32_t* usesBalance) { MOZ_ASSERT(!use->consumer()->block()->isDead()); MOZ_ASSERT_IF(use->consumer()->isDefinition(), !use->consumer()->toDefinition()->isDiscarded()); MOZ_ASSERT(use->consumer()->block() != nullptr); MOZ_ASSERT(use->consumer()->getOperand(use->index()) == producer); #ifdef _DEBUG_CHECK_OPERANDS_USES_BALANCE fprintf(stderr, "==Check Use\n"); use->producer()->dump(stderr); fprintf(stderr, " index: %" PRIuSIZE "\n", use->consumer()->indexOf(use)); use->consumer()->dump(stderr); fprintf(stderr, "==End\n"); #endif ++*usesBalance; } // To properly encode entry resume points, we have to ensure that all the // operands of the entry resume point are located before the safeInsertTop // location. static void AssertOperandsBeforeSafeInsertTop(MResumePoint* resume) { MBasicBlock* block = resume->block(); if (block == block->graph().osrBlock()) return; MInstruction* stop = block->safeInsertTop(); for (size_t i = 0, e = resume->numOperands(); i < e; ++i) { MDefinition* def = resume->getOperand(i); if (def->block() != block) continue; if (def->isPhi()) continue; for (MInstructionIterator ins = block->begin(); true; ins++) { if (*ins == def) break; MOZ_ASSERT(*ins != stop, "Resume point operand located after the safeInsertTop location"); } } } #endif // DEBUG void jit::AssertBasicGraphCoherency(MIRGraph& graph) { #ifdef DEBUG MOZ_ASSERT(graph.entryBlock()->numPredecessors() == 0); MOZ_ASSERT(graph.entryBlock()->phisEmpty()); MOZ_ASSERT(!graph.entryBlock()->unreachable()); if (MBasicBlock* osrBlock = graph.osrBlock()) { MOZ_ASSERT(osrBlock->numPredecessors() == 0); MOZ_ASSERT(osrBlock->phisEmpty()); MOZ_ASSERT(osrBlock != graph.entryBlock()); MOZ_ASSERT(!osrBlock->unreachable()); } if (MResumePoint* resumePoint = graph.entryResumePoint()) MOZ_ASSERT(resumePoint->block() == graph.entryBlock()); // Assert successor and predecessor list coherency. uint32_t count = 0; int32_t usesBalance = 0; for (MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) { count++; MOZ_ASSERT(&block->graph() == &graph); MOZ_ASSERT(!block->isDead()); MOZ_ASSERT_IF(block->outerResumePoint() != nullptr, block->entryResumePoint() != nullptr); for (size_t i = 0; i < block->numSuccessors(); i++) MOZ_ASSERT(CheckSuccessorImpliesPredecessor(*block, block->getSuccessor(i))); for (size_t i = 0; i < block->numPredecessors(); i++) MOZ_ASSERT(CheckPredecessorImpliesSuccessor(*block, block->getPredecessor(i))); if (MResumePoint* resume = block->entryResumePoint()) { MOZ_ASSERT(!resume->instruction()); MOZ_ASSERT(resume->block() == *block); AssertOperandsBeforeSafeInsertTop(resume); } if (MResumePoint* resume = block->outerResumePoint()) { MOZ_ASSERT(!resume->instruction()); MOZ_ASSERT(resume->block() == *block); } for (MResumePointIterator iter(block->resumePointsBegin()); iter != block->resumePointsEnd(); iter++) { // We cannot yet assert that is there is no instruction then this is // the entry resume point because we are still storing resume points // in the InlinePropertyTable. MOZ_ASSERT_IF(iter->instruction(), iter->instruction()->block() == *block); for (uint32_t i = 0, e = iter->numOperands(); i < e; i++) CheckOperand(*iter, iter->getUseFor(i), &usesBalance); } for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd(); phi++) { MOZ_ASSERT(phi->numOperands() == block->numPredecessors()); MOZ_ASSERT(!phi->isRecoveredOnBailout()); MOZ_ASSERT(phi->type() != MIRType::None); MOZ_ASSERT(phi->dependency() == nullptr); } for (MDefinitionIterator iter(*block); iter; iter++) { MOZ_ASSERT(iter->block() == *block); MOZ_ASSERT_IF(iter->hasUses(), iter->type() != MIRType::None); MOZ_ASSERT(!iter->isDiscarded()); MOZ_ASSERT_IF(iter->isStart(), *block == graph.entryBlock() || *block == graph.osrBlock()); MOZ_ASSERT_IF(iter->isParameter(), *block == graph.entryBlock() || *block == graph.osrBlock()); MOZ_ASSERT_IF(iter->isOsrEntry(), *block == graph.osrBlock()); MOZ_ASSERT_IF(iter->isOsrValue(), *block == graph.osrBlock()); // Assert that use chains are valid for this instruction. for (uint32_t i = 0, end = iter->numOperands(); i < end; i++) CheckOperand(*iter, iter->getUseFor(i), &usesBalance); for (MUseIterator use(iter->usesBegin()); use != iter->usesEnd(); use++) CheckUse(*iter, *use, &usesBalance); if (iter->isInstruction()) { if (MResumePoint* resume = iter->toInstruction()->resumePoint()) { MOZ_ASSERT(resume->instruction() == *iter); MOZ_ASSERT(resume->block() == *block); MOZ_ASSERT(resume->block()->entryResumePoint() != nullptr); } } if (iter->isRecoveredOnBailout()) MOZ_ASSERT(!iter->hasLiveDefUses()); } // The control instruction is not visited by the MDefinitionIterator. MControlInstruction* control = block->lastIns(); MOZ_ASSERT(control->block() == *block); MOZ_ASSERT(!control->hasUses()); MOZ_ASSERT(control->type() == MIRType::None); MOZ_ASSERT(!control->isDiscarded()); MOZ_ASSERT(!control->isRecoveredOnBailout()); MOZ_ASSERT(control->resumePoint() == nullptr); for (uint32_t i = 0, end = control->numOperands(); i < end; i++) CheckOperand(control, control->getUseFor(i), &usesBalance); for (size_t i = 0; i < control->numSuccessors(); i++) MOZ_ASSERT(control->getSuccessor(i)); } // In case issues, see the _DEBUG_CHECK_OPERANDS_USES_BALANCE macro above. MOZ_ASSERT(usesBalance <= 0, "More use checks than operand checks"); MOZ_ASSERT(usesBalance >= 0, "More operand checks than use checks"); MOZ_ASSERT(graph.numBlocks() == count); #endif } #ifdef DEBUG static void AssertReversePostorder(MIRGraph& graph) { // Check that every block is visited after all its predecessors (except backedges). for (ReversePostorderIterator iter(graph.rpoBegin()); iter != graph.rpoEnd(); ++iter) { MBasicBlock* block = *iter; MOZ_ASSERT(!block->isMarked()); for (size_t i = 0; i < block->numPredecessors(); i++) { MBasicBlock* pred = block->getPredecessor(i); if (!pred->isMarked()) { MOZ_ASSERT(pred->isLoopBackedge()); MOZ_ASSERT(block->backedge() == pred); } } block->mark(); } graph.unmarkBlocks(); } #endif #ifdef DEBUG static void AssertDominatorTree(MIRGraph& graph) { // Check dominators. MOZ_ASSERT(graph.entryBlock()->immediateDominator() == graph.entryBlock()); if (MBasicBlock* osrBlock = graph.osrBlock()) MOZ_ASSERT(osrBlock->immediateDominator() == osrBlock); else MOZ_ASSERT(graph.entryBlock()->numDominated() == graph.numBlocks()); size_t i = graph.numBlocks(); size_t totalNumDominated = 0; for (MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) { MOZ_ASSERT(block->dominates(*block)); MBasicBlock* idom = block->immediateDominator(); MOZ_ASSERT(idom->dominates(*block)); MOZ_ASSERT(idom == *block || idom->id() < block->id()); if (idom == *block) { totalNumDominated += block->numDominated(); } else { bool foundInParent = false; for (size_t j = 0; j < idom->numImmediatelyDominatedBlocks(); j++) { if (idom->getImmediatelyDominatedBlock(j) == *block) { foundInParent = true; break; } } MOZ_ASSERT(foundInParent); } size_t numDominated = 1; for (size_t j = 0; j < block->numImmediatelyDominatedBlocks(); j++) { MBasicBlock* dom = block->getImmediatelyDominatedBlock(j); MOZ_ASSERT(block->dominates(dom)); MOZ_ASSERT(dom->id() > block->id()); MOZ_ASSERT(dom->immediateDominator() == *block); numDominated += dom->numDominated(); } MOZ_ASSERT(block->numDominated() == numDominated); MOZ_ASSERT(block->numDominated() <= i); MOZ_ASSERT(block->numSuccessors() != 0 || block->numDominated() == 1); i--; } MOZ_ASSERT(i == 0); MOZ_ASSERT(totalNumDominated == graph.numBlocks()); } #endif void jit::AssertGraphCoherency(MIRGraph& graph) { #ifdef DEBUG if (!JitOptions.checkGraphConsistency) return; AssertBasicGraphCoherency(graph); AssertReversePostorder(graph); #endif } #ifdef DEBUG static bool IsResumableMIRType(MIRType type) { // see CodeGeneratorShared::encodeAllocation switch (type) { case MIRType::Undefined: case MIRType::Null: case MIRType::Boolean: case MIRType::Int32: case MIRType::Double: case MIRType::Float32: case MIRType::String: case MIRType::Symbol: case MIRType::Object: case MIRType::MagicOptimizedArguments: case MIRType::MagicOptimizedOut: case MIRType::MagicUninitializedLexical: case MIRType::MagicIsConstructing: case MIRType::Value: case MIRType::Int32x4: case MIRType::Int16x8: case MIRType::Int8x16: case MIRType::Float32x4: case MIRType::Bool32x4: case MIRType::Bool16x8: case MIRType::Bool8x16: return true; case MIRType::MagicHole: case MIRType::ObjectOrNull: case MIRType::None: case MIRType::Slots: case MIRType::Elements: case MIRType::Pointer: case MIRType::Shape: case MIRType::ObjectGroup: case MIRType::Doublex2: // NYI, see also RSimdBox::recover case MIRType::SinCosDouble: case MIRType::Int64: return false; } MOZ_CRASH("Unknown MIRType."); } static void AssertResumableOperands(MNode* node) { for (size_t i = 0, e = node->numOperands(); i < e; ++i) { MDefinition* op = node->getOperand(i); if (op->isRecoveredOnBailout()) continue; MOZ_ASSERT(IsResumableMIRType(op->type()), "Resume point cannot encode its operands"); } } static void AssertIfResumableInstruction(MDefinition* def) { if (!def->isRecoveredOnBailout()) return; AssertResumableOperands(def); } static void AssertResumePointDominatedByOperands(MResumePoint* resume) { for (size_t i = 0, e = resume->numOperands(); i < e; ++i) { MDefinition* op = resume->getOperand(i); if (op->type() == MIRType::MagicOptimizedArguments) continue; MOZ_ASSERT(op->block()->dominates(resume->block()), "Resume point is not dominated by its operands"); } } #endif // DEBUG void jit::AssertExtendedGraphCoherency(MIRGraph& graph, bool underValueNumberer) { // Checks the basic GraphCoherency but also other conditions that // do not hold immediately (such as the fact that critical edges // are split) #ifdef DEBUG if (!JitOptions.checkGraphConsistency) return; AssertGraphCoherency(graph); AssertDominatorTree(graph); DebugOnly idx = 0; for (MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) { MOZ_ASSERT(block->id() == idx); ++idx; // No critical edges: if (block->numSuccessors() > 1) for (size_t i = 0; i < block->numSuccessors(); i++) MOZ_ASSERT(block->getSuccessor(i)->numPredecessors() == 1); if (block->isLoopHeader()) { if (underValueNumberer && block->numPredecessors() == 3) { // Fixup block. MOZ_ASSERT(block->getPredecessor(1)->numPredecessors() == 0); MOZ_ASSERT(graph.osrBlock(), "Fixup blocks should only exists if we have an osr block."); } else { MOZ_ASSERT(block->numPredecessors() == 2); } MBasicBlock* backedge = block->backedge(); MOZ_ASSERT(backedge->id() >= block->id()); MOZ_ASSERT(backedge->numSuccessors() == 1); MOZ_ASSERT(backedge->getSuccessor(0) == *block); } if (!block->phisEmpty()) { for (size_t i = 0; i < block->numPredecessors(); i++) { MBasicBlock* pred = block->getPredecessor(i); MOZ_ASSERT(pred->successorWithPhis() == *block); MOZ_ASSERT(pred->positionInPhiSuccessor() == i); } } uint32_t successorWithPhis = 0; for (size_t i = 0; i < block->numSuccessors(); i++) if (!block->getSuccessor(i)->phisEmpty()) successorWithPhis++; MOZ_ASSERT(successorWithPhis <= 1); MOZ_ASSERT((successorWithPhis != 0) == (block->successorWithPhis() != nullptr)); // Verify that phi operands dominate the corresponding CFG predecessor // edges. for (MPhiIterator iter(block->phisBegin()), end(block->phisEnd()); iter != end; ++iter) { MPhi* phi = *iter; for (size_t i = 0, e = phi->numOperands(); i < e; ++i) { // We sometimes see a phi with a magic-optimized-arguments // operand defined in the normal entry block, while the phi is // also reachable from the OSR entry (auto-regress/bug779818.js) if (phi->getOperand(i)->type() == MIRType::MagicOptimizedArguments) continue; MOZ_ASSERT(phi->getOperand(i)->block()->dominates(block->getPredecessor(i)), "Phi input is not dominated by its operand"); } } // Verify that instructions are dominated by their operands. for (MInstructionIterator iter(block->begin()), end(block->end()); iter != end; ++iter) { MInstruction* ins = *iter; for (size_t i = 0, e = ins->numOperands(); i < e; ++i) { MDefinition* op = ins->getOperand(i); MBasicBlock* opBlock = op->block(); MOZ_ASSERT(opBlock->dominates(*block), "Instruction is not dominated by its operands"); // If the operand is an instruction in the same block, check // that it comes first. if (opBlock == *block && !op->isPhi()) { MInstructionIterator opIter = block->begin(op->toInstruction()); do { ++opIter; MOZ_ASSERT(opIter != block->end(), "Operand in same block as instruction does not precede"); } while (*opIter != ins); } } AssertIfResumableInstruction(ins); if (MResumePoint* resume = ins->resumePoint()) { AssertResumePointDominatedByOperands(resume); AssertResumableOperands(resume); } } // Verify that the block resume points are dominated by their operands. if (MResumePoint* resume = block->entryResumePoint()) { AssertResumePointDominatedByOperands(resume); AssertResumableOperands(resume); } if (MResumePoint* resume = block->outerResumePoint()) { AssertResumePointDominatedByOperands(resume); AssertResumableOperands(resume); } } #endif } struct BoundsCheckInfo { MBoundsCheck* check; uint32_t validEnd; }; typedef HashMap, JitAllocPolicy> BoundsCheckMap; // Compute a hash for bounds checks which ignores constant offsets in the index. static HashNumber BoundsCheckHashIgnoreOffset(MBoundsCheck* check) { SimpleLinearSum indexSum = ExtractLinearSum(check->index()); uintptr_t index = indexSum.term ? uintptr_t(indexSum.term) : 0; uintptr_t length = uintptr_t(check->length()); return index ^ length; } static MBoundsCheck* FindDominatingBoundsCheck(BoundsCheckMap& checks, MBoundsCheck* check, size_t index) { // Since we are traversing the dominator tree in pre-order, when we // are looking at the |index|-th block, the next numDominated() blocks // we traverse are precisely the set of blocks that are dominated. // // So, this value is visible in all blocks if: // index <= index + ins->block->numDominated() // and becomes invalid after that. HashNumber hash = BoundsCheckHashIgnoreOffset(check); BoundsCheckMap::Ptr p = checks.lookup(hash); if (!p || index >= p->value().validEnd) { // We didn't find a dominating bounds check. BoundsCheckInfo info; info.check = check; info.validEnd = index + check->block()->numDominated(); if(!checks.put(hash, info)) return nullptr; return check; } return p->value().check; } static MathSpace ExtractMathSpace(MDefinition* ins) { MOZ_ASSERT(ins->isAdd() || ins->isSub()); MBinaryArithInstruction* arith = nullptr; if (ins->isAdd()) arith = ins->toAdd(); else arith = ins->toSub(); switch (arith->truncateKind()) { case MDefinition::NoTruncate: case MDefinition::TruncateAfterBailouts: // TruncateAfterBailouts is considered as infinite space because the // LinearSum will effectively remove the bailout check. return MathSpace::Infinite; case MDefinition::IndirectTruncate: case MDefinition::Truncate: return MathSpace::Modulo; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unknown TruncateKind"); } static bool MonotoneAdd(int32_t lhs, int32_t rhs) { return (lhs >= 0 && rhs >= 0) || (lhs <= 0 && rhs <= 0); } static bool MonotoneSub(int32_t lhs, int32_t rhs) { return (lhs >= 0 && rhs <= 0) || (lhs <= 0 && rhs >= 0); } // Extract a linear sum from ins, if possible (otherwise giving the sum 'ins + 0'). SimpleLinearSum jit::ExtractLinearSum(MDefinition* ins, MathSpace space) { if (ins->isBeta()) ins = ins->getOperand(0); if (ins->type() != MIRType::Int32) return SimpleLinearSum(ins, 0); if (ins->isConstant()) return SimpleLinearSum(nullptr, ins->toConstant()->toInt32()); if (!ins->isAdd() && !ins->isSub()) return SimpleLinearSum(ins, 0); // Only allow math which are in the same space. MathSpace insSpace = ExtractMathSpace(ins); if (space == MathSpace::Unknown) space = insSpace; else if (space != insSpace) return SimpleLinearSum(ins, 0); MOZ_ASSERT(space == MathSpace::Modulo || space == MathSpace::Infinite); MDefinition* lhs = ins->getOperand(0); MDefinition* rhs = ins->getOperand(1); if (lhs->type() != MIRType::Int32 || rhs->type() != MIRType::Int32) return SimpleLinearSum(ins, 0); // Extract linear sums of each operand. SimpleLinearSum lsum = ExtractLinearSum(lhs, space); SimpleLinearSum rsum = ExtractLinearSum(rhs, space); // LinearSum only considers a single term operand, if both sides have // terms, then ignore extracted linear sums. if (lsum.term && rsum.term) return SimpleLinearSum(ins, 0); // Check if this is of the form + n or n + . if (ins->isAdd()) { int32_t constant; if (space == MathSpace::Modulo) { constant = lsum.constant + rsum.constant; } else if (!SafeAdd(lsum.constant, rsum.constant, &constant) || !MonotoneAdd(lsum.constant, rsum.constant)) { return SimpleLinearSum(ins, 0); } return SimpleLinearSum(lsum.term ? lsum.term : rsum.term, constant); } MOZ_ASSERT(ins->isSub()); // Check if this is of the form - n. if (lsum.term) { int32_t constant; if (space == MathSpace::Modulo) { constant = lsum.constant - rsum.constant; } else if (!SafeSub(lsum.constant, rsum.constant, &constant) || !MonotoneSub(lsum.constant, rsum.constant)) { return SimpleLinearSum(ins, 0); } return SimpleLinearSum(lsum.term, constant); } // Ignore any of the form n - . return SimpleLinearSum(ins, 0); } // Extract a linear inequality holding when a boolean test goes in the // specified direction, of the form 'lhs + lhsN <= rhs' (or >=). bool jit::ExtractLinearInequality(MTest* test, BranchDirection direction, SimpleLinearSum* plhs, MDefinition** prhs, bool* plessEqual) { if (!test->getOperand(0)->isCompare()) return false; MCompare* compare = test->getOperand(0)->toCompare(); MDefinition* lhs = compare->getOperand(0); MDefinition* rhs = compare->getOperand(1); // TODO: optimize Compare_UInt32 if (!compare->isInt32Comparison()) return false; MOZ_ASSERT(lhs->type() == MIRType::Int32); MOZ_ASSERT(rhs->type() == MIRType::Int32); JSOp jsop = compare->jsop(); if (direction == FALSE_BRANCH) jsop = NegateCompareOp(jsop); SimpleLinearSum lsum = ExtractLinearSum(lhs); SimpleLinearSum rsum = ExtractLinearSum(rhs); if (!SafeSub(lsum.constant, rsum.constant, &lsum.constant)) return false; // Normalize operations to use <= or >=. switch (jsop) { case JSOP_LE: *plessEqual = true; break; case JSOP_LT: /* x < y ==> x + 1 <= y */ if (!SafeAdd(lsum.constant, 1, &lsum.constant)) return false; *plessEqual = true; break; case JSOP_GE: *plessEqual = false; break; case JSOP_GT: /* x > y ==> x - 1 >= y */ if (!SafeSub(lsum.constant, 1, &lsum.constant)) return false; *plessEqual = false; break; default: return false; } *plhs = lsum; *prhs = rsum.term; return true; } static bool TryEliminateBoundsCheck(BoundsCheckMap& checks, size_t blockIndex, MBoundsCheck* dominated, bool* eliminated) { MOZ_ASSERT(!*eliminated); // Replace all uses of the bounds check with the actual index. // This is (a) necessary, because we can coalesce two different // bounds checks and would otherwise use the wrong index and // (b) helps register allocation. Note that this is safe since // no other pass after bounds check elimination moves instructions. dominated->replaceAllUsesWith(dominated->index()); if (!dominated->isMovable()) return true; if (!dominated->fallible()) return true; MBoundsCheck* dominating = FindDominatingBoundsCheck(checks, dominated, blockIndex); if (!dominating) return false; if (dominating == dominated) { // We didn't find a dominating bounds check. return true; } // We found two bounds checks with the same hash number, but we still have // to make sure the lengths and index terms are equal. if (dominating->length() != dominated->length()) return true; SimpleLinearSum sumA = ExtractLinearSum(dominating->index()); SimpleLinearSum sumB = ExtractLinearSum(dominated->index()); // Both terms should be nullptr or the same definition. if (sumA.term != sumB.term) return true; // This bounds check is redundant. *eliminated = true; // Normalize the ranges according to the constant offsets in the two indexes. int32_t minimumA, maximumA, minimumB, maximumB; if (!SafeAdd(sumA.constant, dominating->minimum(), &minimumA) || !SafeAdd(sumA.constant, dominating->maximum(), &maximumA) || !SafeAdd(sumB.constant, dominated->minimum(), &minimumB) || !SafeAdd(sumB.constant, dominated->maximum(), &maximumB)) { return false; } // Update the dominating check to cover both ranges, denormalizing the // result per the constant offset in the index. int32_t newMinimum, newMaximum; if (!SafeSub(Min(minimumA, minimumB), sumA.constant, &newMinimum) || !SafeSub(Max(maximumA, maximumB), sumA.constant, &newMaximum)) { return false; } dominating->setMinimum(newMinimum); dominating->setMaximum(newMaximum); return true; } static void TryEliminateTypeBarrierFromTest(MTypeBarrier* barrier, bool filtersNull, bool filtersUndefined, MTest* test, BranchDirection direction, bool* eliminated) { MOZ_ASSERT(filtersNull || filtersUndefined); // Watch for code patterns similar to 'if (x.f) { ... = x.f }'. If x.f // is either an object or null/undefined, there will be a type barrier on // the latter read as the null/undefined value is never realized there. // The type barrier can be eliminated, however, by looking at tests // performed on the result of the first operation that filter out all // types that have been seen in the first access but not the second. // A test 'if (x.f)' filters both null and undefined. // Disregard the possible unbox added before the Typebarrier for checking. MDefinition* input = barrier->input(); MUnbox* inputUnbox = nullptr; if (input->isUnbox() && input->toUnbox()->mode() != MUnbox::Fallible) { inputUnbox = input->toUnbox(); input = inputUnbox->input(); } MDefinition* subject = nullptr; bool removeUndefined; bool removeNull; test->filtersUndefinedOrNull(direction == TRUE_BRANCH, &subject, &removeUndefined, &removeNull); // The Test doesn't filter undefined nor null. if (!subject) return; // Make sure the subject equals the input to the TypeBarrier. if (subject != input) return; // When the TypeBarrier filters undefined, the test must at least also do, // this, before the TypeBarrier can get removed. if (!removeUndefined && filtersUndefined) return; // When the TypeBarrier filters null, the test must at least also do, // this, before the TypeBarrier can get removed. if (!removeNull && filtersNull) return; // Eliminate the TypeBarrier. The possible TypeBarrier unboxing is kept, // but made infallible. *eliminated = true; if (inputUnbox) inputUnbox->makeInfallible(); barrier->replaceAllUsesWith(barrier->input()); } static bool TryEliminateTypeBarrier(MTypeBarrier* barrier, bool* eliminated) { MOZ_ASSERT(!*eliminated); const TemporaryTypeSet* barrierTypes = barrier->resultTypeSet(); const TemporaryTypeSet* inputTypes = barrier->input()->resultTypeSet(); // Disregard the possible unbox added before the Typebarrier. if (barrier->input()->isUnbox() && barrier->input()->toUnbox()->mode() != MUnbox::Fallible) inputTypes = barrier->input()->toUnbox()->input()->resultTypeSet(); if (!barrierTypes || !inputTypes) return true; bool filtersNull = barrierTypes->filtersType(inputTypes, TypeSet::NullType()); bool filtersUndefined = barrierTypes->filtersType(inputTypes, TypeSet::UndefinedType()); if (!filtersNull && !filtersUndefined) return true; MBasicBlock* block = barrier->block(); while (true) { BranchDirection direction; MTest* test = block->immediateDominatorBranch(&direction); if (test) { TryEliminateTypeBarrierFromTest(barrier, filtersNull, filtersUndefined, test, direction, eliminated); } MBasicBlock* previous = block->immediateDominator(); if (previous == block) break; block = previous; } return true; } static bool TryOptimizeLoadObjectOrNull(MDefinition* def, MDefinitionVector* peliminateList) { if (def->type() != MIRType::Value) return true; // Check if this definition can only produce object or null values. TemporaryTypeSet* types = def->resultTypeSet(); if (!types) return true; if (types->baseFlags() & ~(TYPE_FLAG_NULL | TYPE_FLAG_ANYOBJECT)) return true; MDefinitionVector eliminateList(def->block()->graph().alloc()); for (MUseDefIterator iter(def); iter; ++iter) { MDefinition* ndef = iter.def(); switch (ndef->op()) { case MDefinition::Op_Compare: if (ndef->toCompare()->compareType() != MCompare::Compare_Null) return true; break; case MDefinition::Op_Test: break; case MDefinition::Op_PostWriteBarrier: break; case MDefinition::Op_StoreFixedSlot: break; case MDefinition::Op_StoreSlot: break; case MDefinition::Op_ToObjectOrNull: if (!eliminateList.append(ndef->toToObjectOrNull())) return false; break; case MDefinition::Op_Unbox: if (ndef->type() != MIRType::Object) return true; break; case MDefinition::Op_TypeBarrier: // For now, only handle type barriers which are not consumed // anywhere and only test that the value is null. if (ndef->hasUses() || ndef->resultTypeSet()->getKnownMIRType() != MIRType::Null) return true; break; default: return true; } } // On punboxing systems we are better off leaving the value boxed if it // is only stored back to the heap. #ifdef JS_PUNBOX64 bool foundUse = false; for (MUseDefIterator iter(def); iter; ++iter) { MDefinition* ndef = iter.def(); if (!ndef->isStoreFixedSlot() && !ndef->isStoreSlot()) { foundUse = true; break; } } if (!foundUse) return true; #endif // JS_PUNBOX64 def->setResultType(MIRType::ObjectOrNull); // Fixup the result type of MTypeBarrier uses. for (MUseDefIterator iter(def); iter; ++iter) { MDefinition* ndef = iter.def(); if (ndef->isTypeBarrier()) ndef->setResultType(MIRType::ObjectOrNull); } // Eliminate MToObjectOrNull instruction uses. for (size_t i = 0; i < eliminateList.length(); i++) { MDefinition* ndef = eliminateList[i]; ndef->replaceAllUsesWith(def); if (!peliminateList->append(ndef)) return false; } return true; } static inline MDefinition* PassthroughOperand(MDefinition* def) { if (def->isConvertElementsToDoubles()) return def->toConvertElementsToDoubles()->elements(); if (def->isMaybeCopyElementsForWrite()) return def->toMaybeCopyElementsForWrite()->object(); return nullptr; } // Eliminate checks which are redundant given each other or other instructions. // // A type barrier is considered redundant if all missing types have been tested // for by earlier control instructions. // // A bounds check is considered redundant if it's dominated by another bounds // check with the same length and the indexes differ by only a constant amount. // In this case we eliminate the redundant bounds check and update the other one // to cover the ranges of both checks. // // Bounds checks are added to a hash map and since the hash function ignores // differences in constant offset, this offers a fast way to find redundant // checks. bool jit::EliminateRedundantChecks(MIRGraph& graph) { BoundsCheckMap checks(graph.alloc()); if (!checks.init()) return false; // Stack for pre-order CFG traversal. Vector worklist(graph.alloc()); // The index of the current block in the CFG traversal. size_t index = 0; // Add all self-dominating blocks to the worklist. // This includes all roots. Order does not matter. for (MBasicBlockIterator i(graph.begin()); i != graph.end(); i++) { MBasicBlock* block = *i; if (block->immediateDominator() == block) { if (!worklist.append(block)) return false; } } MDefinitionVector eliminateList(graph.alloc()); // Starting from each self-dominating block, traverse the CFG in pre-order. while (!worklist.empty()) { MBasicBlock* block = worklist.popCopy(); // Add all immediate dominators to the front of the worklist. if (!worklist.append(block->immediatelyDominatedBlocksBegin(), block->immediatelyDominatedBlocksEnd())) { return false; } for (MDefinitionIterator iter(block); iter; ) { MDefinition* def = *iter++; bool eliminated = false; switch (def->op()) { case MDefinition::Op_BoundsCheck: if (!TryEliminateBoundsCheck(checks, index, def->toBoundsCheck(), &eliminated)) return false; break; case MDefinition::Op_TypeBarrier: if (!TryEliminateTypeBarrier(def->toTypeBarrier(), &eliminated)) return false; break; case MDefinition::Op_LoadFixedSlot: case MDefinition::Op_LoadSlot: case MDefinition::Op_LoadUnboxedObjectOrNull: if (!TryOptimizeLoadObjectOrNull(def, &eliminateList)) return false; break; default: // Now that code motion passes have finished, replace // instructions which pass through one of their operands // (and perform additional checks) with that operand. if (MDefinition* passthrough = PassthroughOperand(def)) def->replaceAllUsesWith(passthrough); break; } if (eliminated) block->discardDef(def); } index++; } MOZ_ASSERT(index == graph.numBlocks()); for (size_t i = 0; i < eliminateList.length(); i++) { MDefinition* def = eliminateList[i]; def->block()->discardDef(def); } return true; } static bool NeedsKeepAlive(MInstruction* slotsOrElements, MInstruction* use) { MOZ_ASSERT(slotsOrElements->type() == MIRType::Elements || slotsOrElements->type() == MIRType::Slots); if (slotsOrElements->block() != use->block()) return true; MBasicBlock* block = use->block(); MInstructionIterator iter(block->begin(slotsOrElements)); MOZ_ASSERT(*iter == slotsOrElements); ++iter; while (true) { if (*iter == use) return false; switch (iter->op()) { case MDefinition::Op_Nop: case MDefinition::Op_Constant: case MDefinition::Op_KeepAliveObject: case MDefinition::Op_Unbox: case MDefinition::Op_LoadSlot: case MDefinition::Op_StoreSlot: case MDefinition::Op_LoadFixedSlot: case MDefinition::Op_StoreFixedSlot: case MDefinition::Op_LoadElement: case MDefinition::Op_StoreElement: case MDefinition::Op_InitializedLength: case MDefinition::Op_ArrayLength: case MDefinition::Op_BoundsCheck: iter++; break; default: return true; } } MOZ_CRASH("Unreachable"); } bool jit::AddKeepAliveInstructions(MIRGraph& graph) { for (MBasicBlockIterator i(graph.begin()); i != graph.end(); i++) { MBasicBlock* block = *i; for (MInstructionIterator insIter(block->begin()); insIter != block->end(); insIter++) { MInstruction* ins = *insIter; if (ins->type() != MIRType::Elements && ins->type() != MIRType::Slots) continue; MDefinition* ownerObject; switch (ins->op()) { case MDefinition::Op_ConstantElements: continue; case MDefinition::Op_ConvertElementsToDoubles: // EliminateRedundantChecks should have replaced all uses. MOZ_ASSERT(!ins->hasUses()); continue; case MDefinition::Op_Elements: case MDefinition::Op_TypedArrayElements: case MDefinition::Op_TypedObjectElements: MOZ_ASSERT(ins->numOperands() == 1); ownerObject = ins->getOperand(0); break; case MDefinition::Op_Slots: ownerObject = ins->toSlots()->object(); break; default: MOZ_CRASH("Unexpected op"); } MOZ_ASSERT(ownerObject->type() == MIRType::Object); if (ownerObject->isConstant()) { // Constants are kept alive by other pointers, for instance // ImmGCPtr in JIT code. continue; } for (MUseDefIterator uses(ins); uses; uses++) { MInstruction* use = uses.def()->toInstruction(); if (use->isStoreElementHole()) { // StoreElementHole has an explicit object operand. If GVN // is disabled, we can get different unbox instructions with // the same object as input, so we check for that case. MOZ_ASSERT_IF(!use->toStoreElementHole()->object()->isUnbox() && !ownerObject->isUnbox(), use->toStoreElementHole()->object() == ownerObject); continue; } if (use->isFallibleStoreElement()) { // See StoreElementHole case above. MOZ_ASSERT_IF(!use->toFallibleStoreElement()->object()->isUnbox() && !ownerObject->isUnbox(), use->toFallibleStoreElement()->object() == ownerObject); continue; } if (use->isInArray()) { // See StoreElementHole case above. MOZ_ASSERT_IF(!use->toInArray()->object()->isUnbox() && !ownerObject->isUnbox(), use->toInArray()->object() == ownerObject); continue; } if (!NeedsKeepAlive(ins, use)) continue; if (!graph.alloc().ensureBallast()) return false; MKeepAliveObject* keepAlive = MKeepAliveObject::New(graph.alloc(), ownerObject); use->block()->insertAfter(use, keepAlive); } } } return true; } bool LinearSum::multiply(int32_t scale) { for (size_t i = 0; i < terms_.length(); i++) { if (!SafeMul(scale, terms_[i].scale, &terms_[i].scale)) return false; } return SafeMul(scale, constant_, &constant_); } bool LinearSum::divide(uint32_t scale) { MOZ_ASSERT(scale > 0); for (size_t i = 0; i < terms_.length(); i++) { if (terms_[i].scale % scale != 0) return false; } if (constant_ % scale != 0) return false; for (size_t i = 0; i < terms_.length(); i++) terms_[i].scale /= scale; constant_ /= scale; return true; } bool LinearSum::add(const LinearSum& other, int32_t scale /* = 1 */) { for (size_t i = 0; i < other.terms_.length(); i++) { int32_t newScale = scale; if (!SafeMul(scale, other.terms_[i].scale, &newScale)) return false; if (!add(other.terms_[i].term, newScale)) return false; } int32_t newConstant = scale; if (!SafeMul(scale, other.constant_, &newConstant)) return false; return add(newConstant); } bool LinearSum::add(SimpleLinearSum other, int32_t scale) { if (other.term && !add(other.term, scale)) return false; int32_t constant; if (!SafeMul(other.constant, scale, &constant)) return false; return add(constant); } bool LinearSum::add(MDefinition* term, int32_t scale) { MOZ_ASSERT(term); if (scale == 0) return true; if (MConstant* termConst = term->maybeConstantValue()) { int32_t constant = termConst->toInt32(); if (!SafeMul(constant, scale, &constant)) return false; return add(constant); } for (size_t i = 0; i < terms_.length(); i++) { if (term == terms_[i].term) { if (!SafeAdd(scale, terms_[i].scale, &terms_[i].scale)) return false; if (terms_[i].scale == 0) { terms_[i] = terms_.back(); terms_.popBack(); } return true; } } AutoEnterOOMUnsafeRegion oomUnsafe; if (!terms_.append(LinearTerm(term, scale))) oomUnsafe.crash("LinearSum::add"); return true; } bool LinearSum::add(int32_t constant) { return SafeAdd(constant, constant_, &constant_); } void LinearSum::dump(GenericPrinter& out) const { for (size_t i = 0; i < terms_.length(); i++) { int32_t scale = terms_[i].scale; int32_t id = terms_[i].term->id(); MOZ_ASSERT(scale); if (scale > 0) { if (i) out.printf("+"); if (scale == 1) out.printf("#%d", id); else out.printf("%d*#%d", scale, id); } else if (scale == -1) { out.printf("-#%d", id); } else { out.printf("%d*#%d", scale, id); } } if (constant_ > 0) out.printf("+%d", constant_); else if (constant_ < 0) out.printf("%d", constant_); } void LinearSum::dump() const { Fprinter out(stderr); dump(out); out.finish(); } MDefinition* jit::ConvertLinearSum(TempAllocator& alloc, MBasicBlock* block, const LinearSum& sum, bool convertConstant) { MDefinition* def = nullptr; for (size_t i = 0; i < sum.numTerms(); i++) { LinearTerm term = sum.term(i); MOZ_ASSERT(!term.term->isConstant()); if (term.scale == 1) { if (def) { def = MAdd::New(alloc, def, term.term); def->toAdd()->setInt32Specialization(); block->insertAtEnd(def->toInstruction()); def->computeRange(alloc); } else { def = term.term; } } else if (term.scale == -1) { if (!def) { def = MConstant::New(alloc, Int32Value(0)); block->insertAtEnd(def->toInstruction()); def->computeRange(alloc); } def = MSub::New(alloc, def, term.term); def->toSub()->setInt32Specialization(); block->insertAtEnd(def->toInstruction()); def->computeRange(alloc); } else { MOZ_ASSERT(term.scale != 0); MConstant* factor = MConstant::New(alloc, Int32Value(term.scale)); block->insertAtEnd(factor); MMul* mul = MMul::New(alloc, term.term, factor); mul->setInt32Specialization(); block->insertAtEnd(mul); mul->computeRange(alloc); if (def) { def = MAdd::New(alloc, def, mul); def->toAdd()->setInt32Specialization(); block->insertAtEnd(def->toInstruction()); def->computeRange(alloc); } else { def = mul; } } } if (convertConstant && sum.constant()) { MConstant* constant = MConstant::New(alloc, Int32Value(sum.constant())); block->insertAtEnd(constant); constant->computeRange(alloc); if (def) { def = MAdd::New(alloc, def, constant); def->toAdd()->setInt32Specialization(); block->insertAtEnd(def->toInstruction()); def->computeRange(alloc); } else { def = constant; } } if (!def) { def = MConstant::New(alloc, Int32Value(0)); block->insertAtEnd(def->toInstruction()); def->computeRange(alloc); } return def; } MCompare* jit::ConvertLinearInequality(TempAllocator& alloc, MBasicBlock* block, const LinearSum& sum) { LinearSum lhs(sum); // Look for a term with a -1 scale which we can use for the rhs. MDefinition* rhsDef = nullptr; for (size_t i = 0; i < lhs.numTerms(); i++) { if (lhs.term(i).scale == -1) { AutoEnterOOMUnsafeRegion oomUnsafe; rhsDef = lhs.term(i).term; if (!lhs.add(rhsDef, 1)) oomUnsafe.crash("ConvertLinearInequality"); break; } } MDefinition* lhsDef = nullptr; JSOp op = JSOP_GE; do { if (!lhs.numTerms()) { lhsDef = MConstant::New(alloc, Int32Value(lhs.constant())); block->insertAtEnd(lhsDef->toInstruction()); lhsDef->computeRange(alloc); break; } lhsDef = ConvertLinearSum(alloc, block, lhs); if (lhs.constant() == 0) break; if (lhs.constant() == -1) { op = JSOP_GT; break; } if (!rhsDef) { int32_t constant = lhs.constant(); if (SafeMul(constant, -1, &constant)) { rhsDef = MConstant::New(alloc, Int32Value(constant)); block->insertAtEnd(rhsDef->toInstruction()); rhsDef->computeRange(alloc); break; } } MDefinition* constant = MConstant::New(alloc, Int32Value(lhs.constant())); block->insertAtEnd(constant->toInstruction()); constant->computeRange(alloc); lhsDef = MAdd::New(alloc, lhsDef, constant); lhsDef->toAdd()->setInt32Specialization(); block->insertAtEnd(lhsDef->toInstruction()); lhsDef->computeRange(alloc); } while (false); if (!rhsDef) { rhsDef = MConstant::New(alloc, Int32Value(0)); block->insertAtEnd(rhsDef->toInstruction()); rhsDef->computeRange(alloc); } MCompare* compare = MCompare::New(alloc, lhsDef, rhsDef, op); block->insertAtEnd(compare); compare->setCompareType(MCompare::Compare_Int32); return compare; } static bool AnalyzePoppedThis(JSContext* cx, DPAConstraintInfo& constraintInfo, ObjectGroup* group, MDefinition* thisValue, MInstruction* ins, bool definitelyExecuted, HandlePlainObject baseobj, Vector* initializerList, Vector* accessedProperties, bool* phandled) { // Determine the effect that a use of the |this| value when calling |new| // on a script has on the properties definitely held by the new object. if (ins->isCallSetProperty()) { MCallSetProperty* setprop = ins->toCallSetProperty(); if (setprop->object() != thisValue) return true; if (setprop->name() == cx->names().prototype || setprop->name() == cx->names().proto || setprop->name() == cx->names().constructor) { return true; } // Ignore assignments to properties that were already written to. if (baseobj->lookup(cx, NameToId(setprop->name()))) { *phandled = true; return true; } // Don't add definite properties for properties that were already // read in the constructor. for (size_t i = 0; i < accessedProperties->length(); i++) { if ((*accessedProperties)[i] == setprop->name()) return true; } // Assignments to new properties must always execute. if (!definitelyExecuted) return true; RootedId id(cx, NameToId(setprop->name())); bool added = false; if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, constraintInfo, group, id, &added)) { return false; } if (!added) { // The prototype chain already contains a getter/setter for this // property, or type information is too imprecise. return true; } // Add the property to the object, being careful not to update type information. DebugOnly slotSpan = baseobj->slotSpan(); MOZ_ASSERT(!baseobj->containsPure(id)); if (!NativeObject::addDataProperty(cx, baseobj, id, baseobj->slotSpan(), JSPROP_ENUMERATE)) return false; MOZ_ASSERT(baseobj->slotSpan() != slotSpan); MOZ_ASSERT(!baseobj->inDictionaryMode()); Vector callerResumePoints(cx); for (MResumePoint* rp = ins->block()->callerResumePoint(); rp; rp = rp->block()->callerResumePoint()) { if (!callerResumePoints.append(rp)) return false; } for (int i = callerResumePoints.length() - 1; i >= 0; i--) { MResumePoint* rp = callerResumePoints[i]; JSScript* script = rp->block()->info().script(); TypeNewScript::Initializer entry(TypeNewScript::Initializer::SETPROP_FRAME, script->pcToOffset(rp->pc())); if (!initializerList->append(entry)) return false; } JSScript* script = ins->block()->info().script(); TypeNewScript::Initializer entry(TypeNewScript::Initializer::SETPROP, script->pcToOffset(setprop->resumePoint()->pc())); if (!initializerList->append(entry)) return false; *phandled = true; return true; } if (ins->isCallGetProperty()) { MCallGetProperty* get = ins->toCallGetProperty(); /* * Properties can be read from the 'this' object if the following hold: * * - The read is not on a getter along the prototype chain, which * could cause 'this' to escape. * * - The accessed property is either already a definite property or * is not later added as one. Since the definite properties are * added to the object at the point of its creation, reading a * definite property before it is assigned could incorrectly hit. */ RootedId id(cx, NameToId(get->name())); if (!baseobj->lookup(cx, id) && !accessedProperties->append(get->name())) return false; bool added = false; if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, constraintInfo, group, id, &added)) { return false; } if (!added) { // The |this| value can escape if any property reads it does go // through a getter. return true; } *phandled = true; return true; } if (ins->isPostWriteBarrier()) { *phandled = true; return true; } return true; } static int CmpInstructions(const void* a, const void* b) { return (*static_cast(a))->id() - (*static_cast(b))->id(); } bool jit::AnalyzeNewScriptDefiniteProperties(JSContext* cx, DPAConstraintInfo& constraintInfo, HandleFunction fun, ObjectGroup* group, HandlePlainObject baseobj, Vector* initializerList) { MOZ_ASSERT(cx->zone()->types.activeAnalysis); // When invoking 'new' on the specified script, try to find some properties // which will definitely be added to the created object before it has a // chance to escape and be accessed elsewhere. RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun)); if (!script) return false; if (!jit::IsIonEnabled(cx) || !jit::IsBaselineEnabled(cx) || !script->canBaselineCompile()) return true; static const uint32_t MAX_SCRIPT_SIZE = 2000; if (script->length() > MAX_SCRIPT_SIZE) return true; TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, script); AutoTraceLog logScript(logger, event); AutoTraceLog logCompile(logger, TraceLogger_IonAnalysis); Vector accessedProperties(cx); LifoAlloc alloc(TempAllocator::PreferredLifoChunkSize); TempAllocator temp(&alloc); JitContext jctx(cx, &temp); if (!jit::CanLikelyAllocateMoreExecutableMemory()) return true; if (!cx->compartment()->ensureJitCompartmentExists(cx)) return false; if (!script->hasBaselineScript()) { MethodStatus status = BaselineCompile(cx, script); if (status == Method_Error) return false; if (status != Method_Compiled) return true; } TypeScript::SetThis(cx, script, TypeSet::ObjectType(group)); MIRGraph graph(&temp); InlineScriptTree* inlineScriptTree = InlineScriptTree::New(&temp, nullptr, nullptr, script); if (!inlineScriptTree) return false; CompileInfo info(script, fun, /* osrPc = */ nullptr, Analysis_DefiniteProperties, script->needsArgsObj(), inlineScriptTree); const OptimizationInfo* optimizationInfo = IonOptimizations.get(OptimizationLevel::Normal); CompilerConstraintList* constraints = NewCompilerConstraintList(temp); if (!constraints) { ReportOutOfMemory(cx); return false; } BaselineInspector inspector(script); const JitCompileOptions options(cx); IonBuilder builder(cx, CompileCompartment::get(cx->compartment()), options, &temp, &graph, constraints, &inspector, &info, optimizationInfo, /* baselineFrame = */ nullptr); if (!builder.build()) { if (cx->isThrowingOverRecursed() || cx->isThrowingOutOfMemory() || builder.abortReason() == AbortReason_Alloc) { return false; } MOZ_ASSERT(!cx->isExceptionPending()); return true; } FinishDefinitePropertiesAnalysis(cx, constraints); if (!SplitCriticalEdges(graph)) { ReportOutOfMemory(cx); return false; } RenumberBlocks(graph); if (!BuildDominatorTree(graph)) { ReportOutOfMemory(cx); return false; } if (!EliminatePhis(&builder, graph, AggressiveObservability)) { ReportOutOfMemory(cx); return false; } MDefinition* thisValue = graph.entryBlock()->getSlot(info.thisSlot()); // Get a list of instructions using the |this| value in the order they // appear in the graph. Vector instructions(cx); for (MUseDefIterator uses(thisValue); uses; uses++) { MDefinition* use = uses.def(); // Don't track |this| through assignments to phis. if (!use->isInstruction()) return true; if (!instructions.append(use->toInstruction())) return false; } // Sort the instructions to visit in increasing order. qsort(instructions.begin(), instructions.length(), sizeof(MInstruction*), CmpInstructions); // Find all exit blocks in the graph. Vector exitBlocks(cx); for (MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) { if (!block->numSuccessors() && !exitBlocks.append(*block)) return false; } // id of the last block which added a new property. size_t lastAddedBlock = 0; for (size_t i = 0; i < instructions.length(); i++) { MInstruction* ins = instructions[i]; // Track whether the use of |this| is in unconditional code, i.e. // the block dominates all graph exits. bool definitelyExecuted = true; for (size_t i = 0; i < exitBlocks.length(); i++) { for (MBasicBlock* exit = exitBlocks[i]; exit != ins->block(); exit = exit->immediateDominator()) { if (exit == exit->immediateDominator()) { definitelyExecuted = false; break; } } } // Also check to see if the instruction is inside a loop body. Even if // an access will always execute in the script, if it executes multiple // times then we can get confused when rolling back objects while // clearing the new script information. if (ins->block()->loopDepth() != 0) definitelyExecuted = false; bool handled = false; size_t slotSpan = baseobj->slotSpan(); if (!AnalyzePoppedThis(cx, constraintInfo, group, thisValue, ins, definitelyExecuted, baseobj, initializerList, &accessedProperties, &handled)) { return false; } if (!handled) break; if (slotSpan != baseobj->slotSpan()) { MOZ_ASSERT(ins->block()->id() >= lastAddedBlock); lastAddedBlock = ins->block()->id(); } } if (baseobj->slotSpan() != 0) { // We found some definite properties, but their correctness is still // contingent on the correct frames being inlined. Add constraints to // invalidate the definite properties if additional functions could be // called at the inline frame sites. for (MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) { // Inlining decisions made after the last new property was added to // the object don't need to be frozen. if (block->id() > lastAddedBlock) break; if (MResumePoint* rp = block->callerResumePoint()) { if (block->numPredecessors() == 1 && block->getPredecessor(0) == rp->block()) { JSScript* caller = rp->block()->info().script(); JSScript* callee = block->info().script(); if (!constraintInfo.addInliningConstraint(caller, callee)) { return false; } } } } } return true; } static bool ArgumentsUseCanBeLazy(JSContext* cx, JSScript* script, MInstruction* ins, size_t index, bool* argumentsContentsObserved) { // We can read the frame's arguments directly for f.apply(x, arguments). if (ins->isCall()) { if (*ins->toCall()->resumePoint()->pc() == JSOP_FUNAPPLY && ins->toCall()->numActualArgs() == 2 && index == MCall::IndexOfArgument(1)) { *argumentsContentsObserved = true; return true; } } // arguments[i] can read fp->canonicalActualArg(i) directly. if (ins->isCallGetElement() && index == 0) { *argumentsContentsObserved = true; return true; } // MGetArgumentsObjectArg needs to be considered as a use that allows laziness. if (ins->isGetArgumentsObjectArg() && index == 0) return true; // arguments.length length can read fp->numActualArgs() directly. // arguments.callee can read fp->callee() directly if the arguments object // is mapped. if (ins->isCallGetProperty() && index == 0 && (ins->toCallGetProperty()->name() == cx->names().length || (script->hasMappedArgsObj() && ins->toCallGetProperty()->name() == cx->names().callee))) { return true; } return false; } bool jit::AnalyzeArgumentsUsage(JSContext* cx, JSScript* scriptArg) { RootedScript script(cx, scriptArg); AutoEnterAnalysis enter(cx); MOZ_ASSERT(!script->analyzedArgsUsage()); // Treat the script as needing an arguments object until we determine it // does not need one. This both allows us to easily see where the arguments // object can escape through assignments to the function's named arguments, // and also simplifies handling of early returns. script->setNeedsArgsObj(true); // Always construct arguments objects when in debug mode, for generator // scripts (generators can be suspended when speculation fails) or when // direct eval is present. // // FIXME: Don't build arguments for ES6 generator expressions. if (scriptArg->isDebuggee() || script->isGenerator() || script->bindingsAccessedDynamically()) return true; if (!jit::IsIonEnabled(cx)) return true; static const uint32_t MAX_SCRIPT_SIZE = 10000; if (script->length() > MAX_SCRIPT_SIZE) return true; if (!script->ensureHasTypes(cx)) return false; TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, script); AutoTraceLog logScript(logger, event); AutoTraceLog logCompile(logger, TraceLogger_IonAnalysis); LifoAlloc alloc(TempAllocator::PreferredLifoChunkSize); TempAllocator temp(&alloc); JitContext jctx(cx, &temp); if (!jit::CanLikelyAllocateMoreExecutableMemory()) return true; if (!cx->compartment()->ensureJitCompartmentExists(cx)) return false; MIRGraph graph(&temp); InlineScriptTree* inlineScriptTree = InlineScriptTree::New(&temp, nullptr, nullptr, script); if (!inlineScriptTree) { ReportOutOfMemory(cx); return false; } CompileInfo info(script, script->functionNonDelazifying(), /* osrPc = */ nullptr, Analysis_ArgumentsUsage, /* needsArgsObj = */ true, inlineScriptTree); const OptimizationInfo* optimizationInfo = IonOptimizations.get(OptimizationLevel::Normal); CompilerConstraintList* constraints = NewCompilerConstraintList(temp); if (!constraints) { ReportOutOfMemory(cx); return false; } BaselineInspector inspector(script); const JitCompileOptions options(cx); IonBuilder builder(nullptr, CompileCompartment::get(cx->compartment()), options, &temp, &graph, constraints, &inspector, &info, optimizationInfo, /* baselineFrame = */ nullptr); if (!builder.build()) { if (cx->isThrowingOverRecursed() || builder.abortReason() == AbortReason_Alloc) return false; MOZ_ASSERT(!cx->isExceptionPending()); return true; } if (!SplitCriticalEdges(graph)) { ReportOutOfMemory(cx); return false; } RenumberBlocks(graph); if (!BuildDominatorTree(graph)) { ReportOutOfMemory(cx); return false; } if (!EliminatePhis(&builder, graph, AggressiveObservability)) { ReportOutOfMemory(cx); return false; } MDefinition* argumentsValue = graph.entryBlock()->getSlot(info.argsObjSlot()); bool argumentsContentsObserved = false; for (MUseDefIterator uses(argumentsValue); uses; uses++) { MDefinition* use = uses.def(); // Don't track |arguments| through assignments to phis. if (!use->isInstruction()) return true; if (!ArgumentsUseCanBeLazy(cx, script, use->toInstruction(), use->indexOf(uses.use()), &argumentsContentsObserved)) { return true; } } // If a script explicitly accesses the contents of 'arguments', and has // formals which may be stored as part of a call object, don't use lazy // arguments. The compiler can then assume that accesses through // arguments[i] will be on unaliased variables. if (script->funHasAnyAliasedFormal() && argumentsContentsObserved) return true; script->setNeedsArgsObj(false); return true; } // Mark all the blocks that are in the loop with the given header. // Returns the number of blocks marked. Set *canOsr to true if the loop is // reachable from both the normal entry and the OSR entry. size_t jit::MarkLoopBlocks(MIRGraph& graph, MBasicBlock* header, bool* canOsr) { #ifdef DEBUG for (ReversePostorderIterator i = graph.rpoBegin(), e = graph.rpoEnd(); i != e; ++i) MOZ_ASSERT(!i->isMarked(), "Some blocks already marked"); #endif MBasicBlock* osrBlock = graph.osrBlock(); *canOsr = false; // The blocks are in RPO; start at the loop backedge, which marks the bottom // of the loop, and walk up until we get to the header. Loops may be // discontiguous, so we trace predecessors to determine which blocks are // actually part of the loop. The backedge is always part of the loop, and // so are its predecessors, transitively, up to the loop header or an OSR // entry. MBasicBlock* backedge = header->backedge(); backedge->mark(); size_t numMarked = 1; for (PostorderIterator i = graph.poBegin(backedge); ; ++i) { MOZ_ASSERT(i != graph.poEnd(), "Reached the end of the graph while searching for the loop header"); MBasicBlock* block = *i; // If we've reached the loop header, we're done. if (block == header) break; // A block not marked by the time we reach it is not in the loop. if (!block->isMarked()) continue; // This block is in the loop; trace to its predecessors. for (size_t p = 0, e = block->numPredecessors(); p != e; ++p) { MBasicBlock* pred = block->getPredecessor(p); if (pred->isMarked()) continue; // Blocks dominated by the OSR entry are not part of the loop // (unless they aren't reachable from the normal entry). if (osrBlock && pred != header && osrBlock->dominates(pred) && !osrBlock->dominates(header)) { *canOsr = true; continue; } MOZ_ASSERT(pred->id() >= header->id() && pred->id() <= backedge->id(), "Loop block not between loop header and loop backedge"); pred->mark(); ++numMarked; // A nested loop may not exit back to the enclosing loop at its // bottom. If we just marked its header, then the whole nested loop // is part of the enclosing loop. if (pred->isLoopHeader()) { MBasicBlock* innerBackedge = pred->backedge(); if (!innerBackedge->isMarked()) { // Mark its backedge so that we add all of its blocks to the // outer loop as we walk upwards. innerBackedge->mark(); ++numMarked; // If the nested loop is not contiguous, we may have already // passed its backedge. If this happens, back up. if (backedge->id() > block->id()) { i = graph.poBegin(innerBackedge); --i; } } } } } // If there's no path connecting the header to the backedge, then this isn't // actually a loop. This can happen when the code starts with a loop but GVN // folds some branches away. if (!header->isMarked()) { jit::UnmarkLoopBlocks(graph, header); return 0; } return numMarked; } // Unmark all the blocks that are in the loop with the given header. void jit::UnmarkLoopBlocks(MIRGraph& graph, MBasicBlock* header) { MBasicBlock* backedge = header->backedge(); for (ReversePostorderIterator i = graph.rpoBegin(header); ; ++i) { MOZ_ASSERT(i != graph.rpoEnd(), "Reached the end of the graph while searching for the backedge"); MBasicBlock* block = *i; if (block->isMarked()) { block->unmark(); if (block == backedge) break; } } #ifdef DEBUG for (ReversePostorderIterator i = graph.rpoBegin(), e = graph.rpoEnd(); i != e; ++i) MOZ_ASSERT(!i->isMarked(), "Not all blocks got unmarked"); #endif } // Reorder the blocks in the loop starting at the given header to be contiguous. static void MakeLoopContiguous(MIRGraph& graph, MBasicBlock* header, size_t numMarked) { MBasicBlock* backedge = header->backedge(); MOZ_ASSERT(header->isMarked(), "Loop header is not part of loop"); MOZ_ASSERT(backedge->isMarked(), "Loop backedge is not part of loop"); // If there are any blocks between the loop header and the loop backedge // that are not part of the loop, prepare to move them to the end. We keep // them in order, which preserves RPO. ReversePostorderIterator insertIter = graph.rpoBegin(backedge); insertIter++; MBasicBlock* insertPt = *insertIter; // Visit all the blocks from the loop header to the loop backedge. size_t headerId = header->id(); size_t inLoopId = headerId; size_t notInLoopId = inLoopId + numMarked; ReversePostorderIterator i = graph.rpoBegin(header); for (;;) { MBasicBlock* block = *i++; MOZ_ASSERT(block->id() >= header->id() && block->id() <= backedge->id(), "Loop backedge should be last block in loop"); if (block->isMarked()) { // This block is in the loop. block->unmark(); block->setId(inLoopId++); // If we've reached the loop backedge, we're done! if (block == backedge) break; } else { // This block is not in the loop. Move it to the end. graph.moveBlockBefore(insertPt, block); block->setId(notInLoopId++); } } MOZ_ASSERT(header->id() == headerId, "Loop header id changed"); MOZ_ASSERT(inLoopId == headerId + numMarked, "Wrong number of blocks kept in loop"); MOZ_ASSERT(notInLoopId == (insertIter != graph.rpoEnd() ? insertPt->id() : graph.numBlocks()), "Wrong number of blocks moved out of loop"); } // Reorder the blocks in the graph so that loops are contiguous. bool jit::MakeLoopsContiguous(MIRGraph& graph) { // Visit all loop headers (in any order). for (MBasicBlockIterator i(graph.begin()); i != graph.end(); i++) { MBasicBlock* header = *i; if (!header->isLoopHeader()) continue; // Mark all blocks that are actually part of the loop. bool canOsr; size_t numMarked = MarkLoopBlocks(graph, header, &canOsr); // If the loop isn't a loop, don't try to optimize it. if (numMarked == 0) continue; // If there's an OSR block entering the loop in the middle, it's tricky, // so don't try to handle it, for now. if (canOsr) { UnmarkLoopBlocks(graph, header); continue; } // Move all blocks between header and backedge that aren't marked to // the end of the loop, making the loop itself contiguous. MakeLoopContiguous(graph, header, numMarked); } return true; } MRootList::MRootList(TempAllocator& alloc) { #define INIT_VECTOR(name, _0, _1) \ roots_[JS::RootKind::name].emplace(alloc); JS_FOR_EACH_TRACEKIND(INIT_VECTOR) #undef INIT_VECTOR } template static void TraceVector(JSTracer* trc, const MRootList::RootVector& vector, const char* name) { for (auto ptr : vector) { T ptrT = static_cast(ptr); TraceManuallyBarrieredEdge(trc, &ptrT, name); MOZ_ASSERT(ptr == ptrT, "Shouldn't move without updating MIR pointers"); } } void MRootList::trace(JSTracer* trc) { #define TRACE_ROOTS(name, type, _) \ TraceVector(trc, *roots_[JS::RootKind::name], "mir-root-" #name); JS_FOR_EACH_TRACEKIND(TRACE_ROOTS) #undef TRACE_ROOTS } MOZ_MUST_USE bool jit::CreateMIRRootList(IonBuilder& builder) { MOZ_ASSERT(!builder.info().isAnalysis()); TempAllocator& alloc = builder.alloc(); MIRGraph& graph = builder.graph(); MRootList* roots = new(alloc.fallible()) MRootList(alloc); if (!roots) return false; JSScript* prevScript = nullptr; for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) { JSScript* script = block->info().script(); if (script != prevScript) { if (!roots->append(script)) return false; prevScript = script; } for (MInstructionIterator iter(block->begin()), end(block->end()); iter != end; iter++) { if (!iter->appendRoots(*roots)) return false; } } builder.setRootList(*roots); return true; } static void DumpDefinition(GenericPrinter& out, MDefinition* def, size_t depth) { MDefinition::PrintOpcodeName(out, def->op()); if (depth == 0) return; for (size_t i = 0; i < def->numOperands(); i++) { out.printf(" ("); DumpDefinition(out, def->getOperand(i), depth - 1); out.printf(")"); } } void jit::DumpMIRExpressions(MIRGraph& graph) { if (!JitSpewEnabled(JitSpew_MIRExpressions)) return; size_t depth = 2; Fprinter& out = JitSpewPrinter(); for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) { for (MInstructionIterator iter(block->begin()), end(block->end()); iter != end; iter++) { DumpDefinition(out, *iter, depth); out.printf("\n"); } } }