Bug 992845 - Add Scalar replacement of objects with simplest escape analysis. r=jandem
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Thu, 17 Jul 2014 03:04:30 -0700
changeset 215468 b2a822934b97ba6f5a90b3f172929f2039728c98
parent 215467 d616c707051844a0740a2d1d49f65324be5bd6ad
child 215469 328ca3cea665b57f3f044fb37377991d3c1a37ae
push id3857
push userraliiev@mozilla.com
push dateTue, 02 Sep 2014 16:39:23 +0000
treeherdermozilla-beta@5638b907b505 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs992845
milestone33.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 992845 - Add Scalar replacement of objects with simplest escape analysis. r=jandem
js/src/jit-test/tests/ion/recover-objects.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/MIR.cpp
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/ParallelSafetyAnalysis.cpp
js/src/jit/Recover.cpp
js/src/jit/Recover.h
js/src/jit/ScalarReplacement.cpp
js/src/jit/ScalarReplacement.h
js/src/moz.build
js/src/shell/js.cpp
js/src/vm/TraceLogging.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/recover-objects.js
@@ -0,0 +1,106 @@
+setJitCompilerOption("baseline.usecount.trigger", 10);
+setJitCompilerOption("ion.usecount.trigger", 20);
+
+var uceFault = function (i) {
+    if (i > 98)
+        uceFault = function (i) { return true; };
+    return false;
+};
+
+// Without "use script" in the inner function, the arguments might be
+// obersvable.
+function inline_notSoEmpty1(a, b, c, d) {
+    // This function is not strict, so we might be able to observe its
+    // arguments, if somebody ever called fun.arguments inside it.
+    return { v: (a.v + b.v + c.v + d.v - 10) / 4 };
+}
+var uceFault_notSoEmpty1 = eval(uneval(uceFault).replace('uceFault', 'uceFault_notSoEmpty1'));
+function notSoEmpty1() {
+    var a = { v: i };
+    var b = { v: 1 + a.v };
+    var c = { v: 2 + b.v };
+    var d = { v: 3 + c.v };
+    var unused = { v: 4 + d.v };
+    var res = inline_notSoEmpty1(a, b, c, d);
+    if (uceFault_notSoEmpty1(i) || uceFault_notSoEmpty1(i))
+        assertEq(i, res.v);
+}
+
+// Check that we can recover objects with their content.
+function inline_notSoEmpty2(a, b, c, d) {
+    "use strict";
+    return { v: (a.v + b.v + c.v + d.v - 10) / 4 };
+}
+var uceFault_notSoEmpty2 = eval(uneval(uceFault).replace('uceFault', 'uceFault_notSoEmpty2'));
+function notSoEmpty2(i) {
+    var a = { v: i };
+    var b = { v: 1 + a.v };
+    var c = { v: 2 + b.v };
+    var d = { v: 3 + c.v };
+    var unused = { v: 4 + d.v };
+    var res = inline_notSoEmpty2(a, b, c, d);
+    if (uceFault_notSoEmpty2(i) || uceFault_notSoEmpty2(i))
+        assertEq(i, res.v);
+}
+
+// Check that we can recover objects with their content.
+var argFault_observeArg = function (i) {
+    if (i > 98)
+        return inline_observeArg.arguments[0];
+    return { test : i };
+};
+function inline_observeArg(obj, i) {
+    return argFault_observeArg(i);
+}
+function observeArg(i) {
+    var obj = { test: i };
+    var res = inline_observeArg(obj, i);
+    assertEq(res.test, i);
+}
+
+// Check case where one successor can have multiple times the same predecessor.
+function complexPhi(i) {
+    var obj = { test: i };
+    switch (i) { // TableSwitch
+        case 0: obj.test = 0; break;
+        case 1: obj.test = 1; break;
+        case 2: obj.test = 2; break;
+        case 3: case 4: case 5: case 6:
+        default: obj.test = i; break;
+        case 7: obj.test = 7; break;
+        case 8: obj.test = 8; break;
+        case 9: obj.test = 9; break;
+    }
+    assertEq(obj.test, i);
+}
+
+// Check case where one successor can have multiple times the same predecessor.
+function withinIf(i) {
+    var x = undefined;
+    if (i > 5) {
+        let obj = { foo: i };
+        x = obj.foo;
+        obj = undefined;
+    } else {
+        let obj = { bar: i };
+        x = obj.bar;
+        obj = undefined;
+    }
+    assertEq(x, i);
+}
+
+// Check case where one successor can have multiple times the same predecessor.
+function unknownLoad(i) {
+    var obj = { foo: i };
+    assertEq(obj.bar, undefined);
+}
+
+
+for (var i = 0; i < 100; i++) {
+    notSoEmpty1(i);
+    notSoEmpty2(i);
+    observeArg(i);
+    complexPhi(i);
+    withinIf(i);
+    unknownLoad(i);
+}
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -31,16 +31,17 @@
 #include "jit/JitCompartment.h"
 #include "jit/LICM.h"
 #include "jit/LinearScan.h"
 #include "jit/LIR.h"
 #include "jit/Lowering.h"
 #include "jit/ParallelSafetyAnalysis.h"
 #include "jit/PerfSpewer.h"
 #include "jit/RangeAnalysis.h"
+#include "jit/ScalarReplacement.h"
 #include "jit/StupidAllocator.h"
 #include "jit/UnreachableCodeElimination.h"
 #include "jit/ValueNumbering.h"
 #include "vm/ForkJoin.h"
 #include "vm/HelperThreads.h"
 #include "vm/TraceLogging.h"
 
 #include "jscompartmentinlines.h"
@@ -1326,16 +1327,27 @@ OptimizeMIR(MIRGenerator *mir)
         if (!BuildDominatorTree(graph))
             return false;
         // No spew: graph not changed.
 
         if (mir->shouldCancel("Dominator Tree"))
             return false;
     }
 
+    if (mir->optimizationInfo().scalarReplacementEnabled()) {
+        AutoTraceLog log(logger, TraceLogger::ScalarReplacement);
+        if (!ScalarReplacement(mir, graph))
+            return false;
+        IonSpewPass("Scalar Replacement");
+        AssertGraphCoherency(graph);
+
+        if (mir->shouldCancel("Scalar Replacement"))
+            return false;
+    }
+
     {
         AutoTraceLog log(logger, TraceLogger::PhiAnalysis);
         // Aggressive phi elimination must occur before any code elimination. If the
         // script contains a try-statement, we only compiled the try block and not
         // the catch or finally blocks, so in this case it's also invalid to use
         // aggressive phi elimination.
         Observability observability = graph.hasTryBlock()
                                       ? ConservativeObservability
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -182,19 +182,22 @@ jit::EliminateDeadCode(MIRGenerator *mir
     for (PostorderIterator block = graph.poBegin(); block != graph.poEnd(); block++) {
         if (mir->shouldCancel("Eliminate Dead Code (main loop)"))
             return false;
 
         // Remove unused instructions.
         for (MInstructionReverseIterator inst = block->rbegin(); inst != block->rend(); ) {
             if (!inst->isEffectful() && !inst->resumePoint() &&
                 !inst->hasUses() && !inst->isGuard() &&
-                !inst->isControlInstruction()) {
+                !inst->isControlInstruction())
+            {
                 inst = block->discardAt(inst);
-            } else if (!inst->hasLiveDefUses() && inst->canRecoverOnBailout()) {
+            } else if (!inst->isRecoveredOnBailout() && !inst->hasLiveDefUses() &&
+                       inst->canRecoverOnBailout())
+            {
                 inst->setRecoveredOnBailout();
                 inst++;
             } else {
                 inst++;
             }
         }
     }
 
--- a/js/src/jit/IonOptimizationLevels.cpp
+++ b/js/src/jit/IonOptimizationLevels.cpp
@@ -33,16 +33,17 @@ OptimizationInfo::initNormalOptimization
     uce_ = true;
     rangeAnalysis_ = true;
     autoTruncate_ = true;
     registerAllocator_ = RegisterAllocator_LSRA;
 
     inlineMaxTotalBytecodeLength_ = 1000;
     inliningMaxCallerBytecodeLength_ = 10000;
     maxInlineDepth_ = 3;
+    scalarReplacement_ = true;
     smallFunctionMaxInlineDepth_ = 10;
     usesBeforeCompile_ = 1000;
     usesBeforeInliningFactor_ = 0.125;
 }
 
 void
 OptimizationInfo::initAsmjsOptimizationInfo()
 {
@@ -52,16 +53,17 @@ OptimizationInfo::initAsmjsOptimizationI
     // Take normal option values for not specified values.
     initNormalOptimizationInfo();
 
     level_ = Optimization_AsmJS;
     edgeCaseAnalysis_ = false;
     eliminateRedundantChecks_ = false;
     autoTruncate_ = false;
     registerAllocator_ = RegisterAllocator_Backtracking;
+    scalarReplacement_ = false;        // AsmJS has no objects.
 }
 
 uint32_t
 OptimizationInfo::usesBeforeCompile(JSScript *script, jsbytecode *pc) const
 {
     JS_ASSERT(pc == nullptr || pc == script->code() || JSOp(*pc) == JSOP_LOOPENTRY);
 
     if (pc == script->code())
--- a/js/src/jit/IonOptimizationLevels.h
+++ b/js/src/jit/IonOptimizationLevels.h
@@ -86,16 +86,19 @@ class OptimizationInfo
 
     // The maximum bytecode length the caller may have,
     // before we stop inlining large functions in that caller.
     uint32_t inliningMaxCallerBytecodeLength_;
 
     // The maximum inlining depth.
     uint32_t maxInlineDepth_;
 
+    // Toggles whether scalar replacement is used.
+    bool scalarReplacement_;
+
     // The maximum inlining depth for functions.
     //
     // Inlining small functions has almost no compiling overhead
     // and removes the otherwise needed call overhead.
     // The value is currently very low.
     // Actually it is only needed to make sure we don't blow out the stack.
     uint32_t smallFunctionMaxInlineDepth_;
 
@@ -160,16 +163,20 @@ class OptimizationInfo
     }
 
     IonRegisterAllocator registerAllocator() const {
         if (!js_JitOptions.forceRegisterAllocator)
             return registerAllocator_;
         return js_JitOptions.forcedRegisterAllocator;
     }
 
+    bool scalarReplacementEnabled() const {
+        return scalarReplacement_ && !js_JitOptions.disableScalarReplacement;
+    }
+
     uint32_t smallFunctionMaxInlineDepth() const {
         return smallFunctionMaxInlineDepth_;
     }
 
     bool isSmallFunction(JSScript *script) const;
 
     uint32_t maxInlineDepth() const {
         return maxInlineDepth_;
--- a/js/src/jit/JitOptions.cpp
+++ b/js/src/jit/JitOptions.cpp
@@ -31,16 +31,19 @@ JitOptions::JitOptions()
 
     // Whether to enable extra code to perform dynamic validation of
     // RangeAnalysis results.
     checkRangeAnalysis = false;
 
     // Whether Ion should compile try-catch statements.
     compileTryCatch = true;
 
+    // Toggle whether eager scalar replacement is globally disabled.
+    disableScalarReplacement = true; // experimental
+
     // Toggle whether global value numbering is globally disabled.
     disableGvn = false;
 
     // Toggles whether loop invariant code motion is globally disabled.
     disableLicm = false;
 
     // Toggles whether inlining is globally disabled.
     disableInlining = false;
--- a/js/src/jit/JitOptions.h
+++ b/js/src/jit/JitOptions.h
@@ -31,16 +31,17 @@ enum IonRegisterAllocator {
 struct JitOptions
 {
     bool checkGraphConsistency;
 #ifdef CHECK_OSIPOINT_REGISTERS
     bool checkOsiPointRegisters;
 #endif
     bool checkRangeAnalysis;
     bool compileTryCatch;
+    bool disableScalarReplacement;
     bool disableGvn;
     bool disableLicm;
     bool disableInlining;
     bool disableEdgeCaseAnalysis;
     bool disableRangeAnalysis;
     bool disableUce;
     bool disableEaa;
     bool eagerCompilation;
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -2809,16 +2809,57 @@ MBeta::printOpcode(FILE *fp) const
 
 bool
 MNewObject::shouldUseVM() const
 {
     JSObject *obj = templateObject();
     return obj->hasSingletonType() || obj->hasDynamicSlots();
 }
 
+MObjectState::MObjectState(MDefinition *obj)
+{
+    // This instruction is only used as a summary for bailout paths.
+    setRecoveredOnBailout();
+    JSObject *templateObject = obj->toNewObject()->templateObject();
+    numSlots_ = templateObject->slotSpan();
+    numFixedSlots_ = templateObject->numFixedSlots();
+}
+
+bool
+MObjectState::init(TempAllocator &alloc, MDefinition *obj)
+{
+    if (!MVariadicInstruction::init(alloc, numSlots() + 1))
+        return false;
+    initOperand(0, obj);
+    return true;
+}
+
+MObjectState *
+MObjectState::New(TempAllocator &alloc, MDefinition *obj, MDefinition *undefinedVal)
+{
+    MObjectState *res = new(alloc) MObjectState(obj);
+    if (!res || !res->init(alloc, obj))
+        return nullptr;
+    for (size_t i = 0; i < res->numSlots(); i++)
+        res->initSlot(i, undefinedVal);
+    return res;
+}
+
+MObjectState *
+MObjectState::Copy(TempAllocator &alloc, MObjectState *state)
+{
+    MDefinition *obj = state->object();
+    MObjectState *res = new(alloc) MObjectState(obj);
+    if (!res || !res->init(alloc, obj))
+        return nullptr;
+    for (size_t i = 0; i < res->numSlots(); i++)
+        res->initSlot(i, state->getSlot(i));
+    return res;
+}
+
 bool
 MNewArray::shouldUseVM() const
 {
     JS_ASSERT(count() < JSObject::NELEMENTS_LIMIT);
 
     size_t arraySlots =
         gc::GetGCKindSlots(templateObject()->tenuredGetAllocKind()) - ObjectElements::VALUES_PER_HEADER;
 
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -948,16 +948,53 @@ class MQuaternaryInstruction : public MA
         MDefinition *third = getOperand(2);
         MDefinition *fourth = getOperand(3);
 
         return op() + first->id() + second->id() +
                       third->id() + fourth->id();
     }
 };
 
+class MVariadicInstruction : public MInstruction
+{
+    FixedList<MUse> operands_;
+
+  protected:
+    bool init(TempAllocator &alloc, size_t length) {
+        return operands_.init(alloc, length);
+    }
+    void initOperand(size_t index, MDefinition *operand) {
+        // FixedList doesn't initialize its elements, so do an unchecked init.
+        operands_[index].initUnchecked(operand, this);
+    }
+    MUse *getUseFor(size_t index) MOZ_FINAL MOZ_OVERRIDE {
+        return &operands_[index];
+    }
+    const MUse *getUseFor(size_t index) const MOZ_FINAL MOZ_OVERRIDE {
+        return &operands_[index];
+    }
+
+  public:
+    // Will assert if called before initialization.
+    MDefinition *getOperand(size_t index) const MOZ_FINAL MOZ_OVERRIDE {
+        return operands_[index].producer();
+    }
+    size_t numOperands() const MOZ_FINAL MOZ_OVERRIDE {
+        return operands_.length();
+    }
+    size_t indexOf(const MUse *u) const MOZ_FINAL MOZ_OVERRIDE {
+        MOZ_ASSERT(u >= &operands_[0]);
+        MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
+        return u - &operands_[0];
+    }
+    void replaceOperand(size_t index, MDefinition *operand) MOZ_FINAL MOZ_OVERRIDE {
+        operands_[index].replaceProducer(operand);
+    }
+};
+
 // Generates an LSnapshot without further effect.
 class MStart : public MNullaryInstruction
 {
   public:
     enum StartType {
         StartType_Default,
         StartType_Osr
     };
@@ -1806,16 +1843,78 @@ class MNewDerivedTypedObject
     }
 
     bool writeRecoverData(CompactBufferWriter &writer) const;
     bool canRecoverOnBailout() const {
         return true;
     }
 };
 
+// Represent the content of all slots of an object.  This instruction is not
+// lowered and is not used to generate code.
+class MObjectState : public MVariadicInstruction
+{
+  private:
+    uint32_t numSlots_;
+    uint32_t numFixedSlots_;
+
+    MObjectState(MDefinition *obj);
+
+    bool init(TempAllocator &alloc, MDefinition *obj);
+
+    void initSlot(uint32_t slot, MDefinition *def) {
+        initOperand(slot + 1, def);
+    }
+
+  public:
+    INSTRUCTION_HEADER(ObjectState)
+
+    static MObjectState *New(TempAllocator &alloc, MDefinition *obj, MDefinition *undefinedVal);
+    static MObjectState *Copy(TempAllocator &alloc, MObjectState *state);
+
+    MDefinition *object() const {
+        return getOperand(0);
+    }
+
+    size_t numFixedSlots() const {
+        return numFixedSlots_;
+    }
+    size_t numSlots() const {
+        return numSlots_;
+    }
+
+    MDefinition *getSlot(uint32_t slot) const {
+        return getOperand(slot + 1);
+    }
+    void setSlot(uint32_t slot, MDefinition *def) {
+        replaceOperand(slot + 1, def);
+    }
+
+    MDefinition *getFixedSlot(uint32_t slot) const {
+        MOZ_ASSERT(slot < numFixedSlots());
+        return getSlot(slot);
+    }
+    void setFixedSlot(uint32_t slot, MDefinition *def) {
+        MOZ_ASSERT(slot < numFixedSlots());
+        setSlot(slot, def);
+    }
+
+    MDefinition *getDynamicSlot(uint32_t slot) const {
+        return getSlot(slot + numFixedSlots());
+    }
+    void setDynamicSlot(uint32_t slot, MDefinition *def) {
+        setSlot(slot + numFixedSlots(), def);
+    }
+
+    bool writeRecoverData(CompactBufferWriter &writer) const;
+    bool canRecoverOnBailout() const {
+        return true;
+    }
+};
+
 // Setting __proto__ in an object literal.
 class MMutateProto
   : public MAryInstruction<2>,
     public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >
 {
   protected:
     MMutateProto(MDefinition *obj, MDefinition *value)
     {
@@ -1989,53 +2088,16 @@ class MInitElemGetterSetter
     MDefinition *value() const {
         return getOperand(2);
     }
     TypePolicy *typePolicy() {
         return this;
     }
 };
 
-class MVariadicInstruction : public MInstruction
-{
-    FixedList<MUse> operands_;
-
-  protected:
-    bool init(TempAllocator &alloc, size_t length) {
-        return operands_.init(alloc, length);
-    }
-    void initOperand(size_t index, MDefinition *operand) {
-        // FixedList doesn't initialize its elements, so do an unchecked init.
-        operands_[index].initUnchecked(operand, this);
-    }
-    MUse *getUseFor(size_t index) MOZ_FINAL MOZ_OVERRIDE {
-        return &operands_[index];
-    }
-    const MUse *getUseFor(size_t index) const MOZ_FINAL MOZ_OVERRIDE {
-        return &operands_[index];
-    }
-
-  public:
-    // Will assert if called before initialization.
-    MDefinition *getOperand(size_t index) const MOZ_FINAL MOZ_OVERRIDE {
-        return operands_[index].producer();
-    }
-    size_t numOperands() const MOZ_FINAL MOZ_OVERRIDE {
-        return operands_.length();
-    }
-    size_t indexOf(const MUse *u) const MOZ_FINAL MOZ_OVERRIDE {
-        MOZ_ASSERT(u >= &operands_[0]);
-        MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
-        return u - &operands_[0];
-    }
-    void replaceOperand(size_t index, MDefinition *operand) MOZ_FINAL MOZ_OVERRIDE {
-        operands_[index].replaceProducer(operand);
-    }
-};
-
 class MCall
   : public MVariadicInstruction,
     public CallPolicy
 {
   private:
     // An MCall uses the MPrepareCall, MDefinition for the function, and
     // MPassArg instructions. They are stored in the same list.
     static const size_t FunctionOperandIndex   = 0;
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -89,16 +89,17 @@ namespace jit {
     _(TruncateToInt32)                                                      \
     _(ToString)                                                             \
     _(NewArray)                                                             \
     _(NewObject)                                                            \
     _(NewDeclEnvObject)                                                     \
     _(NewCallObject)                                                        \
     _(NewRunOnceCallObject)                                                 \
     _(NewStringObject)                                                      \
+    _(ObjectState)                                                          \
     _(InitElem)                                                             \
     _(InitElemGetterSetter)                                                 \
     _(MutateProto)                                                          \
     _(InitProp)                                                             \
     _(InitPropGetterSetter)                                                 \
     _(Start)                                                                \
     _(OsrEntry)                                                             \
     _(Nop)                                                                  \
--- a/js/src/jit/ParallelSafetyAnalysis.cpp
+++ b/js/src/jit/ParallelSafetyAnalysis.cpp
@@ -182,16 +182,17 @@ class ParallelSafetyVisitor : public MIn
     SAFE_OP(TruncateToInt32)
     SAFE_OP(MaybeToDoubleElement)
     CUSTOM_OP(ToString)
     CUSTOM_OP(NewArray)
     CUSTOM_OP(NewObject)
     CUSTOM_OP(NewCallObject)
     CUSTOM_OP(NewRunOnceCallObject)
     CUSTOM_OP(NewDerivedTypedObject)
+    SAFE_OP(ObjectState)
     UNSAFE_OP(InitElem)
     UNSAFE_OP(InitElemGetterSetter)
     UNSAFE_OP(MutateProto)
     UNSAFE_OP(InitProp)
     UNSAFE_OP(InitPropGetterSetter)
     SAFE_OP(Start)
     UNSAFE_OP(OsrEntry)
     SAFE_OP(Nop)
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -940,8 +940,39 @@ RNewDerivedTypedObject::recover(JSContex
     JSObject *obj = TypedObject::createDerived(cx, descr, owner, offset);
     if (!obj)
         return false;
 
     RootedValue result(cx, ObjectValue(*obj));
     iter.storeInstructionResult(result);
     return true;
 }
+
+bool
+MObjectState::writeRecoverData(CompactBufferWriter &writer) const
+{
+    MOZ_ASSERT(canRecoverOnBailout());
+    writer.writeUnsigned(uint32_t(RInstruction::Recover_ObjectState));
+    writer.writeUnsigned(numSlots());
+    return true;
+}
+
+RObjectState::RObjectState(CompactBufferReader &reader)
+{
+    numSlots_ = reader.readUnsigned();
+}
+
+bool
+RObjectState::recover(JSContext *cx, SnapshotIterator &iter) const
+{
+    RootedObject object(cx, &iter.read().toObject());
+    MOZ_ASSERT(object->slotSpan() == numSlots());
+
+    RootedValue val(cx);
+    for (size_t i = 0; i < numSlots(); i++) {
+        val = iter.read();
+        object->nativeSetSlot(i, val);
+    }
+
+    val.setObject(*object);
+    iter.storeInstructionResult(val);
+    return true;
+}
--- a/js/src/jit/Recover.h
+++ b/js/src/jit/Recover.h
@@ -41,17 +41,18 @@ namespace jit {
     _(Pow)                                      \
     _(PowHalf)                                  \
     _(MinMax)                                   \
     _(Abs)                                      \
     _(Sqrt)                                     \
     _(Atan2)                                    \
     _(StringSplit)                              \
     _(NewObject)                                \
-    _(NewDerivedTypedObject)
+    _(NewDerivedTypedObject)                    \
+    _(ObjectState)
 
 class RResumePoint;
 class SnapshotIterator;
 
 class RInstruction
 {
   public:
     enum Opcode
@@ -479,16 +480,35 @@ class RNewDerivedTypedObject MOZ_FINAL :
 
     virtual uint32_t numOperands() const {
         return 3;
     }
 
     bool recover(JSContext *cx, SnapshotIterator &iter) const;
 };
 
+class RObjectState MOZ_FINAL : public RInstruction
+{
+  private:
+    uint32_t numSlots_;        // Number of slots.
+
+  public:
+    RINSTRUCTION_HEADER_(ObjectState)
+
+    uint32_t numSlots() const {
+        return numSlots_;
+    }
+    virtual uint32_t numOperands() const {
+        // +1 for the object.
+        return numSlots() + 1;
+    }
+
+    bool recover(JSContext *cx, SnapshotIterator &iter) const;
+};
+
 #undef RINSTRUCTION_HEADER_
 
 const RResumePoint *
 RInstruction::toResumePoint() const
 {
     MOZ_ASSERT(isResumePoint());
     return static_cast<const RResumePoint *>(this);
 }
new file mode 100644
--- /dev/null
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -0,0 +1,276 @@
+/* -*- 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/ScalarReplacement.h"
+
+#include "mozilla/Vector.h"
+
+#include "jit/MIR.h"
+#include "jit/MIRGenerator.h"
+#include "jit/MIRGraph.h"
+
+namespace js {
+namespace jit {
+
+// Scan resume point operands in search of a local variable which captures the
+// current object, and replace it by the current object with its state.
+static void
+ReplaceResumePointOperands(MResumePoint *resumePoint, MDefinition *object, MDefinition *state)
+{
+    // Note: This function iterates over the caller as well, this is wrong
+    // because if the object appears in one of the caller, we want to correctly
+    // recover the object value from any block having the same caller.  In
+    // practice, this is correct for 2 reasons:
+    //
+    // 1. We replace resume point operands in RPO, this implies that the caller
+    // would first be updated when we update the resume point of entry block of
+    // the inner function.  This implies that the object state would only hold
+    // valid data for the caller resume point.
+    //
+    // 2. The caller resume point will have no reference of the new object
+    // allocation if the object allocation is done within the callee.
+    //
+    // A side-effect of this implementation is that we would be restoring and
+    // keeping tracks of the content of the object at the entry of the function,
+    // in addition to the content of the object within the function.
+    for (MResumePoint *rp = resumePoint; rp; rp = rp->caller()) {
+        for (size_t op = 0; op < rp->numOperands(); op++) {
+            if (rp->getOperand(op) == object) {
+                rp->replaceOperand(op, state);
+
+                // This assertion verifies the comment which is above still
+                // holds.  Note, this is not true if rp == resumePoint, as the
+                // object state can be a new one created at the beginning of the
+                // block to keep track of the merge state.
+                MOZ_ASSERT_IF(rp != resumePoint, state->block()->dominates(rp->block()));
+            }
+        }
+    }
+}
+
+// Returns False if the object is not escaped and if it is optimizable by
+// ScalarReplacementOfObject.
+//
+// For the moment, this code is dumb as it only supports objects which are not
+// changing shape, and which are known by TI at the object creation.
+static bool
+IsObjectEscaped(MInstruction *ins)
+{
+    MOZ_ASSERT(ins->type() == MIRType_Object);
+
+    // Check if the object is escaped. If the object is not the first argument
+    // of either a known Store / Load, then we consider it as escaped. This is a
+    // cheap an conservative escape analysis.
+    for (MUseIterator i(ins->usesBegin()); i != ins->usesEnd(); i++) {
+        MNode *consumer = (*i)->consumer();
+        if (!consumer->isDefinition()) {
+            // Cannot optimize if it is observable from fun.arguments or others.
+            if (consumer->toResumePoint()->isObservableOperand(*i))
+                return true;
+            continue;
+        }
+
+        MDefinition *def = consumer->toDefinition();
+        switch (def->op()) {
+          case MDefinition::Op_StoreFixedSlot:
+          case MDefinition::Op_LoadFixedSlot:
+            // Not escaped if it is the first argument.
+            if (def->indexOf(*i) == 0)
+                break;
+            return true;
+          default:
+            return true;
+        }
+    }
+
+    return false;
+}
+
+typedef MObjectState BlockState;
+typedef Vector<BlockState *, 8, SystemAllocPolicy> GraphState;
+
+// This function replaces every MStoreFixedSlot / MStoreSlot by an MObjectState
+// which emulates the content of the object. Every MLoadFixedSlot and MLoadSlot
+// is replaced by the corresponding value.
+//
+// In order to restore the value of the object correctly in case of bailouts, we
+// replace all references of the allocation by the MObjectState definitions.
+static bool
+ScalarReplacementOfObject(MIRGenerator *mir, MIRGraph &graph, GraphState &states,
+                          MInstruction *obj)
+{
+    // For each basic block, we record the last/first state of the object in
+    // each of the basic blocks.
+    if (!states.appendN(nullptr, graph.numBlocks()))
+        return false;
+
+    // Uninitialized slots have an "undefined" value.
+    MBasicBlock *objBlock = obj->block();
+    MConstant *undefinedVal = MConstant::New(graph.alloc(), UndefinedValue());
+    objBlock->insertBefore(obj, undefinedVal);
+    states[objBlock->id()] = BlockState::New(graph.alloc(), obj, undefinedVal);
+
+    // Iterate over each basic block and save the object's layout.
+    for (ReversePostorderIterator block = graph.rpoBegin(obj->block()); block != graph.rpoEnd(); block++) {
+        if (mir->shouldCancel("Scalar Replacement of Object"))
+            return false;
+
+        BlockState *state = states[block->id()];
+        if (!state) {
+            MOZ_ASSERT(!objBlock->dominates(*block));
+            continue;
+        }
+
+        // Insert the state either at the location of the new object, or after
+        // all the phi nodes if the block has multiple predecessors.
+        if (*block == objBlock)
+            objBlock->insertAfter(obj, state);
+        else if (block->numPredecessors() > 1)
+            block->insertBefore(*block->begin(), state);
+        else
+            MOZ_ASSERT(state->block()->dominates(*block));
+
+        // Replace the local variable references by references to the object state.
+        ReplaceResumePointOperands(block->entryResumePoint(), obj, state);
+
+        for (MDefinitionIterator ins(*block); ins; ) {
+            switch (ins->op()) {
+              case MDefinition::Op_ObjectState: {
+                ins++;
+                continue;
+              }
+
+              case MDefinition::Op_LoadFixedSlot: {
+                MLoadFixedSlot *def = ins->toLoadFixedSlot();
+
+                // Skip loads made on other objects.
+                if (def->object() != obj)
+                    break;
+
+                // Replace load by the slot value.
+                ins->replaceAllUsesWith(state->getFixedSlot(def->slot()));
+
+                // Remove original instruction.
+                ins = block->discardDefAt(ins);
+                continue;
+              }
+
+              case MDefinition::Op_StoreFixedSlot: {
+                MStoreFixedSlot *def = ins->toStoreFixedSlot();
+
+                // Skip stores made on other objects.
+                if (def->object() != obj)
+                    break;
+
+                // Clone the state and update the slot value.
+                state = BlockState::Copy(graph.alloc(), state);
+                state->setFixedSlot(def->slot(), def->value());
+                block->insertBefore(ins->toInstruction(), state);
+
+                // Remove original instruction.
+                ins = block->discardDefAt(ins);
+                continue;
+              }
+
+              default:
+                break;
+            }
+
+            // Replace the local variable references by references to the object state.
+            if (ins->isInstruction())
+                ReplaceResumePointOperands(ins->toInstruction()->resumePoint(), obj, state);
+
+            ins++;
+        }
+
+        // For each successor, copy/merge the current state as being the initial
+        // state of the successor block.
+        for (size_t s = 0; s < block->numSuccessors(); s++) {
+            MBasicBlock *succ = block->getSuccessor(s);
+            BlockState *succState = states[succ->id()];
+
+            // When a block has no state yet, create an empty one for the
+            // successor.
+            if (!succState) {
+                // If the successor is not dominated then the object cannot flow
+                // in this basic block without a Phi.  We know that no Phi exist
+                // in non-dominated successors as the conservative escaped
+                // analysis fails otherwise.  Such condition can succeed if the
+                // successor is a join at the end of a if-block and the object
+                // only exists within the branch.
+                if (!objBlock->dominates(succ))
+                    continue;
+
+                if (succ->numPredecessors() > 1) {
+                    succState = states[succ->id()] = BlockState::Copy(graph.alloc(), state);
+                    size_t numPreds = succ->numPredecessors();
+                    for (size_t slot = 0; slot < state->numSlots(); slot++) {
+                        MPhi *phi = MPhi::New(graph.alloc());
+                        if (!phi->reserveLength(numPreds))
+                            return false;
+
+                        // Fill the input of the successors Phi with undefined
+                        // values, and each block later fills the Phi inputs.
+                        for (size_t p = 0; p < numPreds; p++)
+                            phi->addInput(undefinedVal);
+
+                        // Add Phi in the list of Phis of the basic block.
+                        succ->addPhi(phi);
+                        succState->setSlot(slot, phi);
+                    }
+                } else {
+                    succState = states[succ->id()] = state;
+                }
+            }
+
+            if (succ->numPredecessors() > 1) {
+                // The current block might appear multiple times among the
+                // predecessors. As we need to replace all the inputs, we need
+                // to check all predecessors against the current block to
+                // replace the Phi node operands.
+                size_t numPreds = succ->numPredecessors();
+                for (size_t p = 0; p < numPreds; p++) {
+                    if (succ->getPredecessor(p) != *block)
+                        continue;
+
+                    // Copy the current slot state to the predecessor index of
+                    // each Phi of the same slot.
+                    for (size_t slot = 0; slot < state->numSlots(); slot++) {
+                        MPhi *phi = succState->getSlot(slot)->toPhi();
+                        phi->replaceOperand(p, state->getSlot(slot));
+                    }
+                }
+            }
+        }
+    }
+
+    MOZ_ASSERT(!obj->hasLiveDefUses());
+    obj->setRecoveredOnBailout();
+    states.clear();
+    return true;
+}
+
+bool
+ScalarReplacement(MIRGenerator *mir, MIRGraph &graph)
+{
+    GraphState objectStates;
+    for (ReversePostorderIterator block = graph.rpoBegin(); block != graph.rpoEnd(); block++) {
+        if (mir->shouldCancel("Scalar Replacement (main loop)"))
+            return false;
+
+        for (MInstructionIterator ins = block->begin(); ins != block->end(); ins++) {
+            if (ins->isNewObject() && !IsObjectEscaped(*ins)) {
+                if (!ScalarReplacementOfObject(mir, graph, objectStates, *ins))
+                    return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+} /* namespace jit */
+} /* namespace js */
new file mode 100644
--- /dev/null
+++ b/js/src/jit/ScalarReplacement.h
@@ -0,0 +1,27 @@
+/* -*- 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 scalar replacement of objects transformation.
+#ifndef jit_ScalarReplacement_h
+#define jit_ScalarReplacement_h
+
+#ifdef JS_ION
+
+namespace js {
+namespace jit {
+
+class MIRGenerator;
+class MIRGraph;
+
+bool
+ScalarReplacement(MIRGenerator *mir, MIRGraph &graph);
+
+} // namespace jit
+} // namespace js
+
+#endif // JS_ION
+
+#endif /* jit_ScalarReplacement_h */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -291,16 +291,17 @@ if CONFIG['ENABLE_ION']:
         'jit/ParallelFunctions.cpp',
         'jit/ParallelSafetyAnalysis.cpp',
         'jit/PerfSpewer.cpp',
         'jit/RangeAnalysis.cpp',
         '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/Snapshots.cpp',
         'jit/StupidAllocator.cpp',
         'jit/TypedObjectPrediction.cpp',
         'jit/TypePolicy.cpp',
         'jit/UnreachableCodeElimination.cpp',
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5975,16 +5975,25 @@ SetRuntimeOptions(JSRuntime *rt, const O
     bool enableAsmJS = !op.getBoolOption("no-asmjs");
     bool enableNativeRegExp = !op.getBoolOption("no-native-regexp");
 
     JS::RuntimeOptionsRef(rt).setBaseline(enableBaseline)
                              .setIon(enableIon)
                              .setAsmJS(enableAsmJS)
                              .setNativeRegExp(enableNativeRegExp);
 
+    if (const char *str = op.getStringOption("ion-scalar-replacement")) {
+        if (strcmp(str, "on") == 0)
+            jit::js_JitOptions.disableScalarReplacement = false;
+        else if (strcmp(str, "off") == 0)
+            jit::js_JitOptions.disableScalarReplacement = true;
+        else
+            return OptionFailure("ion-scalar-replacement", str);
+    }
+
     if (const char *str = op.getStringOption("ion-gvn")) {
         if (strcmp(str, "off") == 0) {
             jit::js_JitOptions.disableGvn = true;
         } else if (strcmp(str, "on") != 0 &&
                    strcmp(str, "optimistic") != 0 &&
                    strcmp(str, "pessimistic") != 0)
         {
             // We accept "pessimistic" and "optimistic" as synonyms for "on"
@@ -6271,16 +6280,18 @@ main(int argc, char **argv, char **envp)
 #ifdef JS_THREADSAFE
         || !op.addIntOption('\0', "thread-count", "COUNT", "Use COUNT auxiliary threads "
                             "(default: # of cores - 1)", -1)
 #endif
         || !op.addBoolOption('\0', "ion", "Enable IonMonkey (default)")
         || !op.addBoolOption('\0', "no-ion", "Disable IonMonkey")
         || !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation")
         || !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp compilation")
+        || !op.addStringOption('\0', "ion-scalar-replacement", "on/off",
+                               "Scalar Replacement (default: off, on to enable)")
         || !op.addStringOption('\0', "ion-gvn", "[mode]",
                                "Specify Ion global value numbering:\n"
                                "  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)")
--- a/js/src/vm/TraceLogging.h
+++ b/js/src/vm/TraceLogging.h
@@ -128,16 +128,17 @@ namespace jit {
     _(TL)                                             \
     _(IrregexpCompile)                                \
     _(IrregexpExecute)                                \
     _(VM)                                             \
                                                       \
     /* Specific passes during ion compilation */      \
     _(SplitCriticalEdges)                             \
     _(RenumberBlocks)                                 \
+    _(ScalarReplacement)                              \
     _(DominatorTree)                                  \
     _(PhiAnalysis)                                    \
     _(MakeLoopsContiguous)                            \
     _(ApplyTypes)                                     \
     _(ParallelSafetyAnalysis)                         \
     _(AliasAnalysis)                                  \
     _(GVN)                                            \
     _(UCE)                                            \