MDefinition * MIRGraph::forkJoinContext() { // Search the entry block to find a ForkJoinContext instruction. If we do // not find one, add one after the Start instruction. // // Note: the original design used a field in MIRGraph to cache the // forkJoinContext rather than searching for it again. However, this // could become out of date due to DCE. Given that we do not generally // have to search very far to find the ForkJoinContext instruction if it // exists, and that we don't look for it that often, I opted to simply // eliminate the cache and search anew each time, so that it is that much // easier to keep the IR coherent. - nmatsakis MBasicBlock *entry = entryBlock(); JS_ASSERT(entry->info().executionMode() == ParallelExecution); MInstruction *start = nullptr; for (MInstructionIterator ins(entry->begin()); ins != entry->end(); ins++) { if (ins->isForkJoinContext()) return *ins; else if (ins->isStart()) start = *ins; } JS_ASSERT(start); MForkJoinContext *cx = MForkJoinContext::New(alloc()); entry->insertAfter(start, cx); return cx; }
MDefinition * MIRGraph::parSlice() { // Search the entry block to find a par slice instruction. If we do not // find one, add one after the Start instruction. // // Note: the original design used a field in MIRGraph to cache the // parSlice rather than searching for it again. However, this // could become out of date due to DCE. Given that we do not // generally have to search very far to find the par slice // instruction if it exists, and that we don't look for it that // often, I opted to simply eliminate the cache and search anew // each time, so that it is that much easier to keep the IR // coherent. - nmatsakis MBasicBlock *entry = entryBlock(); JS_ASSERT(entry->info().executionMode() == ParallelExecution); MInstruction *start = NULL; for (MInstructionIterator ins(entry->begin()); ins != entry->end(); ins++) { if (ins->isParSlice()) return *ins; else if (ins->isStart()) start = *ins; } JS_ASSERT(start); MParSlice *parSlice = new MParSlice(); entry->insertAfter(start, parSlice); return parSlice; }
MControlInstruction * ValueNumberer::simplifyControlInstruction(MControlInstruction *def) { if (def->isEffectful()) return def; MDefinition *repl = def->foldsTo(alloc(), false); if (repl == def) return def; // Ensure this instruction has a value number. if (!repl->valueNumberData()) repl->setValueNumberData(new(alloc()) ValueNumberData); MBasicBlock *block = def->block(); // MControlInstructions should not have consumers. JS_ASSERT(repl->isControlInstruction()); JS_ASSERT(!def->hasUses()); if (def->isInWorklist()) repl->setInWorklist(); block->discardLastIns(); block->end(repl->toControlInstruction()); return repl->toControlInstruction(); }
// Discard |def| and mine its operands for any subsequently dead defs. bool ValueNumberer::discardDef(MDefinition* def) { #ifdef JS_JITSPEW JitSpew(JitSpew_GVN, " Discarding %s %s%u", def->block()->isMarked() ? "unreachable" : "dead", def->opName(), def->id()); #endif #ifdef DEBUG MOZ_ASSERT(def != nextDef_, "Invalidating the MDefinition iterator"); if (def->block()->isMarked()) { MOZ_ASSERT(!def->hasUses(), "Discarding def that still has uses"); } else { MOZ_ASSERT(IsDiscardable(def), "Discarding non-discardable definition"); MOZ_ASSERT(!values_.has(def), "Discarding a definition still in the set"); } #endif MBasicBlock* block = def->block(); if (def->isPhi()) { MPhi* phi = def->toPhi(); if (!releaseAndRemovePhiOperands(phi)) return false; block->discardPhi(phi); } else { MInstruction* ins = def->toInstruction(); if (MResumePoint* resume = ins->resumePoint()) { if (!releaseResumePointOperands(resume)) return false; } if (!releaseOperands(ins)) return false; block->discardIgnoreOperands(ins); } // If that was the last definition in the block, it can be safely removed // from the graph. if (block->phisEmpty() && block->begin() == block->end()) { MOZ_ASSERT(block->isMarked(), "Reachable block lacks at least a control instruction"); // As a special case, don't remove a block which is a dominator tree // root so that we don't invalidate the iterator in visitGraph. We'll // check for this and remove it later. if (block->immediateDominator() != block) { JitSpew(JitSpew_GVN, " Block block%u is now empty; discarding", block->id()); graph_.removeBlock(block); blocksRemoved_ = true; } else { JitSpew(JitSpew_GVN, " Dominator root block%u is now empty; will discard later", block->id()); } } return true; }
bool UnreachableCodeElimination::removeUnmarkedBlocksAndClearDominators() { // Removes blocks that are not marked from the graph. For blocks // that *are* marked, clears the mark and adjusts the id to its // new value. Also adds blocks that are immediately reachable // from an unmarked block to the frontier. size_t id = marked_; for (PostorderIterator iter(graph_.poBegin()); iter != graph_.poEnd();) { if (mir_->shouldCancel("Eliminate Unreachable Code")) return false; MBasicBlock *block = *iter; iter++; // Unconditionally clear the dominators. It's somewhat complex to // adjust the values and relatively fast to just recompute. block->clearDominatorInfo(); if (block->isMarked()) { block->setId(--id); for (MPhiIterator iter(block->phisBegin()); iter != block->phisEnd(); iter++) checkDependencyAndRemoveUsesFromUnmarkedBlocks(*iter); for (MInstructionIterator iter(block->begin()); iter != block->end(); iter++) checkDependencyAndRemoveUsesFromUnmarkedBlocks(*iter); } else { for (size_t i = 0, c = block->numSuccessors(); i < c; i++) { MBasicBlock *succ = block->getSuccessor(i); if (succ->isMarked()) { // succ is on the frontier of blocks to be removed: succ->removePredecessor(block); if (!redundantPhis_) { for (MPhiIterator iter(succ->phisBegin()); iter != succ->phisEnd(); iter++) { if (iter->operandIfRedundant()) { redundantPhis_ = true; break; } } } } } graph_.removeBlock(block); } } JS_ASSERT(id == 0); return true; }
MBasicBlock * MBasicBlock::NewParBailout(MIRGraph &graph, CompileInfo &info, MBasicBlock *pred, jsbytecode *entryPc) { MBasicBlock *block = MBasicBlock::New(graph, info, pred, entryPc, NORMAL); if (!block) return NULL; MParBailout *bailout = new MParBailout(); if (!bailout) return NULL; block->end(bailout); return block; }
// A loop is about to be made reachable only through an OSR entry into one of // its nested loops. Fix everything up. bool ValueNumberer::fixupOSROnlyLoop(MBasicBlock* block, MBasicBlock* backedge) { // Create an empty and unreachable(!) block which jumps to |block|. This // allows |block| to remain marked as a loop header, so we don't have to // worry about moving a different block into place as the new loop header, // which is hard, especially if the OSR is into a nested loop. Doing all // that would produce slightly more optimal code, but this is so // extraordinarily rare that it isn't worth the complexity. MBasicBlock* fake = MBasicBlock::New(graph_, block->info(), nullptr, MBasicBlock::NORMAL); if (fake == nullptr) return false; graph_.insertBlockBefore(block, fake); fake->setImmediateDominator(fake); fake->addNumDominated(1); fake->setDomIndex(fake->id()); fake->setUnreachable(); // Create zero-input phis to use as inputs for any phis in |block|. // Again, this is a little odd, but it's the least-odd thing we can do // without significant complexity. for (MPhiIterator iter(block->phisBegin()), end(block->phisEnd()); iter != end; ++iter) { MPhi* phi = *iter; MPhi* fakePhi = MPhi::New(graph_.alloc(), phi->type()); fake->addPhi(fakePhi); if (!phi->addInputSlow(fakePhi)) return false; } fake->end(MGoto::New(graph_.alloc(), block)); if (!block->addPredecessorWithoutPhis(fake)) return false; // Restore |backedge| as |block|'s loop backedge. block->clearLoopHeader(); block->setLoopHeader(backedge); JitSpew(JitSpew_GVN, " Created fake block%u", fake->id()); hasOSRFixups_ = true; return true; }
// Visit all the blocks in the graph. bool ValueNumberer::visitGraph() { // Due to OSR blocks, the set of blocks dominated by a blocks may not be // contiguous in the RPO. Do a separate traversal for each dominator tree // root. There's always the main entry, and sometimes there's an OSR entry, // and then there are the roots formed where the OSR paths merge with the // main entry paths. for (ReversePostorderIterator iter(graph_.rpoBegin()); ; ) { MOZ_ASSERT(iter != graph_.rpoEnd(), "Inconsistent dominator information"); MBasicBlock* block = *iter; if (block->immediateDominator() == block) { if (!visitDominatorTree(block)) return false; // Normally unreachable blocks would be removed by now, but if this // block is a dominator tree root, it has been special-cased and left // in place in order to avoid invalidating our iterator. Now that // we've finished the tree, increment the iterator, and then if it's // marked for removal, remove it. ++iter; if (block->isMarked()) { JitSpew(JitSpew_GVN, " Discarding dominator root block%u", block->id()); MOZ_ASSERT(block->begin() == block->end(), "Unreachable dominator tree root has instructions after tree walk"); MOZ_ASSERT(block->phisEmpty(), "Unreachable dominator tree root has phis after tree walk"); graph_.removeBlock(block); blocksRemoved_ = true; } MOZ_ASSERT(totalNumVisited_ <= graph_.numBlocks(), "Visited blocks too many times"); if (totalNumVisited_ >= graph_.numBlocks()) break; } else { // This block a dominator tree root. Proceed to the next one. ++iter; } } totalNumVisited_ = 0; return true; }
MBasicBlock * MBasicBlock::NewAbortPar(MIRGraph &graph, CompileInfo &info, MBasicBlock *pred, jsbytecode *entryPc, MResumePoint *resumePoint) { MBasicBlock *block = new MBasicBlock(graph, info, entryPc, NORMAL); resumePoint->block_ = block; block->entryResumePoint_ = resumePoint; if (!block->init()) return NULL; if (!block->addPredecessorWithoutPhis(pred)) return NULL; block->end(new MAbortPar()); return block; }
MBasicBlock * MBasicBlock::NewAbortPar(MIRGraph &graph, CompileInfo &info, MBasicBlock *pred, const BytecodeSite &site, MResumePoint *resumePoint) { MBasicBlock *block = new(graph.alloc()) MBasicBlock(graph, info, site, NORMAL); resumePoint->block_ = block; block->entryResumePoint_ = resumePoint; if (!block->init()) return nullptr; if (!block->addPredecessorWithoutPhis(pred)) return nullptr; block->end(MAbortPar::New(graph.alloc())); return block; }
// Test whether any instruction in the loop possiblyCalls(). static bool LoopContainsPossibleCall(MIRGraph &graph, MBasicBlock *header, MBasicBlock *backedge) { for (auto i(graph.rpoBegin(header)); ; ++i) { MOZ_ASSERT(i != graph.rpoEnd(), "Reached end of graph searching for blocks in loop"); MBasicBlock *block = *i; if (!block->isMarked()) continue; for (auto insIter(block->begin()), insEnd(block->end()); insIter != insEnd; ++insIter) { MInstruction *ins = *insIter; if (ins->possiblyCalls()) { JitSpew(JitSpew_LICM, " Possile call found at %s%u", ins->opName(), ins->id()); return true; } } if (block == backedge) break; } return false; }
// 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 ion::SplitCriticalEdges(MIRGraph &graph) { for (MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) { if (block->numSuccessors() < 2) continue; for (size_t i = 0; i < block->numSuccessors(); i++) { MBasicBlock *target = block->getSuccessor(i); if (target->numPredecessors() < 2) continue; // Create a new block inheriting from the predecessor. MBasicBlock *split = MBasicBlock::NewSplitEdge(graph, block->info(), *block); split->setLoopDepth(block->loopDepth()); graph.insertBlockAfter(*block, split); split->end(MGoto::New(target)); block->replaceSuccessor(i, split); target->replacePredecessor(*block, split); } } return true; }
void LoopUnroller::go(LoopIterationBound *bound) { // For now we always unroll loops the same number of times. static const size_t UnrollCount = 10; JitSpew(JitSpew_Unrolling, "Attempting to unroll loop"); header = bound->header; // UCE might have determined this isn't actually a loop. if (!header->isLoopHeader()) return; backedge = header->backedge(); oldPreheader = header->loopPredecessor(); JS_ASSERT(oldPreheader->numSuccessors() == 1); // Only unroll loops with two blocks: an initial one ending with the // bound's test, and the body ending with the backedge. MTest *test = bound->test; if (header->lastIns() != test) return; if (test->ifTrue() == backedge) { if (test->ifFalse()->id() <= backedge->id()) return; } else if (test->ifFalse() == backedge) { if (test->ifTrue()->id() <= backedge->id()) return; } else { return; } if (backedge->numPredecessors() != 1 || backedge->numSuccessors() != 1) return; JS_ASSERT(backedge->phisEmpty()); MBasicBlock *bodyBlocks[] = { header, backedge }; // All instructions in the header and body must be clonable. for (size_t i = 0; i < ArrayLength(bodyBlocks); i++) { MBasicBlock *block = bodyBlocks[i]; for (MInstructionIterator iter(block->begin()); iter != block->end(); iter++) { MInstruction *ins = *iter; if (ins->canClone()) continue; if (ins->isTest() || ins->isGoto() || ins->isInterruptCheck()) continue; #ifdef DEBUG JitSpew(JitSpew_Unrolling, "Aborting: can't clone instruction %s", ins->opName()); #endif return; } } // Compute the linear inequality we will use for exiting the unrolled loop: // // iterationBound - iterationCount - UnrollCount >= 0 // LinearSum remainingIterationsInequality(bound->boundSum); if (!remainingIterationsInequality.add(bound->currentSum, -1)) return; if (!remainingIterationsInequality.add(-int32_t(UnrollCount))) return; // Terms in the inequality need to be either loop invariant or phis from // the original header. for (size_t i = 0; i < remainingIterationsInequality.numTerms(); i++) { MDefinition *def = remainingIterationsInequality.term(i).term; if (def->block()->id() < header->id()) continue; if (def->block() == header && def->isPhi()) continue; return; } // OK, we've checked everything, now unroll the loop. JitSpew(JitSpew_Unrolling, "Unrolling loop"); // The old preheader will go before the unrolled loop, and the old loop // will need a new empty preheader. CompileInfo &info = oldPreheader->info(); if (header->trackedSite().pc()) { unrolledHeader = MBasicBlock::New(graph, nullptr, info, oldPreheader, header->trackedSite(), MBasicBlock::LOOP_HEADER); unrolledBackedge = MBasicBlock::New(graph, nullptr, info, unrolledHeader, backedge->trackedSite(), MBasicBlock::NORMAL); newPreheader = MBasicBlock::New(graph, nullptr, info, unrolledHeader, oldPreheader->trackedSite(), MBasicBlock::NORMAL); } else { unrolledHeader = MBasicBlock::NewAsmJS(graph, info, oldPreheader, MBasicBlock::LOOP_HEADER); unrolledBackedge = MBasicBlock::NewAsmJS(graph, info, unrolledHeader, MBasicBlock::NORMAL); newPreheader = MBasicBlock::NewAsmJS(graph, info, unrolledHeader, MBasicBlock::NORMAL); } unrolledHeader->discardAllResumePoints(); unrolledBackedge->discardAllResumePoints(); newPreheader->discardAllResumePoints(); // Insert new blocks at their RPO position, and update block ids. graph.insertBlockAfter(oldPreheader, unrolledHeader); graph.insertBlockAfter(unrolledHeader, unrolledBackedge); graph.insertBlockAfter(unrolledBackedge, newPreheader); graph.renumberBlocksAfter(oldPreheader); if (!unrolledDefinitions.init()) CrashAtUnhandlableOOM("LoopUnroller::go"); // Add phis to the unrolled loop header which correspond to the phis in the // original loop header. JS_ASSERT(header->getPredecessor(0) == oldPreheader); for (MPhiIterator iter(header->phisBegin()); iter != header->phisEnd(); iter++) { MPhi *old = *iter; JS_ASSERT(old->numOperands() == 2); MPhi *phi = MPhi::New(alloc); phi->setResultType(old->type()); phi->setResultTypeSet(old->resultTypeSet()); phi->setRange(old->range()); unrolledHeader->addPhi(phi); if (!phi->reserveLength(2)) CrashAtUnhandlableOOM("LoopUnroller::go"); // Set the first input for the phi for now. We'll set the second after // finishing the unroll. phi->addInput(old->getOperand(0)); // The old phi will now take the value produced by the unrolled loop. old->replaceOperand(0, phi); if (!unrolledDefinitions.putNew(old, phi)) CrashAtUnhandlableOOM("LoopUnroller::go"); } // The loop condition can bail out on e.g. integer overflow, so make a // resume point based on the initial resume point of the original header. MResumePoint *headerResumePoint = header->entryResumePoint(); if (headerResumePoint) { MResumePoint *rp = makeReplacementResumePoint(unrolledHeader, headerResumePoint); unrolledHeader->setEntryResumePoint(rp); // Perform an interrupt check at the start of the unrolled loop. unrolledHeader->add(MInterruptCheck::New(alloc)); } // Generate code for the test in the unrolled loop. for (size_t i = 0; i < remainingIterationsInequality.numTerms(); i++) { MDefinition *def = remainingIterationsInequality.term(i).term; MDefinition *replacement = getReplacementDefinition(def); remainingIterationsInequality.replaceTerm(i, replacement); } MCompare *compare = ConvertLinearInequality(alloc, unrolledHeader, remainingIterationsInequality); MTest *unrolledTest = MTest::New(alloc, compare, unrolledBackedge, newPreheader); unrolledHeader->end(unrolledTest); // Make an entry resume point for the unrolled body. The unrolled header // does not have side effects on stack values, even if the original loop // header does, so use the same resume point as for the unrolled header. if (headerResumePoint) { MResumePoint *rp = makeReplacementResumePoint(unrolledBackedge, headerResumePoint); unrolledBackedge->setEntryResumePoint(rp); } // Make an entry resume point for the new preheader. There are no // instructions which use this but some other stuff wants one to be here. if (headerResumePoint) { MResumePoint *rp = makeReplacementResumePoint(newPreheader, headerResumePoint); newPreheader->setEntryResumePoint(rp); } // Generate the unrolled code. JS_ASSERT(UnrollCount > 1); size_t unrollIndex = 0; while (true) { // Clone the contents of the original loop into the unrolled loop body. for (size_t i = 0; i < ArrayLength(bodyBlocks); i++) { MBasicBlock *block = bodyBlocks[i]; for (MInstructionIterator iter(block->begin()); iter != block->end(); iter++) { MInstruction *ins = *iter; if (ins->canClone()) { makeReplacementInstruction(*iter); } else { // Control instructions are handled separately. JS_ASSERT(ins->isTest() || ins->isGoto() || ins->isInterruptCheck()); } } } // Compute the value of each loop header phi after the execution of // this unrolled iteration. MDefinitionVector phiValues(alloc); JS_ASSERT(header->getPredecessor(1) == backedge); for (MPhiIterator iter(header->phisBegin()); iter != header->phisEnd(); iter++) { MPhi *old = *iter; MDefinition *oldInput = old->getOperand(1); if (!phiValues.append(getReplacementDefinition(oldInput))) CrashAtUnhandlableOOM("LoopUnroller::go"); } unrolledDefinitions.clear(); if (unrollIndex == UnrollCount - 1) { // We're at the end of the last unrolled iteration, set the // backedge input for the unrolled loop phis. size_t phiIndex = 0; for (MPhiIterator iter(unrolledHeader->phisBegin()); iter != unrolledHeader->phisEnd(); iter++) { MPhi *phi = *iter; phi->addInput(phiValues[phiIndex++]); } JS_ASSERT(phiIndex == phiValues.length()); break; } // Update the map for the phis in the next iteration. size_t phiIndex = 0; for (MPhiIterator iter(header->phisBegin()); iter != header->phisEnd(); iter++) { MPhi *old = *iter; if (!unrolledDefinitions.putNew(old, phiValues[phiIndex++])) CrashAtUnhandlableOOM("LoopUnroller::go"); } JS_ASSERT(phiIndex == phiValues.length()); unrollIndex++; } MGoto *backedgeJump = MGoto::New(alloc, unrolledHeader); unrolledBackedge->end(backedgeJump); // Place the old preheader before the unrolled loop. JS_ASSERT(oldPreheader->lastIns()->isGoto()); oldPreheader->discardLastIns(); oldPreheader->end(MGoto::New(alloc, unrolledHeader)); // Place the new preheader before the original loop. newPreheader->end(MGoto::New(alloc, header)); // Cleanup the MIR graph. if (!unrolledHeader->addPredecessorWithoutPhis(unrolledBackedge)) CrashAtUnhandlableOOM("LoopUnroller::go"); header->replacePredecessor(oldPreheader, newPreheader); oldPreheader->setSuccessorWithPhis(unrolledHeader, 0); newPreheader->setSuccessorWithPhis(header, 0); unrolledBackedge->setSuccessorWithPhis(unrolledHeader, 1); }
Loop::LoopReturn Loop::init() { IonSpew(IonSpew_LICM, "Loop identified, headed by block %d", header_->id()); IonSpew(IonSpew_LICM, "footer is block %d", header_->backedge()->id()); // The first predecessor of the loop header must dominate the header. JS_ASSERT(header_->id() > header_->getPredecessor(0)->id()); // Loops from backedge to header and marks all visited blocks // as part of the loop. At the same time add all hoistable instructions // (in RPO order) to the instruction worklist. Vector<MBasicBlock *, 1, IonAllocPolicy> inlooplist; if (!inlooplist.append(header_->backedge())) return LoopReturn_Error; header_->backedge()->mark(); while (!inlooplist.empty()) { MBasicBlock *block = inlooplist.back(); // Hoisting requires more finesse if the loop contains a block that // self-dominates: there exists control flow that may enter the loop // without passing through the loop preheader. // // Rather than perform a complicated analysis of the dominance graph, // just return a soft error to ignore this loop. if (block->immediateDominator() == block) { while (!worklist_.empty()) popFromWorklist(); return LoopReturn_Skip; } // Add not yet visited predecessors to the inlooplist. if (block != header_) { for (size_t i = 0; i < block->numPredecessors(); i++) { MBasicBlock *pred = block->getPredecessor(i); if (pred->isMarked()) continue; if (!inlooplist.append(pred)) return LoopReturn_Error; pred->mark(); } } // If any block was added, process them first. if (block != inlooplist.back()) continue; // Add all instructions in this block (but the control instruction) to the worklist for (MInstructionIterator i = block->begin(); i != block->end(); i++) { MInstruction *ins = *i; if (isHoistable(ins)) { if (!insertInWorklist(ins)) return LoopReturn_Error; } } // All successors of this block are visited. inlooplist.popBack(); } return LoopReturn_Success; }
bool UnreachableCodeElimination::prunePointlessBranchesAndMarkReachableBlocks() { BlockList worklist, optimizableBlocks; // Process everything reachable from the start block, ignoring any // OSR block. if (!enqueue(graph_.entryBlock(), worklist)) return false; while (!worklist.empty()) { if (mir_->shouldCancel("Eliminate Unreachable Code")) return false; MBasicBlock *block = worklist.popCopy(); // If this block is a test on a constant operand, only enqueue // the relevant successor. Also, remember the block for later. if (MBasicBlock *succ = optimizableSuccessor(block)) { if (!optimizableBlocks.append(block)) return false; if (!enqueue(succ, worklist)) return false; } else { // Otherwise just visit all successors. for (size_t i = 0; i < block->numSuccessors(); i++) { MBasicBlock *succ = block->getSuccessor(i); if (!enqueue(succ, worklist)) return false; } } } // Now, if there is an OSR block, check that all of its successors // were reachable (bug 880377). If not, we are in danger of // creating a CFG with two disjoint parts, so simply mark all // blocks as reachable. This generally occurs when the TI info for // stack types is incorrect or incomplete, due to operations that // have not yet executed in baseline. if (graph_.osrBlock()) { MBasicBlock *osrBlock = graph_.osrBlock(); JS_ASSERT(!osrBlock->isMarked()); if (!enqueue(osrBlock, worklist)) return false; for (size_t i = 0; i < osrBlock->numSuccessors(); i++) { if (!osrBlock->getSuccessor(i)->isMarked()) { // OSR block has an otherwise unreachable successor, abort. for (MBasicBlockIterator iter(graph_.begin()); iter != graph_.end(); iter++) iter->mark(); marked_ = graph_.numBlocks(); return true; } } } // Now that we know we will not abort due to OSR, go back and // transform any tests on constant operands into gotos. for (uint32_t i = 0; i < optimizableBlocks.length(); i++) { MBasicBlock *block = optimizableBlocks[i]; MBasicBlock *succ = optimizableSuccessor(block); JS_ASSERT(succ); MGoto *gotoIns = MGoto::New(graph_.alloc(), succ); block->discardLastIns(); block->end(gotoIns); MBasicBlock *successorWithPhis = block->successorWithPhis(); if (successorWithPhis && successorWithPhis != succ) block->setSuccessorWithPhis(nullptr, 0); } return true; }
bool MIRGraph::removeSuccessorBlocks(MBasicBlock* start) { if (!start->hasLastIns()) return true; start->mark(); // Mark all successors. Vector<MBasicBlock*, 4, SystemAllocPolicy> blocks; for (size_t i = 0; i < start->numSuccessors(); i++) { if (start->getSuccessor(i)->isMarked()) continue; if (!blocks.append(start->getSuccessor(i))) return false; start->getSuccessor(i)->mark(); } for (size_t i = 0; i < blocks.length(); i++) { MBasicBlock* block = blocks[i]; if (!block->hasLastIns()) continue; for (size_t j = 0; j < block->numSuccessors(); j++) { if (block->getSuccessor(j)->isMarked()) continue; if (!blocks.append(block->getSuccessor(j))) return false; block->getSuccessor(j)->mark(); } } if (osrBlock()) { if (osrBlock()->getSuccessor(0)->isMarked()) osrBlock()->mark(); } // Remove blocks. // If they don't have any predecessor for (size_t i = 0; i < blocks.length(); i++) { MBasicBlock* block = blocks[i]; bool allMarked = true; for (size_t i = 0; i < block->numPredecessors(); i++) { if (block->getPredecessor(i)->isMarked()) continue; allMarked = false; break; } if (allMarked) { removeBlock(block); } else { MOZ_ASSERT(block != osrBlock()); for (size_t j = 0; j < block->numPredecessors(); ) { if (!block->getPredecessor(j)->isMarked()) { j++; continue; } block->removePredecessor(block->getPredecessor(j)); } // This shouldn't have any instructions yet. MOZ_ASSERT(block->begin() == block->end()); } } if (osrBlock()) { if (osrBlock()->getSuccessor(0)->isDead()) removeBlock(osrBlock()); } for (size_t i = 0; i < blocks.length(); i++) blocks[i]->unmark(); start->unmark(); return true; }
MBasicBlock* MBasicBlock::NewSplitEdge(MIRGraph& graph, MBasicBlock* pred, size_t predEdgeIdx, MBasicBlock* succ) { MBasicBlock* split = nullptr; if (!succ->pc()) { // The predecessor does not have a PC, this is a Wasm compilation. split = MBasicBlock::New(graph, succ->info(), pred, SPLIT_EDGE); if (!split) return nullptr; } else { // The predecessor has a PC, this is an IonBuilder compilation. MResumePoint* succEntry = succ->entryResumePoint(); BytecodeSite* site = new(graph.alloc()) BytecodeSite(succ->trackedTree(), succEntry->pc()); split = new(graph.alloc()) MBasicBlock(graph, succ->info(), site, SPLIT_EDGE); if (!split->init()) return nullptr; // A split edge is used to simplify the graph to avoid having a // predecessor with multiple successors as well as a successor with // multiple predecessors. As instructions can be moved in this // split-edge block, we need to give this block a resume point. To do // so, we copy the entry resume points of the successor and filter the // phis to keep inputs from the current edge. // Propagate the caller resume point from the inherited block. split->callerResumePoint_ = succ->callerResumePoint(); // Split-edge are created after the interpreter stack emulation. Thus, // there is no need for creating slots. split->stackPosition_ = succEntry->stackDepth(); // Create a resume point using our initial stack position. MResumePoint* splitEntry = new(graph.alloc()) MResumePoint(split, succEntry->pc(), MResumePoint::ResumeAt); if (!splitEntry->init(graph.alloc())) return nullptr; split->entryResumePoint_ = splitEntry; // The target entry resume point might have phi operands, keep the // operands of the phi coming from our edge. size_t succEdgeIdx = succ->indexForPredecessor(pred); for (size_t i = 0, e = splitEntry->numOperands(); i < e; i++) { MDefinition* def = succEntry->getOperand(i); // This early in the pipeline, we have no recover instructions in // any entry resume point. MOZ_ASSERT_IF(def->block() == succ, def->isPhi()); if (def->block() == succ) def = def->toPhi()->getOperand(succEdgeIdx); splitEntry->initOperand(i, def); } // This is done in the New variant for wasm, so we cannot keep this // line below, where the rest of the graph is modified. if (!split->predecessors_.append(pred)) return nullptr; } split->setLoopDepth(succ->loopDepth()); // Insert the split edge block in-between. split->end(MGoto::New(graph.alloc(), succ)); graph.insertBlockAfter(pred, split); pred->replaceSuccessor(predEdgeIdx, split); succ->replacePredecessor(pred, split); return split; }
bool UnreachableCodeElimination::removeUnmarkedBlocksAndClearDominators() { // Removes blocks that are not marked from the graph. For blocks // that *are* marked, clears the mark and adjusts the id to its // new value. Also adds blocks that are immediately reachable // from an unmarked block to the frontier. size_t id = marked_; for (PostorderIterator iter(graph_.poBegin()); iter != graph_.poEnd();) { if (mir_->shouldCancel("Eliminate Unreachable Code")) return false; MBasicBlock *block = *iter; iter++; // Unconditionally clear the dominators. It's somewhat complex to // adjust the values and relatively fast to just recompute. block->clearDominatorInfo(); if (block->isMarked()) { block->setId(--id); for (MPhiIterator iter(block->phisBegin()); iter != block->phisEnd(); iter++) checkDependencyAndRemoveUsesFromUnmarkedBlocks(*iter); for (MInstructionIterator iter(block->begin()); iter != block->end(); iter++) checkDependencyAndRemoveUsesFromUnmarkedBlocks(*iter); } else { if (block->numPredecessors() > 1) { // If this block had phis, then any reachable // predecessors need to have the successorWithPhis // flag cleared. for (size_t i = 0; i < block->numPredecessors(); i++) block->getPredecessor(i)->setSuccessorWithPhis(nullptr, 0); } if (block->isLoopBackedge()) { // NB. We have to update the loop header if we // eliminate the backedge. At first I thought this // check would be insufficient, because it would be // possible to have code like this: // // while (true) { // ...; // if (1 == 1) break; // } // // in which the backedge is removed as part of // rewriting the condition, but no actual blocks are // removed. However, in all such cases, the backedge // would be a critical edge and hence the critical // edge block is being removed. block->loopHeaderOfBackedge()->clearLoopHeader(); } for (size_t i = 0, c = block->numSuccessors(); i < c; i++) { MBasicBlock *succ = block->getSuccessor(i); if (succ->isMarked()) { // succ is on the frontier of blocks to be removed: succ->removePredecessor(block); if (!redundantPhis_) { for (MPhiIterator iter(succ->phisBegin()); iter != succ->phisEnd(); iter++) { if (iter->operandIfRedundant()) { redundantPhis_ = true; break; } } } } } // When we remove a call, we can't leave the corresponding MPassArg // in the graph. Since lowering will fail. Replace it with the // argument for the exceptional case when it is kept alive in a // ResumePoint. DCE will remove the unused MPassArg instruction. for (MInstructionIterator iter(block->begin()); iter != block->end(); iter++) { if (iter->isCall()) { MCall *call = iter->toCall(); for (size_t i = 0; i < call->numStackArgs(); i++) { JS_ASSERT(call->getArg(i)->isPassArg()); JS_ASSERT(call->getArg(i)->hasOneDefUse()); MPassArg *arg = call->getArg(i)->toPassArg(); arg->replaceAllUsesWith(arg->getArgument()); } } } graph_.removeBlock(block); } } JS_ASSERT(id == 0); return true; }