Bug 1093674 - IonMonkey: Add Sink for instruction which can be recovered on bailout. r=sunfish
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Mon, 24 Nov 2014 16:11:32 +0100
changeset 217242 9188c8b7962b
parent 217241 2471a54e5a5a
child 217243 111b2dd131cf
push id27876
push userkwierso@gmail.com
push date2014-11-25 00:56 +0000
treeherdermozilla-central@74edfbf0e6a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssunfish
bugs1093674
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1093674 - IonMonkey: Add Sink for instruction which can be recovered on bailout. r=sunfish
js/src/jit-test/tests/ion/sink-in-recovered-object.js
js/src/jit/Ion.cpp
js/src/jit/IonAnalysis.cpp
js/src/jit/IonOptimizationLevels.cpp
js/src/jit/IonOptimizationLevels.h
js/src/jit/JitOptions.cpp
js/src/jit/JitOptions.h
js/src/jit/JitSpewer.cpp
js/src/jit/JitSpewer.h
js/src/jit/MIRGraph.cpp
js/src/jit/MIRGraph.h
js/src/jit/RangeAnalysis.cpp
js/src/jit/Sink.cpp
js/src/jit/Sink.h
js/src/moz.build
js/src/shell/js.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/sink-in-recovered-object.js
@@ -0,0 +1,19 @@
+setJitCompilerOption("ion.warmup.trigger", 30);
+
+var arr = [];
+function f (cond, a) {
+  var obj = { a: 0 };
+  var x = 2 * a + 1;
+  if (cond) {
+    obj.a = x;
+    arr.push(obj.a);
+    obj.a = 1;
+  } else {
+    obj.a = 1;
+  }
+  return obj.a;
+}
+
+for (var i = 0; i < 100; i++) {
+  assertEq(f(i % 2, i), 1);
+}
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -33,16 +33,17 @@
 #include "jit/LinearScan.h"
 #include "jit/LIR.h"
 #include "jit/LoopUnroller.h"
 #include "jit/Lowering.h"
 #include "jit/ParallelSafetyAnalysis.h"
 #include "jit/PerfSpewer.h"
 #include "jit/RangeAnalysis.h"
 #include "jit/ScalarReplacement.h"
+#include "jit/Sink.h"
 #include "jit/StupidAllocator.h"
 #include "jit/ValueNumbering.h"
 #include "vm/ForkJoin.h"
 #include "vm/HelperThreads.h"
 #include "vm/TraceLogging.h"
 
 #include "jscompartmentinlines.h"
 #include "jsgcinlines.h"
@@ -1557,16 +1558,27 @@ OptimizeMIR(MIRGenerator *mir)
             return false;
         IonSpewPass("DCE");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("DCE"))
             return false;
     }
 
+    if (mir->optimizationInfo().sinkEnabled()) {
+        AutoTraceLog log(logger, TraceLogger::EliminateDeadCode);
+        if (!Sink(mir, graph))
+            return false;
+        IonSpewPass("Sink");
+        AssertExtendedGraphCoherency(graph);
+
+        if (mir->shouldCancel("Sink"))
+            return false;
+    }
+
     // Make loops contiguous. We do this after GVN/UCE and range analysis,
     // which can remove CFG edges, exposing more blocks that can be moved.
     {
         AutoTraceLog log(logger, TraceLogger::MakeLoopsContiguous);
         if (!MakeLoopsContiguous(graph))
             return false;
         IonSpewPass("Make loops contiguous");
         AssertExtendedGraphCoherency(graph);
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -564,20 +564,16 @@ jit::EliminateDeadCode(MIRGenerator *mir
             return false;
 
         // Remove unused instructions.
         for (MInstructionReverseIterator iter = block->rbegin(); iter != block->rend(); ) {
             MInstruction *inst = *iter++;
             if (js::jit::IsDiscardable(inst))
             {
                 block->discard(inst);
-            } else if (!inst->isRecoveredOnBailout() && !inst->isGuard() &&
-                       !inst->hasLiveDefUses() && inst->canRecoverOnBailout())
-            {
-                inst->setRecoveredOnBailout();
             }
         }
     }
 
     return true;
 }
 
 static inline bool
--- a/js/src/jit/IonOptimizationLevels.cpp
+++ b/js/src/jit/IonOptimizationLevels.cpp
@@ -28,16 +28,17 @@ OptimizationInfo::initNormalOptimization
     eliminateRedundantChecks_ = true;
     inlineInterpreted_ = true;
     inlineNative_ = true;
     gvn_ = true;
     licm_ = true;
     rangeAnalysis_ = true;
     loopUnrolling_ = true;
     autoTruncate_ = true;
+    sink_ = true;
     registerAllocator_ = RegisterAllocator_LSRA;
 
     inlineMaxTotalBytecodeLength_ = 1000;
     inliningMaxCallerBytecodeLength_ = 10000;
     maxInlineDepth_ = 3;
     scalarReplacement_ = true;
     smallFunctionMaxInlineDepth_ = 10;
     compilerWarmUpThreshold_ = 1000;
@@ -53,16 +54,17 @@ OptimizationInfo::initAsmjsOptimizationI
 
     // Take normal option values for not specified values.
     initNormalOptimizationInfo();
 
     level_ = Optimization_AsmJS;
     edgeCaseAnalysis_ = false;
     eliminateRedundantChecks_ = false;
     autoTruncate_ = false;
+    sink_ = false;
     registerAllocator_ = RegisterAllocator_Backtracking;
     scalarReplacement_ = false;        // AsmJS has no objects.
 }
 
 uint32_t
 OptimizationInfo::compilerWarmUpThreshold(JSScript *script, jsbytecode *pc) const
 {
     MOZ_ASSERT(pc == nullptr || pc == script->code() || JSOp(*pc) == JSOP_LOOPENTRY);
--- a/js/src/jit/IonOptimizationLevels.h
+++ b/js/src/jit/IonOptimizationLevels.h
@@ -71,16 +71,19 @@ class OptimizationInfo
     bool rangeAnalysis_;
 
     // Toggles whether loop unrolling is performed.
     bool loopUnrolling_;
 
     // Toggles whether Truncation based on Range Analysis is used.
     bool autoTruncate_;
 
+    // Toggles whether sink is used.
+    bool sink_;
+
     // Describes which register allocator to use.
     IonRegisterAllocator registerAllocator_;
 
     // The maximum total bytecode size of an inline call site.
     uint32_t inlineMaxTotalBytecodeLength_;
 
     // The maximum bytecode length the caller may have,
     // before we stop inlining large functions in that caller.
@@ -148,16 +151,20 @@ class OptimizationInfo
     bool loopUnrollingEnabled() const {
         return loopUnrolling_ && !js_JitOptions.disableLoopUnrolling;
     }
 
     bool autoTruncateEnabled() const {
         return autoTruncate_ && rangeAnalysisEnabled();
     }
 
+    bool sinkEnabled() const {
+        return sink_ && !js_JitOptions.disableSink;
+    }
+
     bool eaaEnabled() const {
         return eaa_ && !js_JitOptions.disableEaa;
     }
 
     bool edgeCaseAnalysisEnabled() const {
         return edgeCaseAnalysis_ && !js_JitOptions.disableEdgeCaseAnalysis;
     }
 
--- a/js/src/jit/JitOptions.cpp
+++ b/js/src/jit/JitOptions.cpp
@@ -76,16 +76,19 @@ JitOptions::JitOptions()
     SET_DEFAULT(disableInlining, false);
 
     // Toggles whether Edge Case Analysis is gobally disabled.
     SET_DEFAULT(disableEdgeCaseAnalysis, false);
 
     // Toggles whether Range Analysis is globally disabled.
     SET_DEFAULT(disableRangeAnalysis, false);
 
+    // Toggles whether sink code motion is globally disabled.
+    SET_DEFAULT(disableSink, false);
+
     // Toggles whether Loop Unrolling is globally disabled.
     SET_DEFAULT(disableLoopUnrolling, true);
 
     // Toggles whether Effective Address Analysis is globally disabled.
     SET_DEFAULT(disableEaa, false);
 
     // Whether functions are compiled immediately.
     SET_DEFAULT(eagerCompilation, false);
--- a/js/src/jit/JitOptions.h
+++ b/js/src/jit/JitOptions.h
@@ -33,16 +33,17 @@ struct JitOptions
 #endif
     bool checkRangeAnalysis;
     bool disableScalarReplacement;
     bool disableGvn;
     bool disableLicm;
     bool disableInlining;
     bool disableEdgeCaseAnalysis;
     bool disableRangeAnalysis;
+    bool disableSink;
     bool disableLoopUnrolling;
     bool disableEaa;
     bool eagerCompilation;
     bool forceDefaultIonWarmUpThreshold;
     uint32_t forcedDefaultIonWarmUpThreshold;
     bool forceRegisterAllocator;
     IonRegisterAllocator forcedRegisterAllocator;
     bool limitScriptSize;
--- a/js/src/jit/JitSpewer.cpp
+++ b/js/src/jit/JitSpewer.cpp
@@ -235,16 +235,17 @@ jit::CheckLogging()
             "\n"
             "  aborts     Compilation abort messages\n"
             "  scripts    Compiled scripts\n"
             "  mir        MIR information\n"
             "  escape     Escape analysis\n"
             "  alias      Alias analysis\n"
             "  gvn        Global Value Numbering\n"
             "  licm       Loop invariant code motion\n"
+            "  sink       Sink transformation\n"
             "  regalloc   Register allocation\n"
             "  inline     Inlining\n"
             "  snapshots  Snapshot information\n"
             "  codegen    Native code generation\n"
             "  bailouts   Bailouts\n"
             "  caches     Inline caches\n"
             "  osi        Invalidation\n"
             "  safepoints Safepoints\n"
@@ -283,16 +284,18 @@ jit::CheckLogging()
     if (ContainsFlag(env, "gvn"))
         EnableChannel(JitSpew_GVN);
     if (ContainsFlag(env, "range"))
         EnableChannel(JitSpew_Range);
     if (ContainsFlag(env, "unroll"))
         EnableChannel(JitSpew_Unrolling);
     if (ContainsFlag(env, "licm"))
         EnableChannel(JitSpew_LICM);
+    if (ContainsFlag(env, "sink"))
+        EnableChannel(JitSpew_Sink);
     if (ContainsFlag(env, "regalloc"))
         EnableChannel(JitSpew_RegAlloc);
     if (ContainsFlag(env, "inline"))
         EnableChannel(JitSpew_Inlining);
     if (ContainsFlag(env, "snapshots"))
         EnableChannel(JitSpew_IonSnapshots);
     if (ContainsFlag(env, "codegen"))
         EnableChannel(JitSpew_Codegen);
--- a/js/src/jit/JitSpewer.h
+++ b/js/src/jit/JitSpewer.h
@@ -21,16 +21,18 @@ namespace jit {
 // New channels may be added below.
 #define JITSPEW_CHANNEL_LIST(_)             \
     /* Information during escape analysis */\
     _(Escape)                               \
     /* Information during alias analysis */ \
     _(Alias)                                \
     /* Information during GVN */            \
     _(GVN)                                  \
+    /* Information during sinking */        \
+    _(Sink)                                 \
     /* Information during Range analysis */ \
     _(Range)                                \
     /* Information during loop unrolling */ \
     _(Unrolling)                            \
     /* Information during LICM */           \
     _(LICM)                                 \
     /* Information during regalloc */       \
     _(RegAlloc)                             \
--- a/js/src/jit/MIRGraph.cpp
+++ b/js/src/jit/MIRGraph.cpp
@@ -790,16 +790,37 @@ MBasicBlock::moveBefore(MInstruction *at
 
     // Insert into new block, which may be distinct.
     // Uses and operands are untouched.
     ins->setBlock(at->block());
     at->block()->instructions_.insertBefore(at, ins);
     ins->setTrackedSite(at->trackedSite());
 }
 
+MInstruction *
+MBasicBlock::safeInsertTop(MDefinition *ins, IgnoreTop ignore)
+{
+    // Beta nodes and interrupt checks are required to be located at the
+    // beginnings of basic blocks, so we must insert new instructions after any
+    // such instructions.
+    MInstructionIterator insertIter = !ins || ins->isPhi()
+                                    ? begin()
+                                    : begin(ins->toInstruction());
+    while (insertIter->isBeta() ||
+           insertIter->isInterruptCheck() ||
+           insertIter->isInterruptCheckPar() ||
+           insertIter->isConstant() ||
+           (!(ignore & IgnoreRecover) && insertIter->isRecoveredOnBailout()))
+    {
+        insertIter++;
+    }
+
+    return *insertIter;
+}
+
 void
 MBasicBlock::discardResumePoint(MResumePoint *rp, ReferencesType refType /* = RefType_Default */)
 {
     if (refType & RefType_DiscardOperands)
         rp->releaseUses();
 #ifdef DEBUG
     MResumePointIterator iter = resumePointsBegin();
     while (*iter != rp) {
--- a/js/src/jit/MIRGraph.h
+++ b/js/src/jit/MIRGraph.h
@@ -281,16 +281,25 @@ class MBasicBlock : public TempObject, p
     void insertAtEnd(MInstruction *ins);
 
     // Add an instruction to this block, from elsewhere in the graph.
     void addFromElsewhere(MInstruction *ins);
 
     // Move an instruction. Movement may cross block boundaries.
     void moveBefore(MInstruction *at, MInstruction *ins);
 
+    enum IgnoreTop {
+        IgnoreNone = 0,
+        IgnoreRecover = 1 << 0
+    };
+
+    // Locate the top of the |block|, where it is safe to insert a new
+    // instruction.
+    MInstruction *safeInsertTop(MDefinition *ins = nullptr, IgnoreTop ignore = IgnoreNone);
+
     // Removes an instruction with the intention to discard it.
     void discard(MInstruction *ins);
     void discardLastIns();
     void discardDef(MDefinition *def);
     void discardAllInstructions();
     void discardAllInstructionsStartingAt(MInstructionIterator iter);
     void discardAllPhiOperands();
     void discardAllPhis();
--- a/js/src/jit/RangeAnalysis.cpp
+++ b/js/src/jit/RangeAnalysis.cpp
@@ -2258,32 +2258,22 @@ RangeAnalysis::addRangeAssertions()
             if (r.isUnknown() || (ins->type() == MIRType_Int32 && r.isUnknownInt32()))
                 continue;
 
             MAssertRange *guard = MAssertRange::New(alloc(), ins, new(alloc()) Range(r));
 
             // Beta nodes and interrupt checks are required to be located at the
             // beginnings of basic blocks, so we must insert range assertions
             // after any such instructions.
-            MInstructionIterator insertIter = ins->isPhi()
-                                            ? block->begin()
-                                            : block->begin(ins->toInstruction());
-            while (insertIter->isBeta() ||
-                   insertIter->isInterruptCheck() ||
-                   insertIter->isInterruptCheckPar() ||
-                   insertIter->isConstant() ||
-                   insertIter->isRecoveredOnBailout())
-            {
-                insertIter++;
-            }
-
-            if (*insertIter == *iter)
-                block->insertAfter(*insertIter,  guard);
+            MInstruction *insertAt = block->safeInsertTop(ins);
+
+            if (insertAt == *iter)
+                block->insertAfter(insertAt,  guard);
             else
-                block->insertBefore(*insertIter, guard);
+                block->insertBefore(insertAt, guard);
         }
     }
 
     return true;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Range based Truncation
new file mode 100644
--- /dev/null
+++ b/js/src/jit/Sink.cpp
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jit/Sink.h"
+
+#include "mozilla/Vector.h"
+
+#include "jit/IonAnalysis.h"
+#include "jit/JitSpewer.h"
+#include "jit/MIR.h"
+#include "jit/MIRGenerator.h"
+#include "jit/MIRGraph.h"
+
+namespace js {
+namespace jit {
+
+// Given the last found common dominator and a new definition to dominate, the
+// CommonDominator function returns the basic block which dominate the last
+// common dominator and the definition. If no such block exists, then this
+// functions return null.
+static MBasicBlock *
+CommonDominator(MBasicBlock *commonDominator, MBasicBlock *defBlock)
+{
+    // This is the first instruction visited, record its basic block as being
+    // the only interesting one.
+    if (!commonDominator)
+        return defBlock;
+
+    // Iterate on immediate dominators of the known common dominator to find a
+    // block which dominates all previous uses as well as this instruction.
+    while (!commonDominator->dominates(defBlock)) {
+        MBasicBlock *nextBlock = commonDominator->immediateDominator();
+        // All uses are dominated, so, this cannot happen unless the graph
+        // coherency is not respected.
+        MOZ_ASSERT(commonDominator != nextBlock);
+        commonDominator = nextBlock;
+    }
+
+    return commonDominator;
+}
+
+bool
+Sink(MIRGenerator *mir, MIRGraph &graph)
+{
+    TempAllocator &alloc = graph.alloc();
+
+    for (PostorderIterator block = graph.poBegin(); block != graph.poEnd(); block++) {
+        if (mir->shouldCancel("Sink"))
+            return false;
+
+        for (MInstructionReverseIterator iter = block->rbegin(); iter != block->rend(); ) {
+            MInstruction *ins = *iter++;
+
+            // Only instructions which can be recovered on bailout can be moved
+            // into the bailout paths.
+            if (ins->isGuard() || ins->isRecoveredOnBailout() || !ins->canRecoverOnBailout())
+                continue;
+
+            // Compute a common dominator for all uses of the current
+            // instruction.
+            bool hasLiveUses = false;
+            bool hasUses = false;
+            MBasicBlock *usesDominator = nullptr;
+            for (MUseIterator i(ins->usesBegin()), e(ins->usesEnd()); i != e; i++) {
+                hasUses = true;
+                MNode *consumerNode = (*i)->consumer();
+                if (consumerNode->isResumePoint())
+                    continue;
+
+                MDefinition *consumer = consumerNode->toDefinition();
+                if (consumer->isRecoveredOnBailout())
+                    continue;
+
+                hasLiveUses = true;
+
+                // If the instruction is a Phi, then we should dominate the
+                // predecessor from which the value is coming from.
+                MBasicBlock *consumerBlock = consumer->block();
+                if (consumer->isPhi())
+                    consumerBlock = consumerBlock->getPredecessor(consumer->indexOf(*i));
+
+                usesDominator = CommonDominator(usesDominator, consumerBlock);
+                if (usesDominator == *block)
+                    break;
+            }
+
+            // Leave this instruction for DCE.
+            if (!hasUses)
+                continue;
+
+            // We have no uses, so sink this instruction in all the bailout
+            // paths.
+            if (!hasLiveUses) {
+                MOZ_ASSERT(!usesDominator);
+                ins->setRecoveredOnBailout();
+                JitSpewDef(JitSpew_Sink, "  No live uses, recover the instruction on bailout\n", ins);
+                continue;
+            }
+
+            // If all the uses are under a loop, we might not want to work
+            // against LICM by moving everything back into the loop, but if the
+            // loop is it-self inside an if, then we still want to move the
+            // computation under this if statement.
+            while (block->loopDepth() < usesDominator->loopDepth()) {
+                MOZ_ASSERT(usesDominator != usesDominator->immediateDominator());
+                usesDominator = usesDominator->immediateDominator();
+            }
+
+            // Only move instructions if there is a branch between the dominator
+            // of the uses and the original instruction. This prevent moving the
+            // computation of the arguments into an inline function if there is
+            // no major win.
+            MBasicBlock *lastJoin = usesDominator;
+            while (*block != lastJoin && lastJoin->numPredecessors() == 1) {
+                MOZ_ASSERT(lastJoin != lastJoin->immediateDominator());
+                MBasicBlock *next = lastJoin->immediateDominator();
+                if (next->numSuccessors() > 1)
+                    break;
+                lastJoin = next;
+            }
+            if (*block == lastJoin)
+                continue;
+
+            // Skip to the next instruction if we cannot find a common dominator
+            // for all the uses of this instruction, or if the common dominator
+            // correspond to the block of the current instruction.
+            if (!usesDominator || usesDominator == *block)
+                continue;
+
+            // Only instruction which can be recovered on bailout and which are
+            // sinkable can be moved into blocks which are below while filling
+            // the resume points with a clone which is recovered on bailout.
+
+            // If the instruction has live uses and if it is clonable, then we
+            // can clone the instruction for all non-dominated uses and move the
+            // instruction into the block which is dominating all live uses.
+            if (!ins->canClone())
+                continue;
+
+            JitSpewDef(JitSpew_Sink, "  Can Clone & Recover, sink instruction\n", ins);
+            JitSpew(JitSpew_Sink, "  into Block %u", usesDominator->id());
+
+            // Copy the arguments and clone the instruction.
+            MDefinitionVector operands(alloc);
+            for (size_t i = 0, end = ins->numOperands(); i < end; i++) {
+                if (!operands.append(ins->getOperand(i)))
+                    return false;
+            }
+
+            MInstruction *clone = ins->clone(alloc, operands);
+            ins->block()->insertBefore(ins, clone);
+            clone->setRecoveredOnBailout();
+
+            // We should not update the producer of the entry resume point, as
+            // it cannot refer to any instruction within the basic block excepts
+            // for Phi nodes.
+            MResumePoint *entry = usesDominator->entryResumePoint();
+
+            // Replace the instruction by its clone in all the resume points /
+            // recovered-on-bailout instructions which are not in blocks which
+            // are dominated by the usesDominator block.
+            for (MUseIterator i(ins->usesBegin()), e(ins->usesEnd()); i != e; ) {
+                MUse *use = *i++;
+                MNode *consumer = use->consumer();
+
+                // If the consumer is a Phi, then we look for the index of the
+                // use to find the corresponding predecessor block, which is
+                // then used as the consumer block.
+                MBasicBlock *consumerBlock = consumer->block();
+                if (consumer->isDefinition() && consumer->toDefinition()->isPhi()) {
+                    consumerBlock = consumerBlock->getPredecessor(
+                        consumer->toDefinition()->toPhi()->indexOf(use));
+                }
+
+                // Keep the current instruction for all dominated uses, except
+                // for the entry resume point of the block in which the
+                // instruction would be moved into.
+                if (usesDominator->dominates(consumerBlock) &&
+                    (!consumer->isResumePoint() || consumer->toResumePoint() != entry))
+                {
+                    continue;
+                }
+
+                use->replaceProducer(clone);
+            }
+
+            // Now, that all uses which are not dominated by usesDominator are
+            // using the cloned instruction, we can safely move the instruction
+            // into the usesDominator block.
+            MInstruction *at = usesDominator->safeInsertTop(nullptr, MBasicBlock::IgnoreRecover);
+            block->moveBefore(at, ins);
+        }
+    }
+
+    return true;
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit/Sink.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file declares sink transformation.
+#ifndef jit_Sink_h
+#define jit_Sink_h
+
+namespace js {
+namespace jit {
+
+class MIRGenerator;
+class MIRGraph;
+
+bool
+Sink(MIRGenerator *mir, MIRGraph &graph);
+
+} // namespace jit
+} // namespace js
+
+#endif /* jit_Sink_h */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -185,16 +185,17 @@ UNIFIED_SOURCES += [
     'jit/Recover.cpp',
     'jit/RegisterAllocator.cpp',
     'jit/RematerializedFrame.cpp',
     'jit/Safepoints.cpp',
     'jit/ScalarReplacement.cpp',
     'jit/shared/BaselineCompiler-shared.cpp',
     'jit/shared/CodeGenerator-shared.cpp',
     'jit/shared/Lowering-shared.cpp',
+    'jit/Sink.cpp',
     'jit/Snapshots.cpp',
     'jit/StupidAllocator.cpp',
     'jit/TypedObjectPrediction.cpp',
     'jit/TypePolicy.cpp',
     'jit/ValueNumbering.cpp',
     'jit/VMFunctions.cpp',
     'jsalloc.cpp',
     'jsapi.cpp',
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5495,16 +5495,25 @@ SetRuntimeOptions(JSRuntime *rt, const O
         if (strcmp(str, "on") == 0)
             jit::js_JitOptions.disableRangeAnalysis = false;
         else if (strcmp(str, "off") == 0)
             jit::js_JitOptions.disableRangeAnalysis = true;
         else
             return OptionFailure("ion-range-analysis", str);
     }
 
+    if (const char *str = op.getStringOption("ion-sink")) {
+        if (strcmp(str, "on") == 0)
+            jit::js_JitOptions.disableSink = false;
+        else if (strcmp(str, "off") == 0)
+            jit::js_JitOptions.disableSink = true;
+        else
+            return OptionFailure("ion-sink", str);
+    }
+
     if (const char *str = op.getStringOption("ion-loop-unrolling")) {
         if (strcmp(str, "on") == 0)
             jit::js_JitOptions.disableLoopUnrolling = false;
         else if (strcmp(str, "off") == 0)
             jit::js_JitOptions.disableLoopUnrolling = true;
         else
             return OptionFailure("ion-loop-unrolling", str);
     }
@@ -5792,16 +5801,18 @@ main(int argc, char **argv, char **envp)
                                "  off: disable GVN\n"
                                "  on:  enable GVN (default)\n")
         || !op.addStringOption('\0', "ion-licm", "on/off",
                                "Loop invariant code motion (default: on, off to disable)")
         || !op.addStringOption('\0', "ion-edgecase-analysis", "on/off",
                                "Find edge cases where Ion can avoid bailouts (default: on, off to disable)")
         || !op.addStringOption('\0', "ion-range-analysis", "on/off",
                                "Range analysis (default: on, off to disable)")
+        || !op.addStringOption('\0', "ion-sink", "on/off",
+                               "Sink code motion (default: on, off to disable)")
         || !op.addStringOption('\0', "ion-loop-unrolling", "on/off",
                                "Loop unrolling (default: off, on to enable)")
         || !op.addBoolOption('\0', "ion-check-range-analysis",
                                "Range analysis checking")
         || !op.addStringOption('\0', "ion-inlining", "on/off",
                                "Inline methods where possible (default: on, off to disable)")
         || !op.addStringOption('\0', "ion-osr", "on/off",
                                "On-Stack Replacement (default: on, off to disable)")