summaryrefslogtreecommitdiffstats
path: root/js/src/jit/IonAnalysis.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/src/jit/IonAnalysis.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit/IonAnalysis.cpp')
-rw-r--r--js/src/jit/IonAnalysis.cpp5259
1 files changed, 5259 insertions, 0 deletions
diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp
new file mode 100644
index 0000000000..a0c9a51c39
--- /dev/null
+++ b/js/src/jit/IonAnalysis.cpp
@@ -0,0 +1,5259 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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 <algorithm>
+#include <utility> // for ::std::pair
+
+#include "jit/AliasAnalysis.h"
+#include "jit/CompileInfo.h"
+#include "jit/MIRGenerator.h"
+#include "jit/MIRGraph.h"
+#include "util/CheckedArithmetic.h"
+
+#include "vm/BytecodeUtil-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+using mozilla::DebugOnly;
+
+// Stack used by FlagPhiInputsAsImplicitlyUsed. It stores the Phi instruction
+// pointer and the MUseIterator which should be visited next.
+using MPhiUseIteratorStack =
+ Vector<std::pair<MPhi*, MUseIterator>, 16, SystemAllocPolicy>;
+
+// Look for Phi uses with a depth-first search. If any uses are found the stack
+// of MPhi instructions is returned in the |worklist| argument.
+static bool DepthFirstSearchUse(MIRGenerator* mir,
+ MPhiUseIteratorStack& worklist, MPhi* phi) {
+ // Push a Phi and the next use to iterate over in the worklist.
+ auto push = [&worklist](MPhi* phi, MUseIterator use) -> bool {
+ phi->setInWorklist();
+ return worklist.append(std::make_pair(phi, use));
+ };
+
+#ifdef DEBUG
+ // Used to assert that when we have no uses, we at least visited all the
+ // transitive uses.
+ size_t refUseCount = phi->useCount();
+ size_t useCount = 0;
+#endif
+ MOZ_ASSERT(worklist.empty());
+ if (!push(phi, phi->usesBegin())) {
+ return false;
+ }
+
+ while (!worklist.empty()) {
+ // Resume iterating over the last phi-use pair added by the next loop.
+ auto pair = worklist.popCopy();
+ MPhi* producer = pair.first;
+ MUseIterator use = pair.second;
+ MUseIterator end(producer->usesEnd());
+ producer->setNotInWorklist();
+
+ // Keep going down the tree of uses, skipping (continue)
+ // non-observable/unused cases and Phi which are already listed in the
+ // worklist. Stop (return) as soon as one use is found.
+ while (use != end) {
+ MNode* consumer = (*use)->consumer();
+ MUseIterator it = use;
+ use++;
+#ifdef DEBUG
+ useCount++;
+#endif
+ if (mir->shouldCancel("FlagPhiInputsAsImplicitlyUsed inner loop")) {
+ return false;
+ }
+
+ if (consumer->isResumePoint()) {
+ MResumePoint* rp = consumer->toResumePoint();
+ // Observable operands are similar to potential uses.
+ if (rp->isObservableOperand(*it)) {
+ return push(producer, use);
+ }
+ continue;
+ }
+
+ MDefinition* cdef = consumer->toDefinition();
+ if (!cdef->isPhi()) {
+ // The producer is explicitly used by a definition.
+ return push(producer, use);
+ }
+
+ MPhi* cphi = cdef->toPhi();
+ if (cphi->getUsageAnalysis() == PhiUsage::Used ||
+ cphi->isImplicitlyUsed()) {
+ // The information got cached on the Phi the last time it
+ // got visited, or when flagging operands of implicitly used
+ // instructions.
+ return push(producer, use);
+ }
+
+ if (cphi->isInWorklist() || cphi == producer) {
+ // We are already iterating over the uses of this Phi instruction which
+ // are part of a loop, instead of trying to handle loops, conservatively
+ // mark them as used.
+ return push(producer, use);
+ }
+
+ if (cphi->getUsageAnalysis() == PhiUsage::Unused) {
+ // The instruction already got visited and is known to have
+ // no uses. Skip it.
+ continue;
+ }
+
+ // We found another Phi instruction, move the use iterator to
+ // the next use push it to the worklist stack. Then, continue
+ // with a depth search.
+ if (!push(producer, use)) {
+ return false;
+ }
+ producer = cphi;
+ use = producer->usesBegin();
+ end = producer->usesEnd();
+#ifdef DEBUG
+ refUseCount += producer->useCount();
+#endif
+ }
+
+ // When unused, we cannot bubble up this information without iterating
+ // over the rest of the previous Phi instruction consumers.
+ MOZ_ASSERT(use == end);
+ producer->setUsageAnalysis(PhiUsage::Unused);
+ }
+
+ MOZ_ASSERT(useCount == refUseCount);
+ return true;
+}
+
+static bool FlagPhiInputsAsImplicitlyUsed(MIRGenerator* mir, MBasicBlock* block,
+ MBasicBlock* succ,
+ MPhiUseIteratorStack& 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 being
+ // implicitly used 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 implicitly used, 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.
+ //
+ // Unfortunately, we cannot be too conservative about flagging Phi inputs as
+ // having implicit uses, as this would prevent many optimizations from being
+ // used. Thus, the following code is in charge of flagging Phi instructions
+ // as Unused or Used, and setting ImplicitlyUsed accordingly.
+ size_t predIndex = succ->getPredecessorIndex(block);
+ MPhiIterator end = succ->phisEnd();
+ MPhiIterator it = succ->phisBegin();
+ for (; it != end; it++) {
+ MPhi* phi = *it;
+
+ if (mir->shouldCancel("FlagPhiInputsAsImplicitlyUsed 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->isImplicitlyUsed()) {
+ continue;
+ }
+
+ // If the Phi is either Used or Unused, set the ImplicitlyUsed flag
+ // accordingly.
+ if (phi->getUsageAnalysis() == PhiUsage::Used || phi->isImplicitlyUsed()) {
+ def->setImplicitlyUsedUnchecked();
+ continue;
+ } else if (phi->getUsageAnalysis() == PhiUsage::Unused) {
+ continue;
+ }
+
+ // We do not know if the Phi was Used or Unused, iterate over all uses
+ // with a depth-search of uses. Returns the matching stack in the
+ // worklist as soon as one use is found.
+ MOZ_ASSERT(worklist.empty());
+ if (!DepthFirstSearchUse(mir, worklist, phi)) {
+ return false;
+ }
+
+ MOZ_ASSERT_IF(worklist.empty(),
+ phi->getUsageAnalysis() == PhiUsage::Unused);
+ if (!worklist.empty()) {
+ // One of the Phis is used, set Used flags on all the Phis which are
+ // in the use chain.
+ def->setImplicitlyUsedUnchecked();
+ do {
+ auto pair = worklist.popCopy();
+ MPhi* producer = pair.first;
+ producer->setUsageAnalysis(PhiUsage::Used);
+ producer->setNotInWorklist();
+ } while (!worklist.empty());
+ }
+ MOZ_ASSERT(phi->getUsageAnalysis() != PhiUsage::Unknown);
+ }
+
+ return true;
+}
+
+static MInstructionIterator FindFirstInstructionAfterBail(MBasicBlock* block) {
+ MOZ_ASSERT(block->alwaysBails());
+ for (MInstructionIterator it = block->begin(); it != block->end(); it++) {
+ MInstruction* ins = *it;
+ if (ins->isBail()) {
+ it++;
+ return it;
+ }
+ }
+ MOZ_CRASH("Expected MBail in alwaysBails block");
+}
+
+// Given an iterator pointing to the first removed instruction, mark
+// the operands of each removed instruction as having implicit uses.
+static bool FlagOperandsAsImplicitlyUsedAfter(
+ MIRGenerator* mir, MBasicBlock* block, MInstructionIterator firstRemoved) {
+ MOZ_ASSERT(firstRemoved->block() == block);
+
+ const CompileInfo& info = block->info();
+
+ // Flag operands of removed instructions as having implicit uses.
+ MInstructionIterator end = block->end();
+ for (MInstructionIterator it = firstRemoved; it != end; it++) {
+ if (mir->shouldCancel("FlagOperandsAsImplicitlyUsedAfter (loop 1)")) {
+ return false;
+ }
+
+ MInstruction* ins = *it;
+ for (size_t i = 0, e = ins->numOperands(); i < e; i++) {
+ ins->getOperand(i)->setImplicitlyUsedUnchecked();
+ }
+
+ // Flag observable resume point operands as having implicit 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.
+ MOZ_ASSERT(&rp->block()->info() == &info);
+ for (size_t i = 0, e = rp->numOperands(); i < e; i++) {
+ if (info.isObservableSlot(i)) {
+ rp->getOperand(i)->setImplicitlyUsedUnchecked();
+ }
+ }
+ }
+ }
+
+ // Flag Phi inputs of the successors as having implicit uses.
+ MPhiUseIteratorStack worklist;
+ for (size_t i = 0, e = block->numSuccessors(); i < e; i++) {
+ if (mir->shouldCancel("FlagOperandsAsImplicitlyUsedAfter (loop 2)")) {
+ return false;
+ }
+
+ if (!FlagPhiInputsAsImplicitlyUsed(mir, block, block->getSuccessor(i),
+ worklist)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool FlagEntryResumePointOperands(MIRGenerator* mir,
+ MBasicBlock* block) {
+ // Flag observable operands of the entry resume point as having implicit uses.
+ MResumePoint* rp = block->entryResumePoint();
+ while (rp) {
+ if (mir->shouldCancel("FlagEntryResumePointOperands")) {
+ return false;
+ }
+
+ const CompileInfo& info = rp->block()->info();
+ for (size_t i = 0, e = rp->numOperands(); i < e; i++) {
+ if (info.isObservableSlot(i)) {
+ rp->getOperand(i)->setImplicitlyUsedUnchecked();
+ }
+ }
+
+ rp = rp->caller();
+ }
+
+ return true;
+}
+
+static bool FlagAllOperandsAsImplicitlyUsed(MIRGenerator* mir,
+ MBasicBlock* block) {
+ return FlagEntryResumePointOperands(mir, block) &&
+ FlagOperandsAsImplicitlyUsedAfter(mir, block, block->begin());
+}
+
+// WarpBuilder sets the alwaysBails flag on blocks that contain an
+// unconditional bailout. We trim any instructions in those blocks
+// after the first unconditional bailout, and remove any blocks that
+// are only reachable through bailing blocks.
+bool jit::PruneUnusedBranches(MIRGenerator* mir, MIRGraph& graph) {
+ JitSpew(JitSpew_Prune, "Begin");
+
+ // Pruning is guided by unconditional bailouts. Wasm does not have bailouts.
+ MOZ_ASSERT(!mir->compilingWasm());
+
+ Vector<MBasicBlock*, 16, SystemAllocPolicy> worklist;
+ uint32_t numMarked = 0;
+ bool needsTrim = false;
+
+ auto markReachable = [&](MBasicBlock* block) -> bool {
+ block->mark();
+ numMarked++;
+ if (block->alwaysBails()) {
+ needsTrim = true;
+ }
+ return worklist.append(block);
+ };
+
+ // The entry block is always reachable.
+ if (!markReachable(graph.entryBlock())) {
+ return false;
+ }
+
+ // The OSR entry block is always reachable if it exists.
+ if (graph.osrBlock() && !markReachable(graph.osrBlock())) {
+ return false;
+ }
+
+ // Iteratively mark all reachable blocks.
+ while (!worklist.empty()) {
+ if (mir->shouldCancel("Prune unused branches (marking reachable)")) {
+ return false;
+ }
+ MBasicBlock* block = worklist.popCopy();
+
+ JitSpew(JitSpew_Prune, "Visit block %u:", block->id());
+ JitSpewIndent indent(JitSpew_Prune);
+
+ // If this block always bails, then it does not reach its successors.
+ if (block->alwaysBails()) {
+ continue;
+ }
+
+ for (size_t i = 0; i < block->numSuccessors(); i++) {
+ MBasicBlock* succ = block->getSuccessor(i);
+ if (succ->isMarked()) {
+ continue;
+ }
+ JitSpew(JitSpew_Prune, "Reaches block %u", succ->id());
+ if (!markReachable(succ)) {
+ return false;
+ }
+ }
+ }
+
+ if (!needsTrim && numMarked == graph.numBlocks()) {
+ // There is nothing to prune.
+ graph.unmarkBlocks();
+ return true;
+ }
+
+ JitSpew(JitSpew_Prune, "Remove unreachable instructions and blocks:");
+ JitSpewIndent indent(JitSpew_Prune);
+
+ // The operands of removed instructions may be needed in baseline
+ // after bailing out.
+ for (PostorderIterator it(graph.poBegin()); it != graph.poEnd();) {
+ if (mir->shouldCancel("Prune unused branches (marking operands)")) {
+ return false;
+ }
+
+ MBasicBlock* block = *it++;
+ if (!block->isMarked()) {
+ // If we are removing the block entirely, mark the operands of every
+ // instruction as being implicitly used.
+ FlagAllOperandsAsImplicitlyUsed(mir, block);
+ } else if (block->alwaysBails()) {
+ // If we are only trimming instructions after a bail, only mark operands
+ // of removed instructions.
+ MInstructionIterator firstRemoved = FindFirstInstructionAfterBail(block);
+ FlagOperandsAsImplicitlyUsedAfter(mir, block, firstRemoved);
+ }
+ }
+
+ // 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;
+ }
+ if (!graph.alloc().ensureBallast()) {
+ return false;
+ }
+
+ MBasicBlock* block = *it++;
+ if (block->isMarked() && !block->alwaysBails()) {
+ continue;
+ }
+
+ // As we are going to replace/remove the last instruction, we first have
+ // to remove this block from the predecessor list of its successors.
+ size_t numSucc = block->numSuccessors();
+ for (uint32_t i = 0; i < numSucc; i++) {
+ MBasicBlock* succ = block->getSuccessor(i);
+ if (succ->isDead()) {
+ continue;
+ }
+
+ // Our dominators code expects all loop headers to have two predecessors.
+ // If we are removing the normal entry to a loop, but can still reach
+ // the loop header via OSR, we create a fake unreachable predecessor.
+ if (succ->isLoopHeader() && block != succ->backedge()) {
+ MOZ_ASSERT(graph.osrBlock());
+ if (!graph.alloc().ensureBallast()) {
+ return false;
+ }
+
+ MBasicBlock* fake = MBasicBlock::NewFakeLoopPredecessor(graph, succ);
+ if (!fake) {
+ return false;
+ }
+ // Mark the block to avoid removing it as unreachable.
+ fake->mark();
+
+ JitSpew(JitSpew_Prune,
+ "Header %u only reachable by OSR. Add fake predecessor %u",
+ succ->id(), fake->id());
+ }
+
+ JitSpew(JitSpew_Prune, "Remove block edge %u -> %u.", block->id(),
+ succ->id());
+ succ->removePredecessor(block);
+ }
+
+ if (!block->isMarked()) {
+ // Remove unreachable blocks from the CFG.
+ JitSpew(JitSpew_Prune, "Remove block %u.", block->id());
+ graph.removeBlock(block);
+ } else {
+ // Remove unreachable instructions after unconditional bailouts.
+ JitSpew(JitSpew_Prune, "Trim block %u.", block->id());
+
+ // Discard all instructions after the first MBail.
+ MInstructionIterator firstRemoved = FindFirstInstructionAfterBail(block);
+ block->discardAllInstructionsStartingAt(firstRemoved);
+
+ if (block->outerResumePoint()) {
+ block->clearOuterResumePoint();
+ }
+
+ block->end(MUnreachable::New(graph.alloc()));
+ }
+ }
+ graph.unmarkBlocks();
+
+ 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;
+}
+
+// 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;
+ }
+ }
+
+ auto iter = testBlock->rbegin();
+ if (!iter->isTest()) {
+ return false;
+ }
+ MTest* test = iter->toTest();
+
+ // Unwrap boolean conversion performed through the '!!' idiom.
+ MInstruction* testOrNot = test;
+ bool hasOddNumberOfNots = false;
+ while (++iter != testBlock->rend()) {
+ if (iter->isNot()) {
+ // The MNot must only be used by |testOrNot|.
+ auto* notIns = iter->toNot();
+ if (testOrNot->getOperand(0) != notIns) {
+ return false;
+ }
+ if (!notIns->hasOneUse()) {
+ return false;
+ }
+
+ testOrNot = notIns;
+ hasOddNumberOfNots = !hasOddNumberOfNots;
+ } else {
+ // Fail if there are any other instructions than MNot.
+ return false;
+ }
+ }
+
+ // There's an odd number of MNot, so this can't be the '!!' idiom.
+ if (hasOddNumberOfNots) {
+ return false;
+ }
+
+ MOZ_ASSERT(testOrNot->isTest() || testOrNot->isNot());
+
+ MDefinition* testInput = testOrNot->getOperand(0);
+ if (!testInput->isPhi()) {
+ return false;
+ }
+ MPhi* phi = testInput->toPhi();
+ if (phi->block() != phiBlock) {
+ return false;
+ }
+
+ for (MUseIterator iter = phi->usesBegin(); iter != phi->usesEnd(); ++iter) {
+ MUse* use = *iter;
+ if (use->consumer() == testOrNot) {
+ 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) {
+ return false;
+ }
+ }
+
+ if (phiBlock != testBlock && !testBlock->phisEmpty()) {
+ return false;
+ }
+
+ *pphi = phi;
+ *ptest = test;
+
+ return true;
+}
+
+// Determine if value is directly or indirectly the test input.
+static bool IsTestInputMaybeToBool(MTest* test, MDefinition* value) {
+ auto* input = test->input();
+ bool hasEvenNumberOfNots = true;
+ while (true) {
+ // Only accept if there's an even number of MNot.
+ if (input == value && hasEvenNumberOfNots) {
+ return true;
+ }
+
+ // Unwrap boolean conversion performed through the '!!' idiom.
+ if (input->isNot()) {
+ input = input->toNot()->input();
+ hasEvenNumberOfNots = !hasEvenNumberOfNots;
+ continue;
+ }
+
+ return false;
+ }
+}
+
+// Change block so that it ends in a goto to the specific target block.
+// existingPred is an existing predecessor of the block.
+[[nodiscard]] static bool 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);
+
+ return 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.
+[[nodiscard]] static bool 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);
+ if (!ifTrue->addPredecessorSameInputsAs(block, existingPred)) {
+ return false;
+ }
+ MOZ_ASSERT(test->ifTrue() == test->getSuccessor(0));
+ test->replaceSuccessor(0, ifTrue);
+ }
+
+ if (ifFalse != test->ifFalse()) {
+ test->ifFalse()->removePredecessor(block);
+ if (!ifFalse->addPredecessorSameInputsAs(block, existingPred)) {
+ return false;
+ }
+ MOZ_ASSERT(test->ifFalse() == test->getSuccessor(1));
+ test->replaceSuccessor(1, ifFalse);
+ }
+
+ return true;
+ }
+
+ MOZ_ASSERT(ins->isGoto());
+ ins->toGoto()->target()->removePredecessor(block);
+ block->discardLastIns();
+
+ MTest* test = MTest::New(alloc, value, ifTrue, ifFalse);
+ block->end(test);
+
+ if (!ifTrue->addPredecessorSameInputsAs(block, existingPred)) {
+ return false;
+ }
+ if (!ifFalse->addPredecessorSameInputsAs(block, existingPred)) {
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Look for a diamond pattern:
+ *
+ * initialBlock
+ * / \
+ * trueBranch falseBranch
+ * \ /
+ * phiBlock
+ * |
+ * testBlock
+ */
+static bool IsDiamondPattern(MBasicBlock* initialBlock) {
+ MInstruction* ins = initialBlock->lastIns();
+ if (!ins->isTest()) {
+ return false;
+ }
+ MTest* initialTest = ins->toTest();
+
+ MBasicBlock* trueBranch = initialTest->ifTrue();
+ if (trueBranch->numPredecessors() != 1 || trueBranch->numSuccessors() != 1) {
+ return false;
+ }
+
+ MBasicBlock* falseBranch = initialTest->ifFalse();
+ if (falseBranch->numPredecessors() != 1 ||
+ falseBranch->numSuccessors() != 1) {
+ return false;
+ }
+
+ MBasicBlock* phiBlock = trueBranch->getSuccessor(0);
+ if (phiBlock != falseBranch->getSuccessor(0)) {
+ return false;
+ }
+ if (phiBlock->numPredecessors() != 2) {
+ return false;
+ }
+ return true;
+}
+
+static bool MaybeFoldDiamondConditionBlock(MIRGraph& graph,
+ MBasicBlock* initialBlock) {
+ MOZ_ASSERT(IsDiamondPattern(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.
+
+ /*
+ * 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.
+ */
+
+ MTest* initialTest = initialBlock->lastIns()->toTest();
+
+ MBasicBlock* trueBranch = initialTest->ifTrue();
+ MBasicBlock* falseBranch = initialTest->ifFalse();
+ if (initialBlock->isLoopBackedge() || trueBranch->isLoopBackedge() ||
+ falseBranch->isLoopBackedge()) {
+ return true;
+ }
+
+ MBasicBlock* phiBlock = trueBranch->getSuccessor(0);
+ MBasicBlock* testBlock = phiBlock;
+ if (testBlock->numSuccessors() == 1) {
+ if (testBlock->isLoopBackedge()) {
+ return true;
+ }
+ testBlock = testBlock->getSuccessor(0);
+ if (testBlock->numPredecessors() != 1) {
+ return true;
+ }
+ }
+
+ MPhi* phi;
+ MTest* finalTest;
+ if (!BlockIsSingleTest(phiBlock, testBlock, &phi, &finalTest)) {
+ return true;
+ }
+
+ MOZ_ASSERT(phi->numOperands() == 2);
+
+ // Make sure the test block does not have any outgoing loop backedges.
+ if (!SplitCriticalEdgesForBlock(graph, testBlock)) {
+ return false;
+ }
+
+ MDefinition* trueResult =
+ phi->getOperand(phiBlock->indexForPredecessor(trueBranch));
+ MDefinition* falseResult =
+ phi->getOperand(phiBlock->indexForPredecessor(falseBranch));
+
+ // OK, we found the desired pattern, now transform the graph.
+
+ // Remove the phi from phiBlock.
+ phiBlock->discardPhi(*phiBlock->phisBegin());
+
+ // Change the end of the block to a test that jumps directly to successors of
+ // testBlock, rather than to testBlock itself.
+
+ if (IsTestInputMaybeToBool(initialTest, trueResult)) {
+ if (!UpdateGotoSuccessor(graph.alloc(), trueBranch, finalTest->ifTrue(),
+ testBlock)) {
+ return false;
+ }
+ } else {
+ if (!UpdateTestSuccessors(graph.alloc(), trueBranch, trueResult,
+ finalTest->ifTrue(), finalTest->ifFalse(),
+ testBlock)) {
+ return false;
+ }
+ }
+
+ if (IsTestInputMaybeToBool(initialTest, falseResult)) {
+ if (!UpdateGotoSuccessor(graph.alloc(), falseBranch, finalTest->ifFalse(),
+ testBlock)) {
+ return false;
+ }
+ } else {
+ if (!UpdateTestSuccessors(graph.alloc(), falseBranch, falseResult,
+ finalTest->ifTrue(), finalTest->ifFalse(),
+ testBlock)) {
+ return false;
+ }
+ }
+
+ // 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;
+}
+
+/*
+ * Look for a triangle pattern:
+ *
+ * initialBlock
+ * / \
+ * trueBranch |
+ * \ /
+ * phiBlock+falseBranch
+ * |
+ * testBlock
+ *
+ * Or:
+ *
+ * initialBlock
+ * / \
+ * | falseBranch
+ * \ /
+ * phiBlock+trueBranch
+ * |
+ * testBlock
+ */
+static bool IsTrianglePattern(MBasicBlock* initialBlock) {
+ MInstruction* ins = initialBlock->lastIns();
+ if (!ins->isTest()) {
+ return false;
+ }
+ MTest* initialTest = ins->toTest();
+
+ MBasicBlock* trueBranch = initialTest->ifTrue();
+ MBasicBlock* falseBranch = initialTest->ifFalse();
+
+ if (trueBranch->numSuccessors() == 1 &&
+ trueBranch->getSuccessor(0) == falseBranch) {
+ if (trueBranch->numPredecessors() != 1) {
+ return false;
+ }
+ if (falseBranch->numPredecessors() != 2) {
+ return false;
+ }
+ return true;
+ }
+
+ if (falseBranch->numSuccessors() == 1 &&
+ falseBranch->getSuccessor(0) == trueBranch) {
+ if (trueBranch->numPredecessors() != 2) {
+ return false;
+ }
+ if (falseBranch->numPredecessors() != 1) {
+ return false;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+static bool MaybeFoldTriangleConditionBlock(MIRGraph& graph,
+ MBasicBlock* initialBlock) {
+ MOZ_ASSERT(IsTrianglePattern(initialBlock));
+
+ // Optimize the MIR graph to improve the code generated for boolean
+ // operations. A test like 'if (a && b)' normally requires three blocks, with
+ // a phi for the intermediate value. This can be improved to use no phi value.
+
+ /*
+ * Look for a triangle pattern:
+ *
+ * initialBlock
+ * / \
+ * trueBranch |
+ * \ /
+ * phiBlock+falseBranch
+ * |
+ * testBlock
+ *
+ * Or:
+ *
+ * initialBlock
+ * / \
+ * | falseBranch
+ * \ /
+ * phiBlock+trueBranch
+ * |
+ * 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.
+ */
+
+ MTest* initialTest = initialBlock->lastIns()->toTest();
+
+ MBasicBlock* trueBranch = initialTest->ifTrue();
+ MBasicBlock* falseBranch = initialTest->ifFalse();
+ if (initialBlock->isLoopBackedge() || trueBranch->isLoopBackedge() ||
+ falseBranch->isLoopBackedge()) {
+ return true;
+ }
+
+ MBasicBlock* phiBlock;
+ if (trueBranch->numSuccessors() == 1 &&
+ trueBranch->getSuccessor(0) == falseBranch) {
+ phiBlock = falseBranch;
+ } else {
+ MOZ_ASSERT(falseBranch->getSuccessor(0) == trueBranch);
+ phiBlock = trueBranch;
+ }
+
+ MBasicBlock* testBlock = phiBlock;
+ if (testBlock->numSuccessors() == 1) {
+ MOZ_ASSERT(!testBlock->isLoopBackedge());
+
+ testBlock = testBlock->getSuccessor(0);
+ if (testBlock->numPredecessors() != 1) {
+ return true;
+ }
+ }
+
+ MPhi* phi;
+ MTest* finalTest;
+ if (!BlockIsSingleTest(phiBlock, testBlock, &phi, &finalTest)) {
+ return true;
+ }
+
+ MOZ_ASSERT(phi->numOperands() == 2);
+
+ // If the phi-operand doesn't match the initial input, we can't fold the test.
+ auto* phiInputForInitialBlock =
+ phi->getOperand(phiBlock->indexForPredecessor(initialBlock));
+ if (!IsTestInputMaybeToBool(initialTest, phiInputForInitialBlock)) {
+ return true;
+ }
+
+ // Make sure the test block does not have any outgoing loop backedges.
+ if (!SplitCriticalEdgesForBlock(graph, testBlock)) {
+ return false;
+ }
+
+ MDefinition* trueResult;
+ MDefinition* falseResult;
+ if (phiBlock == trueBranch) {
+ trueResult = phi->getOperand(phiBlock->indexForPredecessor(initialBlock));
+ falseResult = phi->getOperand(phiBlock->indexForPredecessor(falseBranch));
+ } else {
+ trueResult = phi->getOperand(phiBlock->indexForPredecessor(trueBranch));
+ falseResult = phi->getOperand(phiBlock->indexForPredecessor(initialBlock));
+ }
+
+ // OK, we found the desired pattern, now transform the graph.
+
+ // Remove the phi from phiBlock.
+ phiBlock->discardPhi(*phiBlock->phisBegin());
+
+ // Change the end of the block to a test that jumps directly to successors of
+ // testBlock, rather than to testBlock itself.
+
+ if (phiBlock == trueBranch) {
+ if (!UpdateTestSuccessors(graph.alloc(), initialBlock, initialTest->input(),
+ finalTest->ifTrue(), initialTest->ifFalse(),
+ testBlock)) {
+ return false;
+ }
+ } else if (IsTestInputMaybeToBool(initialTest, trueResult)) {
+ if (!UpdateGotoSuccessor(graph.alloc(), trueBranch, finalTest->ifTrue(),
+ testBlock)) {
+ return false;
+ }
+ } else {
+ if (!UpdateTestSuccessors(graph.alloc(), trueBranch, trueResult,
+ finalTest->ifTrue(), finalTest->ifFalse(),
+ testBlock)) {
+ return false;
+ }
+ }
+
+ if (phiBlock == falseBranch) {
+ if (!UpdateTestSuccessors(graph.alloc(), initialBlock, initialTest->input(),
+ initialTest->ifTrue(), finalTest->ifFalse(),
+ testBlock)) {
+ return false;
+ }
+ } else if (IsTestInputMaybeToBool(initialTest, falseResult)) {
+ if (!UpdateGotoSuccessor(graph.alloc(), falseBranch, finalTest->ifFalse(),
+ testBlock)) {
+ return false;
+ }
+ } else {
+ if (!UpdateTestSuccessors(graph.alloc(), falseBranch, falseResult,
+ finalTest->ifTrue(), finalTest->ifFalse(),
+ testBlock)) {
+ return false;
+ }
+ }
+
+ // 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;
+}
+
+static bool MaybeFoldConditionBlock(MIRGraph& graph,
+ MBasicBlock* initialBlock) {
+ if (IsDiamondPattern(initialBlock)) {
+ return MaybeFoldDiamondConditionBlock(graph, initialBlock);
+ }
+ if (IsTrianglePattern(initialBlock)) {
+ return MaybeFoldTriangleConditionBlock(graph, initialBlock);
+ }
+ return true;
+}
+
+static bool MaybeFoldTestBlock(MIRGraph& graph, MBasicBlock* initialBlock) {
+ // Handle test expressions on more than two inputs. For example
+ // |if ((x > 10) && (y > 20) && (z > 30)) { ... }|, which results in the below
+ // pattern.
+ //
+ // Look for the pattern:
+ // ┌─────────────────┐
+ // 1 │ 1 compare │
+ // ┌─────┤ 2 test compare1 │
+ // │ └──────┬──────────┘
+ // │ │0
+ // ┌───────▼─────────┐ │
+ // │ 3 compare │ │
+ // │ 4 test compare3 │ └──────────┐
+ // └──┬──────────┬───┘ │
+ // 1│ │0 │
+ // ┌──────────▼──────┐ │ │
+ // │ 5 compare │ └─────────┐ │
+ // │ 6 goto │ │ │
+ // └───────┬─────────┘ │ │
+ // │ │ │
+ // │ ┌──────────────────▼───────▼───────┐
+ // └───►│ 9 phi compare1 compare3 compare5 │
+ // │10 goto │
+ // └────────────────┬─────────────────┘
+ // │
+ // ┌────────▼────────┐
+ // │11 test phi9 │
+ // └─────┬─────┬─────┘
+ // 1│ │0
+ // ┌────────────┐ │ │ ┌─────────────┐
+ // │ TrueBranch │◄────┘ └─────►│ FalseBranch │
+ // └────────────┘ └─────────────┘
+ //
+ // And transform it to:
+ //
+ // ┌─────────────────┐
+ // 1 │ 1 compare │
+ // ┌───┤ 2 test compare1 │
+ // │ └──────────┬──────┘
+ // │ │0
+ // ┌───────▼─────────┐ │
+ // │ 3 compare │ │
+ // │ 4 test compare3 │ │
+ // └──┬─────────┬────┘ │
+ // 1│ │0 │
+ // ┌──────────▼──────┐ │ │
+ // │ 5 compare │ └──────┐ │
+ // │ 6 test compare5 │ │ │
+ // └────┬────────┬───┘ │ │
+ // 1│ │0 │ │
+ // ┌─────▼──────┐ │ ┌───▼──▼──────┐
+ // │ TrueBranch │ └─────────► FalseBranch │
+ // └────────────┘ └─────────────┘
+
+ auto* ins = initialBlock->lastIns();
+ if (!ins->isTest()) {
+ return true;
+ }
+ auto* initialTest = ins->toTest();
+
+ MBasicBlock* trueBranch = initialTest->ifTrue();
+ MBasicBlock* falseBranch = initialTest->ifFalse();
+
+ // MaybeFoldConditionBlock handles the case for two operands.
+ MBasicBlock* phiBlock;
+ if (trueBranch->numPredecessors() > 2) {
+ phiBlock = trueBranch;
+ } else if (falseBranch->numPredecessors() > 2) {
+ phiBlock = falseBranch;
+ } else {
+ return true;
+ }
+
+ MBasicBlock* testBlock = phiBlock;
+ if (testBlock->numSuccessors() == 1) {
+ if (testBlock->isLoopBackedge()) {
+ return true;
+ }
+ testBlock = testBlock->getSuccessor(0);
+ if (testBlock->numPredecessors() != 1) {
+ return true;
+ }
+ }
+
+ MOZ_ASSERT(!phiBlock->isLoopBackedge());
+
+ MPhi* phi = nullptr;
+ MTest* finalTest = nullptr;
+ if (!BlockIsSingleTest(phiBlock, testBlock, &phi, &finalTest)) {
+ return true;
+ }
+
+ MOZ_ASSERT(phiBlock->numPredecessors() == phi->numOperands());
+
+ // If the phi-operand doesn't match the initial input, we can't fold the test.
+ auto* phiInputForInitialBlock =
+ phi->getOperand(phiBlock->indexForPredecessor(initialBlock));
+ if (!IsTestInputMaybeToBool(initialTest, phiInputForInitialBlock)) {
+ return true;
+ }
+
+ MBasicBlock* newTestBlock = nullptr;
+ MDefinition* newTestInput = nullptr;
+
+ // The block of each phi operand must either end with a test instruction on
+ // that phi operand or it's the sole block which ends with a goto instruction.
+ for (size_t i = 0; i < phiBlock->numPredecessors(); i++) {
+ auto* pred = phiBlock->getPredecessor(i);
+ auto* operand = phi->getOperand(i);
+
+ // Each predecessor must end with either a test or goto instruction.
+ auto* lastIns = pred->lastIns();
+ if (lastIns->isGoto() && !newTestBlock) {
+ newTestBlock = pred;
+ newTestInput = operand;
+ } else if (lastIns->isTest()) {
+ if (!IsTestInputMaybeToBool(lastIns->toTest(), operand)) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+
+ MOZ_ASSERT(!pred->isLoopBackedge());
+ }
+
+ // Ensure we found the single goto block.
+ if (!newTestBlock) {
+ return true;
+ }
+
+ // Make sure the test block does not have any outgoing loop backedges.
+ if (!SplitCriticalEdgesForBlock(graph, testBlock)) {
+ return false;
+ }
+
+ // OK, we found the desired pattern, now transform the graph.
+
+ // Remove the phi from phiBlock.
+ phiBlock->discardPhi(*phiBlock->phisBegin());
+
+ // Create the new test instruction.
+ if (!UpdateTestSuccessors(graph.alloc(), newTestBlock, newTestInput,
+ finalTest->ifTrue(), finalTest->ifFalse(),
+ testBlock)) {
+ return false;
+ }
+
+ // Update all test instructions to point to the final target.
+ while (phiBlock->numPredecessors()) {
+ mozilla::DebugOnly<size_t> oldNumPred = phiBlock->numPredecessors();
+
+ auto* pred = phiBlock->getPredecessor(0);
+ auto* test = pred->lastIns()->toTest();
+ if (test->ifTrue() == phiBlock) {
+ if (!UpdateTestSuccessors(graph.alloc(), pred, test->input(),
+ finalTest->ifTrue(), test->ifFalse(),
+ testBlock)) {
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(test->ifFalse() == phiBlock);
+ if (!UpdateTestSuccessors(graph.alloc(), pred, test->input(),
+ test->ifTrue(), finalTest->ifFalse(),
+ testBlock)) {
+ return false;
+ }
+ }
+
+ // Ensure we've made progress.
+ MOZ_ASSERT(phiBlock->numPredecessors() + 1 == oldNumPred);
+ }
+
+ // 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 (PostorderIterator block(graph.poBegin()); block != graph.poEnd();
+ block++) {
+ if (!MaybeFoldConditionBlock(graph, *block)) {
+ return false;
+ }
+ if (!MaybeFoldTestBlock(graph, *block)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool jit::FoldEmptyBlocks(MIRGraph& graph) {
+ for (MBasicBlockIterator iter(graph.begin()); iter != graph.end();) {
+ MBasicBlock* block = *iter;
+ iter++;
+
+ if (block->numPredecessors() != 1 || block->numSuccessors() != 1) {
+ continue;
+ }
+
+ if (!block->phisEmpty()) {
+ continue;
+ }
+
+ if (block->outerResumePoint()) {
+ continue;
+ }
+
+ if (*block->begin() != *block->rbegin()) {
+ continue;
+ }
+
+ MBasicBlock* succ = block->getSuccessor(0);
+ MBasicBlock* pred = block->getPredecessor(0);
+
+ if (succ->numPredecessors() != 1) {
+ continue;
+ }
+
+ size_t pos = pred->getSuccessorIndex(block);
+ pred->lastIns()->replaceSuccessor(pos, succ);
+
+ graph.removeBlock(block);
+
+ if (!succ->addPredecessorSameInputsAs(pred, block)) {
+ return false;
+ }
+ succ->removePredecessor(block);
+ }
+ 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() != ResumeMode::ResumeAt) {
+ return;
+ }
+
+ jsbytecode* pc = rp->pc();
+ if (JSOp(*pc) == JSOp::JumpTarget) {
+ pc += JSOpLength_JumpTarget;
+ }
+ if (JSOp(*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 pass only replaces resume points which are
+// trivially dead.
+//
+// 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::EliminateTriviallyDeadResumePointOperands(MIRGenerator* mir,
+ MIRGraph& graph) {
+ for (auto* block : graph) {
+ if (MResumePoint* rp = block->entryResumePoint()) {
+ if (!graph.alloc().ensureBallast()) {
+ return false;
+ }
+ ::EliminateTriviallyDeadResumePointOperands(graph, rp);
+ }
+ }
+ return true;
+}
+
+// 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()) {
+ if (!graph.alloc().ensureBallast()) {
+ return false;
+ }
+ ::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()) {
+ if (!graph.alloc().ensureBallast()) {
+ return false;
+ }
+ ::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->isBoxNonStrictThis()) {
+ continue;
+ }
+
+ // Early intermediate values captured by resume points, such as
+ // 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->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()) {
+ 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 = std::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) {
+ // Effectful instructions of course cannot be removed.
+ if (def->isEffectful()) {
+ return false;
+ }
+
+ // Never eliminate guard instructions.
+ if (def->isGuard()) {
+ return false;
+ }
+
+ // Required to be preserved, as the type guard related to this instruction
+ // is part of the semantics of a transformation.
+ if (def->isGuardRangeBailouts()) {
+ return false;
+ }
+
+ // Control instructions have no uses, but also shouldn't be optimized out
+ if (def->isControlInstruction()) {
+ return false;
+ }
+
+ // Used when lowering to generate the corresponding snapshots and aggregate
+ // the list of recover instructions to be repeated.
+ if (def->isInstruction() && def->toInstruction()->resumePoint()) {
+ return false;
+ }
+
+ return true;
+}
+
+// Similar to DeadIfUnused(), but additionally allows effectful instructions.
+bool js::jit::DeadIfUnusedAllowEffectful(const MDefinition* def) {
+ // Never eliminate guard instructions.
+ if (def->isGuard()) {
+ return false;
+ }
+
+ // Required to be preserved, as the type guard related to this instruction
+ // is part of the semantics of a transformation.
+ if (def->isGuardRangeBailouts()) {
+ return false;
+ }
+
+ // Control instructions have no uses, but also shouldn't be optimized out
+ if (def->isControlInstruction()) {
+ return false;
+ }
+
+ // Used when lowering to generate the corresponding snapshots and aggregate
+ // the list of recover instructions to be repeated.
+ if (def->isInstruction() && def->toInstruction()->resumePoint()) {
+ // All effectful instructions must have a resume point attached. We're
+ // allowing effectful instructions here, so we have to ignore any resume
+ // points if we want to consider effectful instructions as dead.
+ if (!def->isEffectful()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// 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());
+}
+
+// Similar to IsDiscardable(), but additionally allows effectful instructions.
+bool js::jit::IsDiscardableAllowEffectful(const MDefinition* def) {
+ return !def->hasUses() &&
+ (DeadIfUnusedAllowEffectful(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()) {
+ 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<MPhi*, 16, SystemAllocPolicy> 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<MPhi*, 0, SystemAllocPolicy> 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;
+ }
+
+ [[nodiscard]] bool propagateAllPhiSpecializations();
+
+ bool respecialize(MPhi* phi, MIRType type);
+ bool propagateSpecialization(MPhi* phi);
+ bool specializePhis();
+ bool specializeOsrOnlyPhis();
+ 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();
+ bool propagateUnbox();
+
+ bool shouldSpecializeOsrPhis() const;
+ MIRType guessPhiType(MPhi* phi) const;
+
+ public:
+ TypeAnalyzer(MIRGenerator* mir, MIRGraph& graph) : mir(mir), graph(graph) {}
+
+ bool analyze();
+};
+
+} /* anonymous namespace */
+
+bool TypeAnalyzer::shouldSpecializeOsrPhis() const {
+ // [SMDOC] OSR Phi Type Specialization
+ //
+ // Without special handling for OSR phis, we end up with unspecialized phis
+ // (MIRType::Value) in the loop (pre)header and other blocks, resulting in
+ // unnecessary boxing and unboxing in the loop body.
+ //
+ // To fix this, phi type specialization needs special code to deal with the
+ // OSR entry block. Recall that OSR results in the following basic block
+ // structure:
+ //
+ // +------------------+ +-----------------+
+ // | Code before loop | | OSR entry block |
+ // +------------------+ +-----------------+
+ // | |
+ // | |
+ // | +---------------+ |
+ // +---------> | OSR preheader | <---------+
+ // +---------------+
+ // |
+ // V
+ // +---------------+
+ // | Loop header |<-----+
+ // +---------------+ |
+ // | |
+ // ... |
+ // | |
+ // +---------------+ |
+ // | Loop backedge |------+
+ // +---------------+
+ //
+ // OSR phi specialization happens in three steps:
+ //
+ // (1) Specialize phis but ignore MOsrValue phi inputs. In other words,
+ // pretend the OSR entry block doesn't exist. See guessPhiType.
+ //
+ // (2) Once phi specialization is done, look at the types of loop header phis
+ // and add these types to the corresponding preheader phis. This way, the
+ // types of the preheader phis are based on the code before the loop and
+ // the code in the loop body. These are exactly the types we expect for
+ // the OSR Values. See the last part of TypeAnalyzer::specializePhis.
+ //
+ // (3) For type-specialized preheader phis, add guard/unbox instructions to
+ // the OSR entry block to guard the incoming Value indeed has this type.
+ // This happens in:
+ //
+ // * TypeAnalyzer::adjustPhiInputs: adds a fallible unbox for values that
+ // can be unboxed.
+ //
+ // * TypeAnalyzer::replaceRedundantPhi: adds a type guard for values that
+ // can't be unboxed (null/undefined/magic Values).
+ if (!mir->graph().osrBlock()) {
+ return false;
+ }
+
+ return !mir->outerInfo().hadSpeculativePhiBailout();
+}
+
+// Try to specialize this phi based on its non-cyclic inputs.
+MIRType TypeAnalyzer::guessPhiType(MPhi* phi) const {
+#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::MagicHole ||
+ in->type() == MIRType::MagicIsConstructing) {
+ if (magicType == MIRType::None) {
+ magicType = in->type();
+ }
+ MOZ_ASSERT(magicType == in->type());
+ }
+ }
+#endif
+
+ MIRType type = MIRType::None;
+ bool convertibleToFloat32 = false;
+ bool hasOSRValueInput = false;
+ DebugOnly<bool> hasSpecializableInput = false;
+ for (size_t i = 0, e = phi->numOperands(); i < e; i++) {
+ MDefinition* in = phi->getOperand(i);
+ if (in->isPhi()) {
+ hasSpecializableInput = 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;
+ }
+ }
+
+ // See shouldSpecializeOsrPhis comment. This is the first step mentioned
+ // there.
+ if (shouldSpecializeOsrPhis() && in->isOsrValue()) {
+ hasOSRValueInput = true;
+ hasSpecializableInput = true;
+ continue;
+ }
+
+ if (type == MIRType::None) {
+ type = in->type();
+ if (in->canProduceFloat32() &&
+ !mir->outerInfo().hadSpeculativePhiBailout()) {
+ convertibleToFloat32 = true;
+ }
+ continue;
+ }
+
+ if (type == in->type()) {
+ convertibleToFloat32 = convertibleToFloat32 && in->canProduceFloat32();
+ } else {
+ 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 = convertibleToFloat32 && in->canProduceFloat32();
+ } else {
+ return MIRType::Value;
+ }
+ }
+ }
+
+ if (hasOSRValueInput && type == MIRType::Float32) {
+ // TODO(post-Warp): simplify float32 handling in this function or (better)
+ // make the float32 analysis a stand-alone optimization pass instead of
+ // complicating type analysis. See bug 1655773.
+ type = MIRType::Double;
+ }
+
+ MOZ_ASSERT_IF(type == MIRType::None, hasSpecializableInput);
+ 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 it can't be specialized as float32, specialize
+ // as double.
+ MIRType type = phi->type();
+ if (type == MIRType::Float32 && !use->canProduceFloat32()) {
+ type = MIRType::Double;
+ }
+ if (!respecialize(use, 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::propagateAllPhiSpecializations() {
+ while (!phiWorklist_.empty()) {
+ if (mir->shouldCancel("Specialize Phis (worklist)")) {
+ return false;
+ }
+
+ MPhi* phi = popPhi();
+ if (!propagateSpecialization(phi)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// If branch pruning removes the path from the entry block to the OSR
+// preheader, we may have phis (or chains of phis) with no operands
+// other than OsrValues. These phis will still have MIRType::None.
+// Since we don't have any information about them, we specialize them
+// as MIRType::Value.
+bool TypeAnalyzer::specializeOsrOnlyPhis() {
+ MOZ_ASSERT(graph.osrBlock());
+ MOZ_ASSERT(graph.osrPreHeaderBlock()->numPredecessors() == 1);
+
+ for (PostorderIterator block(graph.poBegin()); block != graph.poEnd();
+ block++) {
+ if (mir->shouldCancel("Specialize osr-only phis (main loop)")) {
+ return false;
+ }
+
+ for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd(); phi++) {
+ if (mir->shouldCancel("Specialize osr-only phis (inner loop)")) {
+ return false;
+ }
+
+ if (phi->type() == MIRType::None) {
+ phi->specialize(MIRType::Value);
+ }
+ }
+ }
+ return true;
+}
+
+bool TypeAnalyzer::specializePhis() {
+ 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;
+ }
+
+ MIRType type = guessPhiType(*phi);
+ 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.
+ continue;
+ }
+ if (!propagateSpecialization(*phi)) {
+ return false;
+ }
+ }
+ }
+
+ if (!propagateAllPhiSpecializations()) {
+ return false;
+ }
+
+ if (shouldSpecializeOsrPhis()) {
+ // See shouldSpecializeOsrPhis comment. This is the second step, propagating
+ // loop header phi types to preheader phis.
+ MBasicBlock* preHeader = graph.osrPreHeaderBlock();
+ MBasicBlock* header = preHeader->getSingleSuccessor();
+
+ if (preHeader->numPredecessors() == 1) {
+ MOZ_ASSERT(preHeader->getPredecessor(0) == graph.osrBlock());
+ // Branch pruning has removed the path from the entry block
+ // to the preheader. Specialize any phis with no non-osr inputs.
+ if (!specializeOsrOnlyPhis()) {
+ return false;
+ }
+ } else if (header->isLoopHeader()) {
+ for (MPhiIterator phi(header->phisBegin()); phi != header->phisEnd();
+ phi++) {
+ MPhi* preHeaderPhi = phi->getOperand(0)->toPhi();
+ MOZ_ASSERT(preHeaderPhi->block() == preHeader);
+
+ if (preHeaderPhi->type() == MIRType::Value) {
+ // Already includes everything.
+ continue;
+ }
+
+ MIRType loopType = phi->type();
+ if (!respecialize(preHeaderPhi, loopType)) {
+ return false;
+ }
+ }
+ if (!propagateAllPhiSpecializations()) {
+ return false;
+ }
+ } else {
+ // Edge case: there is no backedge in this loop. This can happen
+ // if the header is a 'pending' loop header when control flow in
+ // the loop body is terminated unconditionally, or if a block
+ // that dominates the backedge unconditionally bails out. In
+ // this case the header only has the preheader as predecessor
+ // and we don't need to do anything.
+ MOZ_ASSERT(header->numPredecessors() == 1);
+ }
+ }
+
+ MOZ_ASSERT(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);
+ unbox->setBailoutKind(BailoutKind::SpeculativePhi);
+ 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);
+ }
+
+ replacement->setBailoutKind(BailoutKind::SpeculativePhi);
+ 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. Don't bother optimizing if magic values are involved.
+ if (in->isUnbox()) {
+ MDefinition* unboxInput = in->toUnbox()->input();
+ if (!IsMagicType(unboxInput->type()) && phi->typeIncludes(unboxInput)) {
+ 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();
+ const 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::MagicOptimizedOut:
+ v = MagicValue(JS_OPTIMIZED_OUT);
+ break;
+ case MIRType::MagicUninitializedLexical:
+ v = MagicValue(JS_UNINITIALIZED_LEXICAL);
+ break;
+ case MIRType::MagicIsConstructing:
+ v = MagicValue(JS_IS_CONSTRUCTING);
+ break;
+ case MIRType::MagicHole:
+ 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);
+
+ if (shouldSpecializeOsrPhis()) {
+ // See shouldSpecializeOsrPhis comment. This is part of the third step,
+ // guard the incoming MOsrValue is of this type.
+ for (uint32_t i = 0; i < phi->numOperands(); i++) {
+ MDefinition* def = phi->getOperand(i);
+ if (def->type() != phi->type()) {
+ MOZ_ASSERT(def->isOsrValue() || def->isPhi());
+ MOZ_ASSERT(def->type() == MIRType::Value);
+ MGuardValue* guard = MGuardValue::New(alloc(), def, v);
+ guard->setBailoutKind(BailoutKind::SpeculativePhi);
+ def->block()->insertBefore(def->block()->lastIns(), guard);
+ }
+ }
+ }
+}
+
+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 (IsNullOrUndefined(phi->type()) || IsMagicType(phi->type())) {
+ // We can replace this phi with a constant.
+ if (!alloc().ensureBallast()) {
+ return false;
+ }
+ 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;
+}
+
+/* clang-format off */
+//
+// 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.
+//
+/* clang-format on */
+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 = !phi->isImplicitlyUsed();
+ 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;
+ }
+
+ // WarpBuilder skips over code that can't be reached except through
+ // a catch block. Locals and arguments may be observable in such
+ // code after bailing out, so we can't rely on seeing all uses.
+ if (graph.hasTryBlock()) {
+ 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;
+}
+
+static bool HappensBefore(const MDefinition* earlier,
+ const MDefinition* later) {
+ MOZ_ASSERT(earlier->block() == later->block());
+
+ for (auto* ins : *earlier->block()) {
+ if (ins == earlier) {
+ return true;
+ }
+ if (ins == later) {
+ return false;
+ }
+ }
+ MOZ_CRASH("earlier and later are instructions in the block");
+}
+
+// Propagate type information from dominating unbox instructions.
+//
+// This optimization applies for example for self-hosted String.prototype
+// functions.
+//
+// Example:
+// ```
+// String.prototype.id = function() {
+// // Strict mode to avoid ToObject on primitive this-values.
+// "use strict";
+//
+// // Template string to apply ToString on the this-value.
+// return `${this}`;
+// };
+//
+// function f(s) {
+// // Assume |s| is a string value.
+// return s.id();
+// }
+// ```
+//
+// Compiles into: (Graph after Scalar Replacement)
+//
+// ┌───────────────────────────────────────────────────────────────────────────┐
+// │ Block 0 │
+// │ resumepoint 1 0 2 2 │
+// │ 0 parameter THIS_SLOT Value │
+// │ 1 parameter 0 Value │
+// │ 2 constant undefined Undefined │
+// │ 3 start │
+// │ 4 checkoverrecursed │
+// │ 5 unbox parameter1 to String (fallible) String │
+// │ 6 constant object 1d908053e088 (String) Object │
+// │ 7 guardshape constant6:Object Object │
+// │ 8 slots guardshape7:Object Slots │
+// │ 9 loaddynamicslot slots8:Slots (slot 53) Value │
+// │ 10 constant 0x0 Int32 │
+// │ 11 unbox loaddynamicslot9 to Object (fallible) Object │
+// │ 12 nurseryobject Object │
+// │ 13 guardspecificfunction unbox11:Object nurseryobject12:Object Object │
+// │ 14 goto block1 │
+// └──────────────────────────────────┬────────────────────────────────────────┘
+// │
+// ┌──────────────────────────────────▼────────────────────────────────────────┐
+// │ Block 1 │
+// │ ((0)) resumepoint 15 1 15 15 | 1 13 1 0 2 2 │
+// │ 15 constant undefined Undefined │
+// │ 16 tostring parameter1:Value String │
+// │ 18 goto block2 │
+// └──────────────────────────────────┬────────────────────────────────────────┘
+// │
+// ┌──────────────▼──────────────┐
+// │ Block 2 │
+// │ resumepoint 16 1 0 2 2 │
+// │ 19 return tostring16:String │
+// └─────────────────────────────┘
+//
+// The Unbox instruction is used as a type guard. The ToString instruction
+// doesn't use the type information from the preceding Unbox instruction and
+// therefore has to assume its operand can be any value.
+//
+// When instead propagating the type information from the preceding Unbox
+// instruction, this graph is constructed after the "Apply types" phase:
+//
+// ┌───────────────────────────────────────────────────────────────────────────┐
+// │ Block 0 │
+// │ resumepoint 1 0 2 2 │
+// │ 0 parameter THIS_SLOT Value │
+// │ 1 parameter 0 Value │
+// │ 2 constant undefined Undefined │
+// │ 3 start │
+// │ 4 checkoverrecursed │
+// │ 5 unbox parameter1 to String (fallible) String │
+// │ 6 constant object 1d908053e088 (String) Object │
+// │ 7 guardshape constant6:Object Object │
+// │ 8 slots guardshape7:Object Slots │
+// │ 9 loaddynamicslot slots8:Slots (slot 53) Value │
+// │ 10 constant 0x0 Int32 │
+// │ 11 unbox loaddynamicslot9 to Object (fallible) Object │
+// │ 12 nurseryobject Object │
+// │ 13 guardspecificfunction unbox11:Object nurseryobject12:Object Object │
+// │ 14 goto block1 │
+// └──────────────────────────────────┬────────────────────────────────────────┘
+// │
+// ┌──────────────────────────────────▼────────────────────────────────────────┐
+// │ Block 1 │
+// │ ((0)) resumepoint 15 1 15 15 | 1 13 1 0 2 2 │
+// │ 15 constant undefined Undefined │
+// │ 20 unbox parameter1 to String (fallible) String │
+// │ 16 tostring parameter1:Value String │
+// │ 18 goto block2 │
+// └──────────────────────────────────┬────────────────────────────────────────┘
+// │
+// ┌──────────────▼─────────────────────┐
+// │ Block 2 │
+// │ resumepoint 16 1 0 2 2 │
+// │ 21 box tostring16:String Value │
+// │ 19 return box21:Value │
+// └────────────────────────────────────┘
+//
+// GVN will later merge both Unbox instructions and fold away the ToString
+// instruction, so we get this final graph:
+//
+// ┌───────────────────────────────────────────────────────────────────────────┐
+// │ Block 0 │
+// │ resumepoint 1 0 2 2 │
+// │ 0 parameter THIS_SLOT Value │
+// │ 1 parameter 0 Value │
+// │ 2 constant undefined Undefined │
+// │ 3 start │
+// │ 4 checkoverrecursed │
+// │ 5 unbox parameter1 to String (fallible) String │
+// │ 6 constant object 1d908053e088 (String) Object │
+// │ 7 guardshape constant6:Object Object │
+// │ 8 slots guardshape7:Object Slots │
+// │ 22 loaddynamicslotandunbox slots8:Slots (slot 53) Object │
+// │ 11 nurseryobject Object │
+// │ 12 guardspecificfunction load22:Object nurseryobject11:Object Object │
+// │ 13 goto block1 │
+// └──────────────────────────────────┬────────────────────────────────────────┘
+// │
+// ┌──────────────────────────────────▼────────────────────────────────────────┐
+// │ Block 1 │
+// │ ((0)) resumepoint 2 1 2 2 | 1 12 1 0 2 2 │
+// │ 14 goto block2 │
+// └──────────────────────────────────┬────────────────────────────────────────┘
+// │
+// ┌──────────────▼─────────────────────┐
+// │ Block 2 │
+// │ resumepoint 5 1 0 2 2 │
+// │ 15 return parameter1:Value │
+// └────────────────────────────────────┘
+//
+bool TypeAnalyzer::propagateUnbox() {
+ // Visit the blocks in post-order, so that the type information of the closest
+ // unbox operation is used.
+ for (PostorderIterator block(graph.poBegin()); block != graph.poEnd();
+ block++) {
+ if (mir->shouldCancel("Propagate Unbox")) {
+ return false;
+ }
+
+ // Iterate over all instructions to look for unbox instructions.
+ for (MInstructionIterator iter(block->begin()); iter != block->end();
+ iter++) {
+ if (!iter->isUnbox()) {
+ continue;
+ }
+
+ auto* unbox = iter->toUnbox();
+ auto* input = unbox->input();
+
+ // Ignore unbox operations on typed values.
+ if (input->type() != MIRType::Value) {
+ continue;
+ }
+
+ // Ignore unbox to floating point types, because propagating boxed Int32
+ // values as Double can lead to repeated bailouts when later instructions
+ // expect Int32 inputs.
+ if (IsFloatingPointType(unbox->type())) {
+ continue;
+ }
+
+ // Inspect other uses of |input| to propagate the unboxed type information
+ // from |unbox|.
+ for (auto uses = input->usesBegin(); uses != input->usesEnd();) {
+ auto* use = *uses++;
+
+ // Ignore resume points.
+ if (!use->consumer()->isDefinition()) {
+ continue;
+ }
+ auto* def = use->consumer()->toDefinition();
+
+ // Ignore any unbox operations, including the current |unbox|.
+ if (def->isUnbox()) {
+ continue;
+ }
+
+ // Ignore phi nodes, because we don't yet support them.
+ if (def->isPhi()) {
+ continue;
+ }
+
+ // The unbox operation needs to happen before the other use, otherwise
+ // we can't propagate the type information.
+ if (unbox->block() == def->block()) {
+ if (!HappensBefore(unbox, def)) {
+ continue;
+ }
+ } else {
+ if (!unbox->block()->dominates(def->block())) {
+ continue;
+ }
+ }
+
+ // Replace the use with |unbox|, so that GVN knows about the actual
+ // value type and can more easily fold unnecessary operations. If the
+ // instruction actually needs a boxed input, the BoxPolicy type policy
+ // will simply unwrap the unbox instruction.
+ use->replaceProducer(unbox);
+
+ // The uses in the MIR graph no longer reflect the uses in the bytecode,
+ // so we have to mark |input| as implicitly used.
+ input->setImplicitlyUsedUnchecked();
+ }
+ }
+ }
+ return true;
+}
+
+bool TypeAnalyzer::analyze() {
+ if (!tryEmitFloatOperations()) {
+ return false;
+ }
+ if (!specializePhis()) {
+ return false;
+ }
+ if (!propagateUnbox()) {
+ 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;
+}
+
+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 adds/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) {
+ if (!AliasAnalysis(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;
+ }
+
+ FlagAllOperandsAsImplicitlyUsed(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.removeBlock(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) {
+ MOZ_ASSERT(graph.canBuildDominators());
+
+ ComputeImmediateDominators(graph);
+
+ Vector<MBasicBlock*, 4, JitAllocPolicy> 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
+ Fprinter print(stderr);
+ print.printf("==Check Operand\n");
+ use->producer()->dump(print);
+ print.printf(" index: %zu\n", use->consumer()->indexOf(use));
+ use->consumer()->dump(print);
+ print.printf("==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
+ Fprinter print(stderr);
+ print.printf("==Check Use\n");
+ use->producer()->dump(print);
+ print.printf(" index: %zu\n", use->consumer()->indexOf(use));
+ use->consumer()->dump(print);
+ print.printf("==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, bool force) {
+#ifdef DEBUG
+ if (!JitOptions.fullDebugChecks && !force) {
+ return;
+ }
+
+ 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, bool force) {
+#ifdef DEBUG
+ if (!JitOptions.checkGraphConsistency) {
+ return;
+ }
+ if (!JitOptions.fullDebugChecks && !force) {
+ return;
+ }
+ AssertBasicGraphCoherency(graph, force);
+ 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::BigInt:
+ case MIRType::Object:
+ case MIRType::Shape:
+ case MIRType::MagicOptimizedOut:
+ case MIRType::MagicUninitializedLexical:
+ case MIRType::MagicIsConstructing:
+ case MIRType::Value:
+ case MIRType::Simd128:
+ return true;
+
+ case MIRType::MagicHole:
+ case MIRType::None:
+ case MIRType::Slots:
+ case MIRType::Elements:
+ case MIRType::Pointer:
+ case MIRType::Int64:
+ case MIRType::WasmAnyRef:
+ case MIRType::WasmArrayData:
+ case MIRType::StackResults:
+ case MIRType::IntPtr:
+ 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);
+ MOZ_ASSERT(op->block()->dominates(resume->block()),
+ "Resume point is not dominated by its operands");
+ }
+}
+#endif // DEBUG
+
+void jit::AssertExtendedGraphCoherency(MIRGraph& graph, bool underValueNumberer,
+ bool force) {
+ // 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;
+ }
+ if (!JitOptions.fullDebugChecks && !force) {
+ return;
+ }
+
+ AssertGraphCoherency(graph, force);
+
+ AssertDominatorTree(graph);
+
+ DebugOnly<uint32_t> 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) {
+ 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<uint32_t, BoundsCheckInfo, DefaultHasher<uint32_t>,
+ 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 TruncateKind::NoTruncate:
+ case TruncateKind::TruncateAfterBailouts:
+ // TruncateAfterBailouts is considered as infinite space because the
+ // LinearSum will effectively remove the bailout check.
+ return MathSpace::Infinite;
+ case TruncateKind::IndirectTruncate:
+ case TruncateKind::Truncate:
+ return MathSpace::Modulo;
+ }
+ MOZ_CRASH("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,
+ int32_t recursionDepth) {
+ const int32_t SAFE_RECURSION_LIMIT = 100;
+ if (recursionDepth > SAFE_RECURSION_LIMIT) {
+ return SimpleLinearSum(ins, 0);
+ }
+
+ // Unwrap Int32ToIntPtr. This instruction only changes the representation
+ // (int32_t to intptr_t) without affecting the value.
+ if (ins->isInt32ToIntPtr()) {
+ ins = ins->toInt32ToIntPtr()->input();
+ }
+
+ if (ins->isBeta()) {
+ ins = ins->getOperand(0);
+ }
+
+ MOZ_ASSERT(!ins->isInt32ToIntPtr());
+
+ 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, recursionDepth + 1);
+ SimpleLinearSum rsum = ExtractLinearSum(rhs, space, recursionDepth + 1);
+
+ // 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 <SUM> + n or n + <SUM>.
+ if (ins->isAdd()) {
+ int32_t constant;
+ if (space == MathSpace::Modulo) {
+ constant = uint32_t(lsum.constant) + uint32_t(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 <SUM> - n.
+ if (lsum.term) {
+ int32_t constant;
+ if (space == MathSpace::Modulo) {
+ constant = uint32_t(lsum.constant) - uint32_t(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 - <SUM>.
+ 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(std::min(minimumA, minimumB), sumA.constant, &newMinimum) ||
+ !SafeSub(std::max(maximumA, maximumB), sumA.constant, &newMaximum)) {
+ return false;
+ }
+
+ dominating->setMinimum(newMinimum);
+ dominating->setMaximum(newMaximum);
+ dominating->setBailoutKind(BailoutKind::HoistBoundsCheck);
+
+ return true;
+}
+
+// Eliminate checks which are redundant given each other or other 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());
+
+ // Stack for pre-order CFG traversal.
+ Vector<MBasicBlock*, 1, JitAllocPolicy> 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;
+ }
+ }
+ }
+
+ // 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++;
+
+ if (!def->isBoundsCheck()) {
+ continue;
+ }
+ auto* boundsCheck = def->toBoundsCheck();
+
+ bool eliminated = false;
+ if (!TryEliminateBoundsCheck(checks, index, boundsCheck, &eliminated)) {
+ return false;
+ }
+
+ if (eliminated) {
+ block->discard(boundsCheck);
+ }
+ }
+ index++;
+ }
+
+ MOZ_ASSERT(index == graph.numBlocks());
+
+ return true;
+}
+
+static bool ShapeGuardIsRedundant(MGuardShape* guard,
+ const MDefinition* storeObject,
+ const Shape* storeShape) {
+ const MDefinition* guardObject = guard->object()->skipObjectGuards();
+ if (guardObject != storeObject) {
+ JitSpew(JitSpew_RedundantShapeGuards, "SKIP: different objects (%d vs %d)",
+ guardObject->id(), storeObject->id());
+ return false;
+ }
+
+ const Shape* guardShape = guard->shape();
+ if (guardShape != storeShape) {
+ JitSpew(JitSpew_RedundantShapeGuards, "SKIP: different shapes");
+ return false;
+ }
+
+ return true;
+}
+
+// Eliminate shape guards which are redundant given other instructions.
+//
+// A shape guard is redundant if we can prove that the object being
+// guarded already has the correct shape. The conditions for doing so
+// are as follows:
+//
+// 1. We can see the most recent change to the shape of this object.
+// (This can be an AddAndStoreSlot, an AllocateAndStoreSlot, or the
+// creation of the object itself.
+// 2. That mutation dominates the shape guard.
+// 3. The shape that was assigned at that point matches the shape
+// we expect.
+//
+// If all of these conditions hold, then we can remove the shape guard.
+// In debug, we replace it with an AssertShape to help verify correctness.
+bool jit::EliminateRedundantShapeGuards(MIRGraph& graph) {
+ JitSpew(JitSpew_RedundantShapeGuards, "Begin");
+
+ for (ReversePostorderIterator block = graph.rpoBegin();
+ block != graph.rpoEnd(); block++) {
+ for (MInstructionIterator insIter(block->begin());
+ insIter != block->end();) {
+ MInstruction* ins = *insIter;
+ insIter++;
+
+ // Skip instructions that aren't shape guards.
+ if (!ins->isGuardShape()) {
+ continue;
+ }
+ MGuardShape* guard = ins->toGuardShape();
+ MDefinition* lastStore = guard->dependency();
+
+ JitSpew(JitSpew_RedundantShapeGuards, "Visit shape guard %d",
+ guard->id());
+ JitSpewIndent spewIndent(JitSpew_RedundantShapeGuards);
+
+ if (lastStore->isDiscarded() || lastStore->block()->isDead() ||
+ !lastStore->block()->dominates(guard->block())) {
+ JitSpew(JitSpew_RedundantShapeGuards,
+ "SKIP: ins %d does not dominate block %d", lastStore->id(),
+ guard->block()->id());
+ continue;
+ }
+
+ if (lastStore->isAddAndStoreSlot()) {
+ auto* add = lastStore->toAddAndStoreSlot();
+ auto* addObject = add->object()->skipObjectGuards();
+ if (!ShapeGuardIsRedundant(guard, addObject, add->shape())) {
+ continue;
+ }
+ } else if (lastStore->isAllocateAndStoreSlot()) {
+ auto* allocate = lastStore->toAllocateAndStoreSlot();
+ auto* allocateObject = allocate->object()->skipObjectGuards();
+ if (!ShapeGuardIsRedundant(guard, allocateObject, allocate->shape())) {
+ continue;
+ }
+ } else if (lastStore->isStart()) {
+ // The guard doesn't depend on any other instruction that is modifying
+ // the object operand, so we check the object operand directly.
+ auto* obj = guard->object()->skipObjectGuards();
+
+ const Shape* initialShape = nullptr;
+ if (obj->isNewObject()) {
+ auto* templateObject = obj->toNewObject()->templateObject();
+ if (!templateObject) {
+ JitSpew(JitSpew_RedundantShapeGuards, "SKIP: no template");
+ continue;
+ }
+ initialShape = templateObject->shape();
+ } else if (obj->isNewPlainObject()) {
+ initialShape = obj->toNewPlainObject()->shape();
+ } else {
+ JitSpew(JitSpew_RedundantShapeGuards,
+ "SKIP: not NewObject or NewPlainObject (%d)", obj->id());
+ continue;
+ }
+ if (initialShape != guard->shape()) {
+ JitSpew(JitSpew_RedundantShapeGuards, "SKIP: shapes don't match");
+ continue;
+ }
+ } else {
+ JitSpew(JitSpew_RedundantShapeGuards,
+ "SKIP: Last store not supported (%d)", lastStore->id());
+ continue;
+ }
+
+#ifdef DEBUG
+ if (!graph.alloc().ensureBallast()) {
+ return false;
+ }
+ auto* assert = MAssertShape::New(graph.alloc(), guard->object(),
+ const_cast<Shape*>(guard->shape()));
+ guard->block()->insertBefore(guard, assert);
+#endif
+
+ JitSpew(JitSpew_RedundantShapeGuards, "SUCCESS: Removing shape guard %d",
+ guard->id());
+ guard->replaceAllUsesWith(guard->input());
+ guard->block()->discard(guard);
+ }
+ }
+
+ return true;
+}
+
+static bool TryEliminateGCBarriersForAllocation(TempAllocator& alloc,
+ MInstruction* allocation) {
+ MOZ_ASSERT(allocation->type() == MIRType::Object);
+
+ JitSpew(JitSpew_RedundantGCBarriers, "Analyzing allocation %s",
+ allocation->opName());
+
+ MBasicBlock* block = allocation->block();
+ MInstructionIterator insIter(block->begin(allocation));
+
+ // Skip `allocation`.
+ MOZ_ASSERT(*insIter == allocation);
+ insIter++;
+
+ // Try to optimize the other instructions in the block.
+ while (insIter != block->end()) {
+ MInstruction* ins = *insIter;
+ insIter++;
+ switch (ins->op()) {
+ case MDefinition::Opcode::Constant:
+ case MDefinition::Opcode::Box:
+ case MDefinition::Opcode::Unbox:
+ case MDefinition::Opcode::AssertCanElidePostWriteBarrier:
+ // These instructions can't trigger GC or affect this analysis in other
+ // ways.
+ break;
+ case MDefinition::Opcode::StoreFixedSlot: {
+ auto* store = ins->toStoreFixedSlot();
+ if (store->object() != allocation) {
+ JitSpew(JitSpew_RedundantGCBarriers,
+ "Stopped at StoreFixedSlot for other object");
+ return true;
+ }
+ store->setNeedsBarrier(false);
+ JitSpew(JitSpew_RedundantGCBarriers, "Elided StoreFixedSlot barrier");
+ break;
+ }
+ case MDefinition::Opcode::PostWriteBarrier: {
+ auto* barrier = ins->toPostWriteBarrier();
+ if (barrier->object() != allocation) {
+ JitSpew(JitSpew_RedundantGCBarriers,
+ "Stopped at PostWriteBarrier for other object");
+ return true;
+ }
+#ifdef DEBUG
+ if (!alloc.ensureBallast()) {
+ return false;
+ }
+ MDefinition* value = barrier->value();
+ if (value->type() != MIRType::Value) {
+ value = MBox::New(alloc, value);
+ block->insertBefore(barrier, value->toInstruction());
+ }
+ auto* assert =
+ MAssertCanElidePostWriteBarrier::New(alloc, allocation, value);
+ block->insertBefore(barrier, assert);
+#endif
+ block->discard(barrier);
+ JitSpew(JitSpew_RedundantGCBarriers, "Elided PostWriteBarrier");
+ break;
+ }
+ default:
+ JitSpew(JitSpew_RedundantGCBarriers,
+ "Stopped at unsupported instruction %s", ins->opName());
+ return true;
+ }
+ }
+
+ return true;
+}
+
+bool jit::EliminateRedundantGCBarriers(MIRGraph& graph) {
+ // Peephole optimization for the following pattern:
+ //
+ // 0: MNewCallObject
+ // 1: MStoreFixedSlot(0, ...)
+ // 2: MStoreFixedSlot(0, ...)
+ // 3: MPostWriteBarrier(0, ...)
+ //
+ // If the instructions immediately following the allocation instruction can't
+ // trigger GC and we are storing to the new object's slots, we can elide the
+ // pre-barrier.
+ //
+ // We also eliminate the post barrier and (in debug builds) replace it with an
+ // assertion.
+ //
+ // See also the similar optimizations in WarpBuilder::buildCallObject.
+
+ JitSpew(JitSpew_RedundantGCBarriers, "Begin");
+
+ for (ReversePostorderIterator block = graph.rpoBegin();
+ block != graph.rpoEnd(); block++) {
+ for (MInstructionIterator insIter(block->begin());
+ insIter != block->end();) {
+ MInstruction* ins = *insIter;
+ insIter++;
+
+ if (ins->isNewCallObject()) {
+ if (!TryEliminateGCBarriersForAllocation(graph.alloc(), ins)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool jit::MarkLoadsUsedAsPropertyKeys(MIRGraph& graph) {
+ // When a string is used as a property key, or as the key for a Map or Set, we
+ // require it to be atomized. To avoid repeatedly atomizing the same string,
+ // this analysis looks for cases where we are loading a value from the slot of
+ // an object (which includes access to global variables and global lexicals)
+ // and using it as a property key, and marks those loads. During codegen,
+ // marked loads will check whether the value loaded is a non-atomized string.
+ // If it is, we will atomize the string and update the stored value, ensuring
+ // that future loads from the same slot will not have to atomize again.
+ JitSpew(JitSpew_MarkLoadsUsedAsPropertyKeys, "Begin");
+
+ for (ReversePostorderIterator block = graph.rpoBegin();
+ block != graph.rpoEnd(); block++) {
+ for (MInstructionIterator insIter(block->begin());
+ insIter != block->end();) {
+ MInstruction* ins = *insIter;
+ insIter++;
+
+ MDefinition* idVal = nullptr;
+ if (ins->isGetPropertyCache()) {
+ idVal = ins->toGetPropertyCache()->idval();
+ } else if (ins->isHasOwnCache()) {
+ idVal = ins->toHasOwnCache()->idval();
+ } else if (ins->isSetPropertyCache()) {
+ idVal = ins->toSetPropertyCache()->idval();
+ } else if (ins->isGetPropSuperCache()) {
+ idVal = ins->toGetPropSuperCache()->idval();
+ } else if (ins->isMegamorphicLoadSlotByValue()) {
+ idVal = ins->toMegamorphicLoadSlotByValue()->idVal();
+ } else if (ins->isMegamorphicHasProp()) {
+ idVal = ins->toMegamorphicHasProp()->idVal();
+ } else if (ins->isMegamorphicSetElement()) {
+ idVal = ins->toMegamorphicSetElement()->index();
+ } else if (ins->isProxyGetByValue()) {
+ idVal = ins->toProxyGetByValue()->idVal();
+ } else if (ins->isProxyHasProp()) {
+ idVal = ins->toProxyHasProp()->idVal();
+ } else if (ins->isProxySetByValue()) {
+ idVal = ins->toProxySetByValue()->idVal();
+ } else if (ins->isIdToStringOrSymbol()) {
+ idVal = ins->toIdToStringOrSymbol()->idVal();
+ } else if (ins->isGuardSpecificAtom()) {
+ idVal = ins->toGuardSpecificAtom()->input();
+ } else if (ins->isToHashableString()) {
+ idVal = ins->toToHashableString()->input();
+ } else if (ins->isToHashableValue()) {
+ idVal = ins->toToHashableValue()->input();
+ } else if (ins->isMapObjectHasValueVMCall()) {
+ idVal = ins->toMapObjectHasValueVMCall()->value();
+ } else if (ins->isMapObjectGetValueVMCall()) {
+ idVal = ins->toMapObjectGetValueVMCall()->value();
+ } else if (ins->isSetObjectHasValueVMCall()) {
+ idVal = ins->toSetObjectHasValueVMCall()->value();
+ } else {
+ continue;
+ }
+ JitSpew(JitSpew_MarkLoadsUsedAsPropertyKeys,
+ "Analyzing property access %s%d with idVal %s%d", ins->opName(),
+ ins->id(), idVal->opName(), idVal->id());
+
+ // Skip intermediate nodes.
+ do {
+ if (idVal->isLexicalCheck()) {
+ idVal = idVal->toLexicalCheck()->input();
+ JitSpew(JitSpew_MarkLoadsUsedAsPropertyKeys,
+ "- Skipping lexical check. idVal is now %s%d",
+ idVal->opName(), idVal->id());
+ continue;
+ }
+ if (idVal->isUnbox() && idVal->type() == MIRType::String) {
+ idVal = idVal->toUnbox()->input();
+ JitSpew(JitSpew_MarkLoadsUsedAsPropertyKeys,
+ "- Skipping unbox. idVal is now %s%d", idVal->opName(),
+ idVal->id());
+ continue;
+ }
+ break;
+ } while (true);
+
+ if (idVal->isLoadFixedSlot()) {
+ JitSpew(JitSpew_MarkLoadsUsedAsPropertyKeys,
+ "- SUCCESS: Marking fixed slot");
+ idVal->toLoadFixedSlot()->setUsedAsPropertyKey();
+ } else if (idVal->isLoadDynamicSlot()) {
+ JitSpew(JitSpew_MarkLoadsUsedAsPropertyKeys,
+ "- SUCCESS: Marking dynamic slot");
+ idVal->toLoadDynamicSlot()->setUsedAsPropertyKey();
+ } else {
+ JitSpew(JitSpew_MarkLoadsUsedAsPropertyKeys, "- SKIP: %s not supported",
+ idVal->opName());
+ }
+ }
+ }
+
+ 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;
+ }
+
+ // Allocating a BigInt can GC, so we have to keep the object alive.
+ if (use->type() == MIRType::BigInt) {
+ 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::Opcode::Nop:
+ case MDefinition::Opcode::Constant:
+ case MDefinition::Opcode::KeepAliveObject:
+ case MDefinition::Opcode::Unbox:
+ case MDefinition::Opcode::LoadDynamicSlot:
+ case MDefinition::Opcode::StoreDynamicSlot:
+ case MDefinition::Opcode::LoadFixedSlot:
+ case MDefinition::Opcode::StoreFixedSlot:
+ case MDefinition::Opcode::LoadElement:
+ case MDefinition::Opcode::LoadElementAndUnbox:
+ case MDefinition::Opcode::LoadElementHole:
+ case MDefinition::Opcode::StoreElement:
+ case MDefinition::Opcode::StoreHoleValueElement:
+ case MDefinition::Opcode::InitializedLength:
+ case MDefinition::Opcode::ArrayLength:
+ case MDefinition::Opcode::BoundsCheck:
+ case MDefinition::Opcode::GuardElementNotHole:
+ case MDefinition::Opcode::InArray:
+ case MDefinition::Opcode::SpectreMaskIndex:
+ case MDefinition::Opcode::DebugEnterGCUnsafeRegion:
+ case MDefinition::Opcode::DebugLeaveGCUnsafeRegion:
+ 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::Opcode::Elements:
+ case MDefinition::Opcode::ArrayBufferViewElements:
+ MOZ_ASSERT(ins->numOperands() == 1);
+ ownerObject = ins->getOperand(0);
+ break;
+ case MDefinition::Opcode::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 (!NeedsKeepAlive(ins, use)) {
+#ifdef DEBUG
+ // These two instructions don't start a GC unsafe region, because they
+ // overwrite their elements register at the very start. This ensures
+ // there's no invalidated elements value kept on the stack.
+ if (use->isApplyArray() || use->isConstructArray()) {
+ continue;
+ }
+
+ if (!graph.alloc().ensureBallast()) {
+ return false;
+ }
+
+ // Enter a GC unsafe region while the elements/slots are on the stack.
+ auto* enter = MDebugEnterGCUnsafeRegion::New(graph.alloc());
+ use->block()->insertAfter(ins, enter);
+
+ // Leave the region after the use.
+ auto* leave = MDebugLeaveGCUnsafeRegion::New(graph.alloc());
+ use->block()->insertAfter(use, leave);
+#endif
+ 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,
+ BailoutKind bailoutKind) {
+ 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, MIRType::Int32);
+ def->setBailoutKind(bailoutKind);
+ 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, MIRType::Int32);
+ def->setBailoutKind(bailoutKind);
+ 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, MIRType::Int32);
+ mul->setBailoutKind(bailoutKind);
+ block->insertAtEnd(mul);
+ mul->computeRange(alloc);
+ if (def) {
+ def = MAdd::New(alloc, def, mul, MIRType::Int32);
+ def->setBailoutKind(bailoutKind);
+ block->insertAtEnd(def->toInstruction());
+ def->computeRange(alloc);
+ } else {
+ def = mul;
+ }
+ }
+ }
+
+ if (!def) {
+ def = MConstant::New(alloc, Int32Value(0));
+ block->insertAtEnd(def->toInstruction());
+ def->computeRange(alloc);
+ }
+
+ return def;
+}
+
+// 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 (innerBackedge->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
+}
+
+bool jit::FoldLoadsWithUnbox(MIRGenerator* mir, MIRGraph& graph) {
+ // This pass folds MLoadFixedSlot, MLoadDynamicSlot, MLoadElement instructions
+ // followed by MUnbox into a single instruction. For LoadElement this allows
+ // us to fuse the hole check with the type check for the unbox.
+
+ for (MBasicBlockIterator block(graph.begin()); block != graph.end();
+ block++) {
+ if (mir->shouldCancel("FoldLoadsWithUnbox")) {
+ return false;
+ }
+
+ for (MInstructionIterator insIter(block->begin());
+ insIter != block->end();) {
+ MInstruction* ins = *insIter;
+ insIter++;
+
+ // We're only interested in loads producing a Value.
+ if (!ins->isLoadFixedSlot() && !ins->isLoadDynamicSlot() &&
+ !ins->isLoadElement()) {
+ continue;
+ }
+ if (ins->type() != MIRType::Value) {
+ continue;
+ }
+
+ MInstruction* load = ins;
+
+ // Ensure there's a single def-use (ignoring resume points) and it's an
+ // unbox. Unwrap MLexicalCheck because it's redundant if we have a
+ // fallible unbox (checked below).
+ MDefinition* defUse = load->maybeSingleDefUse();
+ if (!defUse) {
+ continue;
+ }
+ MLexicalCheck* lexicalCheck = nullptr;
+ if (defUse->isLexicalCheck()) {
+ lexicalCheck = defUse->toLexicalCheck();
+ defUse = lexicalCheck->maybeSingleDefUse();
+ if (!defUse) {
+ continue;
+ }
+ }
+ if (!defUse->isUnbox()) {
+ continue;
+ }
+
+ // For now require the load and unbox to be in the same block. This isn't
+ // strictly necessary but it's the common case and could prevent bailouts
+ // when moving the unbox before a loop.
+ MUnbox* unbox = defUse->toUnbox();
+ if (unbox->block() != *block) {
+ continue;
+ }
+ MOZ_ASSERT_IF(lexicalCheck, lexicalCheck->block() == *block);
+
+ MOZ_ASSERT(!IsMagicType(unbox->type()));
+
+ // If this is a LoadElement or if we have a lexical check between the load
+ // and unbox, we only support folding the load with a fallible unbox so
+ // that we can eliminate the MagicValue check.
+ if ((load->isLoadElement() || lexicalCheck) && !unbox->fallible()) {
+ continue;
+ }
+
+ // Combine the load and unbox into a single MIR instruction.
+ if (!graph.alloc().ensureBallast()) {
+ return false;
+ }
+
+ MIRType type = unbox->type();
+ MUnbox::Mode mode = unbox->mode();
+
+ MInstruction* replacement;
+ switch (load->op()) {
+ case MDefinition::Opcode::LoadFixedSlot: {
+ auto* loadIns = load->toLoadFixedSlot();
+ replacement = MLoadFixedSlotAndUnbox::New(
+ graph.alloc(), loadIns->object(), loadIns->slot(), mode, type,
+ loadIns->usedAsPropertyKey());
+ break;
+ }
+ case MDefinition::Opcode::LoadDynamicSlot: {
+ auto* loadIns = load->toLoadDynamicSlot();
+ replacement = MLoadDynamicSlotAndUnbox::New(
+ graph.alloc(), loadIns->slots(), loadIns->slot(), mode, type,
+ loadIns->usedAsPropertyKey());
+ break;
+ }
+ case MDefinition::Opcode::LoadElement: {
+ auto* loadIns = load->toLoadElement();
+ MOZ_ASSERT(unbox->fallible());
+ replacement = MLoadElementAndUnbox::New(
+ graph.alloc(), loadIns->elements(), loadIns->index(), mode, type);
+ break;
+ }
+ default:
+ MOZ_CRASH("Unexpected instruction");
+ }
+ replacement->setBailoutKind(BailoutKind::UnboxFolding);
+
+ block->insertBefore(load, replacement);
+ unbox->replaceAllUsesWith(replacement);
+ if (lexicalCheck) {
+ lexicalCheck->replaceAllUsesWith(replacement);
+ }
+ load->replaceAllUsesWith(replacement);
+
+ if (lexicalCheck && *insIter == lexicalCheck) {
+ insIter++;
+ }
+ if (*insIter == unbox) {
+ insIter++;
+ }
+ block->discard(unbox);
+ if (lexicalCheck) {
+ block->discard(lexicalCheck);
+ }
+ block->discard(load);
+ }
+ }
+
+ return true;
+}
+
+// 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;
+}
+
+static MDefinition* SkipUnbox(MDefinition* ins) {
+ if (ins->isUnbox()) {
+ return ins->toUnbox()->input();
+ }
+ return ins;
+}
+
+bool jit::OptimizeIteratorIndices(MIRGenerator* mir, MIRGraph& graph) {
+ bool changed = false;
+
+ for (ReversePostorderIterator blockIter = graph.rpoBegin();
+ blockIter != graph.rpoEnd();) {
+ MBasicBlock* block = *blockIter++;
+ for (MInstructionIterator insIter(block->begin());
+ insIter != block->end();) {
+ MInstruction* ins = *insIter;
+ insIter++;
+ if (!graph.alloc().ensureBallast()) {
+ return false;
+ }
+
+ MDefinition* receiver = nullptr;
+ MDefinition* idVal = nullptr;
+ MDefinition* setValue = nullptr;
+ if (ins->isMegamorphicHasProp() &&
+ ins->toMegamorphicHasProp()->hasOwn()) {
+ receiver = ins->toMegamorphicHasProp()->object();
+ idVal = ins->toMegamorphicHasProp()->idVal();
+ } else if (ins->isHasOwnCache()) {
+ receiver = ins->toHasOwnCache()->value();
+ idVal = ins->toHasOwnCache()->idval();
+ } else if (ins->isMegamorphicLoadSlotByValue()) {
+ receiver = ins->toMegamorphicLoadSlotByValue()->object();
+ idVal = ins->toMegamorphicLoadSlotByValue()->idVal();
+ } else if (ins->isGetPropertyCache()) {
+ receiver = ins->toGetPropertyCache()->value();
+ idVal = ins->toGetPropertyCache()->idval();
+ } else if (ins->isMegamorphicSetElement()) {
+ receiver = ins->toMegamorphicSetElement()->object();
+ idVal = ins->toMegamorphicSetElement()->index();
+ setValue = ins->toMegamorphicSetElement()->value();
+ } else if (ins->isSetPropertyCache()) {
+ receiver = ins->toSetPropertyCache()->object();
+ idVal = ins->toSetPropertyCache()->idval();
+ setValue = ins->toSetPropertyCache()->value();
+ }
+
+ if (!receiver) {
+ continue;
+ }
+
+ // Given the following structure (that occurs inside for-in loops):
+ // obj: some object
+ // iter: ObjectToIterator <obj>
+ // iterNext: IteratorMore <iter>
+ // access: HasProp/GetElem <obj> <iterNext>
+ // If the iterator object has an indices array, we can speed up the
+ // property access:
+ // 1. If the property access is a HasProp looking for own properties,
+ // then the result will always be true if the iterator has indices,
+ // because we only populate the indices array for objects with no
+ // enumerable properties on the prototype.
+ // 2. If the property access is a GetProp, then we can use the contents
+ // of the indices array to find the correct property faster than
+ // the megamorphic cache.
+ if (!idVal->isIteratorMore()) {
+ continue;
+ }
+ auto* iterNext = idVal->toIteratorMore();
+
+ if (!iterNext->iterator()->isObjectToIterator()) {
+ continue;
+ }
+
+ MObjectToIterator* iter = iterNext->iterator()->toObjectToIterator();
+ if (SkipUnbox(iter->object()) != SkipUnbox(receiver)) {
+ continue;
+ }
+
+ MInstruction* indicesCheck =
+ MIteratorHasIndices::New(graph.alloc(), iter->object(), iter);
+ MInstruction* replacement;
+ if (ins->isHasOwnCache() || ins->isMegamorphicHasProp()) {
+ MOZ_ASSERT(!setValue);
+ replacement = MConstant::New(graph.alloc(), BooleanValue(true));
+ } else if (ins->isMegamorphicLoadSlotByValue() ||
+ ins->isGetPropertyCache()) {
+ MOZ_ASSERT(!setValue);
+ replacement =
+ MLoadSlotByIteratorIndex::New(graph.alloc(), receiver, iter);
+ } else {
+ MOZ_ASSERT(ins->isMegamorphicSetElement() || ins->isSetPropertyCache());
+ MOZ_ASSERT(setValue);
+ replacement = MStoreSlotByIteratorIndex::New(graph.alloc(), receiver,
+ iter, setValue);
+ }
+
+ if (!block->wrapInstructionInFastpath(ins, replacement, indicesCheck)) {
+ return false;
+ }
+
+ iter->setWantsIndices(true);
+ changed = true;
+
+ // Advance to join block.
+ blockIter = graph.rpoBegin(block->getSuccessor(0)->getSuccessor(0));
+ break;
+ }
+ }
+ if (changed && !AccountForCFGChanges(mir, graph,
+ /*updateAliasAnalysis=*/false)) {
+ return false;
+ }
+
+ return true;
+}
+
+void jit::DumpMIRDefinition(GenericPrinter& out, MDefinition* def) {
+#ifdef JS_JITSPEW
+ out.printf("%u = %s.", def->id(), StringFromMIRType(def->type()));
+ if (def->isConstant()) {
+ def->printOpcode(out);
+ } else {
+ MDefinition::PrintOpcodeName(out, def->op());
+ }
+
+ // Get any extra bits of text that the MIR node wants to show us. Both the
+ // vector and the strings added to it belong to this function, so both will
+ // be automatically freed at exit.
+ ExtrasCollector extras;
+ def->getExtras(&extras);
+ for (size_t i = 0; i < extras.count(); i++) {
+ out.printf(" %s", extras.get(i).get());
+ }
+
+ for (size_t i = 0; i < def->numOperands(); i++) {
+ out.printf(" %u", def->getOperand(i)->id());
+ }
+#endif
+}
+
+void jit::DumpMIRExpressions(GenericPrinter& out, MIRGraph& graph,
+ const CompileInfo& info, const char* phase) {
+#ifdef JS_JITSPEW
+ if (!JitSpewEnabled(JitSpew_MIRExpressions)) {
+ return;
+ }
+
+ out.printf("===== %s =====\n", phase);
+
+ for (ReversePostorderIterator block(graph.rpoBegin());
+ block != graph.rpoEnd(); block++) {
+ out.printf(" Block%u:\n", block->id());
+ for (MPhiIterator iter(block->phisBegin()), end(block->phisEnd());
+ iter != end; iter++) {
+ out.printf(" ");
+ jit::DumpMIRDefinition(out, *iter);
+ out.printf("\n");
+ }
+ for (MInstructionIterator iter(block->begin()), end(block->end());
+ iter != end; iter++) {
+ out.printf(" ");
+ DumpMIRDefinition(out, *iter);
+ out.printf("\n");
+ }
+ }
+
+ if (info.compilingWasm()) {
+ out.printf("===== end wasm MIR dump =====\n");
+ } else {
+ out.printf("===== %s:%u =====\n", info.filename(), info.lineno());
+ }
+#endif
+}