Bug 1166711 part 2.4 - MObjectState: Add support for encoding unboxed objects. r=bhackett
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Thu, 11 Jun 2015 14:30:33 +0200
changeset 248314 ace9cd550bf13f9b4ade331380bb1b1a1f2f419f
parent 248313 02dcf30ba6acc3812aed3a3406b17fa1696d065c
child 248315 46958967c5226d14b405af5e1f12c25414caafee
push id28893
push userkwierso@gmail.com
push dateFri, 12 Jun 2015 00:02:58 +0000
treeherderautoland@8cf9d3e497f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs1166711
milestone41.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 1166711 part 2.4 - MObjectState: Add support for encoding unboxed objects. r=bhackett
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/Recover.cpp
js/src/jit/ScalarReplacement.cpp
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -3924,59 +3924,123 @@ bool
 MCreateThisWithTemplate::canRecoverOnBailout() const
 {
     MOZ_ASSERT(templateObject()->is<PlainObject>() || templateObject()->is<UnboxedPlainObject>());
     MOZ_ASSERT_IF(templateObject()->is<PlainObject>(),
                   !templateObject()->as<PlainObject>().denseElementsAreCopyOnWrite());
     return true;
 }
 
-MObjectState::MObjectState(MDefinition* obj)
+bool
+OperandIndexMap::init(TempAllocator& alloc, JSObject* templateObject)
+{
+    const UnboxedLayout& layout =
+        templateObject->as<UnboxedPlainObject>().layoutDontCheckGeneration();
+
+    // 0 is used as an error code.
+    const UnboxedLayout::PropertyVector& properties = layout.properties();
+    MOZ_ASSERT(properties.length() < 255);
+
+    // Allocate an array of indexes, where the top of each field correspond to
+    // the index of the operand in the MObjectState instance.
+    if (!map.init(alloc, layout.size()))
+        return false;
+
+    // Reset all indexes to 0, which is an error code.
+    for (size_t i = 0; i < map.length(); i++)
+        map[i] = 0;
+
+    // Map the property offsets to the indexes of MObjectState operands.
+    uint8_t index = 1;
+    for (size_t i = 0; i < properties.length(); i++, index++)
+        map[properties[i].offset] = index;
+
+    return true;
+}
+
+MObjectState::MObjectState(MObjectState* state)
+  : numSlots_(state->numSlots_),
+    numFixedSlots_(state->numFixedSlots_),
+    operandIndex_(state->operandIndex_)
 {
     // This instruction is only used as a summary for bailout paths.
     setResultType(MIRType_Object);
     setRecoveredOnBailout();
-    NativeObject* templateObject = nullptr;
+}
+
+MObjectState::MObjectState(JSObject *templateObject, OperandIndexMap* operandIndex)
+{
+    // This instruction is only used as a summary for bailout paths.
+    setResultType(MIRType_Object);
+    setRecoveredOnBailout();
+
+    if (templateObject->is<NativeObject>()) {
+        NativeObject* nativeObject = &templateObject->as<NativeObject>();
+        numSlots_ = nativeObject->slotSpan();
+        numFixedSlots_ = nativeObject->numFixedSlots();
+    } else {
+        const UnboxedLayout& layout =
+            templateObject->as<UnboxedPlainObject>().layoutDontCheckGeneration();
+        // Same as UnboxedLayout::makeNativeGroup
+        numSlots_ = layout.properties().length();
+        numFixedSlots_ = gc::GetGCKindSlots(layout.getAllocKind());
+    }
+
+    operandIndex_ = operandIndex;
+}
+
+JSObject*
+MObjectState::templateObjectOf(MDefinition* obj)
+{
     if (obj->isNewObject())
-        templateObject = &obj->toNewObject()->templateObject()->as<PlainObject>();
+        return obj->toNewObject()->templateObject();
     else if (obj->isCreateThisWithTemplate())
-        templateObject = &obj->toCreateThisWithTemplate()->templateObject()->as<PlainObject>();
+        return obj->toCreateThisWithTemplate()->templateObject();
     else
-        templateObject = obj->toNewCallObject()->templateObject();
-    numSlots_ = templateObject->slotSpan();
-    numFixedSlots_ = templateObject->numFixedSlots();
+        return obj->toNewCallObject()->templateObject();
+
+    return nullptr;
 }
 
 bool
 MObjectState::init(TempAllocator& alloc, MDefinition* obj)
 {
     if (!MVariadicInstruction::init(alloc, numSlots() + 1))
         return false;
     // +1, for the Object.
     initOperand(0, obj);
     return true;
 }
 
 MObjectState*
 MObjectState::New(TempAllocator& alloc, MDefinition* obj, MDefinition* undefinedVal)
 {
-    MObjectState* res = new(alloc) MObjectState(obj);
+    JSObject* templateObject = templateObjectOf(obj);
+    MOZ_ASSERT(templateObject, "Unexpected object creation.");
+
+    OperandIndexMap* operandIndex = nullptr;
+    if (templateObject->is<UnboxedPlainObject>()) {
+        operandIndex = new(alloc) OperandIndexMap;
+        if (!operandIndex || !operandIndex->init(alloc, templateObject))
+            return nullptr;
+    }
+
+    MObjectState* res = new(alloc) MObjectState(templateObject, operandIndex);
     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))
+    MObjectState* res = new(alloc) MObjectState(state);
+    if (!res || !res->init(alloc, state->object()))
         return nullptr;
     for (size_t i = 0; i < res->numSlots(); i++)
         res->initSlot(i, state->getSlot(i));
     return res;
 }
 
 MArrayState::MArrayState(MDefinition* arr)
 {
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -3354,45 +3354,68 @@ class MNewDerivedTypedObject
     }
 
     bool writeRecoverData(CompactBufferWriter& writer) const override;
     bool canRecoverOnBailout() const override {
         return true;
     }
 };
 
+// This vector is used when the recovered object is kept unboxed. We map the
+// offset of each property to the index of the corresponding operands in the
+// object state.
+struct OperandIndexMap : public TempObject
+{
+    // The number of properties is limited by scalar replacement. Thus we cannot
+    // have any large number of properties.
+    FixedList<uint8_t> map;
+
+    bool init(TempAllocator& alloc, JSObject* templateObject);
+};
+
 // 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,
     public NoFloatPolicyAfter<1>::Data
 {
   private:
     uint32_t numSlots_;
-    uint32_t numFixedSlots_;
-
-    explicit MObjectState(MDefinition* obj);
+    uint32_t numFixedSlots_;        // valid if isUnboxed() == false.
+    OperandIndexMap* operandIndex_; // valid if isUnboxed() == true.
+
+    bool isUnboxed() const {
+        return operandIndex_ != nullptr;
+    }
+
+    MObjectState(JSObject *templateObject, OperandIndexMap* operandIndex);
+    explicit MObjectState(MObjectState* state);
 
     bool init(TempAllocator& alloc, MDefinition* obj);
 
     void initSlot(uint32_t slot, MDefinition* def) {
         initOperand(slot + 1, def);
     }
 
   public:
     INSTRUCTION_HEADER(ObjectState)
 
+    // Return the template object of any object creation which can be recovered
+    // on bailout.
+    static JSObject* templateObjectOf(MDefinition* obj);
+
     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 {
+        MOZ_ASSERT(!isUnboxed());
         return numFixedSlots_;
     }
     size_t numSlots() const {
         return numSlots_;
     }
 
     MDefinition* getSlot(uint32_t slot) const {
         return getOperand(slot + 1);
@@ -3418,16 +3441,28 @@ class MObjectState
     }
     MDefinition* getDynamicSlot(uint32_t slot) const {
         return getSlot(slot + numFixedSlots());
     }
     void setDynamicSlot(uint32_t slot, MDefinition* def) {
         setSlot(slot + numFixedSlots(), def);
     }
 
+    // Interface reserved for unboxed objects.
+    bool hasOffset(uint32_t offset) const {
+        MOZ_ASSERT(isUnboxed());
+        return offset < operandIndex_->map.length() && operandIndex_->map[offset] != 0;
+    }
+    MDefinition* getOffset(uint32_t offset) const {
+        return getOperand(operandIndex_->map[offset]);
+    }
+    void setOffset(uint32_t offset, MDefinition* def) {
+        replaceOperand(operandIndex_->map[offset], def);
+    }
+
     bool writeRecoverData(CompactBufferWriter& writer) const override;
     bool canRecoverOnBailout() const override {
         return true;
     }
 };
 
 // Represent the contents of all elements of an array.  This instruction is not
 // lowered and is not used to generate code.
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -1364,23 +1364,40 @@ MObjectState::writeRecoverData(CompactBu
 RObjectState::RObjectState(CompactBufferReader& reader)
 {
     numSlots_ = reader.readUnsigned();
 }
 
 bool
 RObjectState::recover(JSContext* cx, SnapshotIterator& iter) const
 {
-    RootedNativeObject object(cx, &iter.read().toObject().as<NativeObject>());
-    MOZ_ASSERT(object->slotSpan() == numSlots());
-
+    RootedObject object(cx, &iter.read().toObject());
     RootedValue val(cx);
-    for (size_t i = 0; i < numSlots(); i++) {
-        val = iter.read();
-        object->setSlot(i, val);
+
+    if (object->is<UnboxedPlainObject>()) {
+        const UnboxedLayout& layout = object->as<UnboxedPlainObject>().layout();
+
+        const UnboxedLayout::PropertyVector& properties = layout.properties();
+        for (size_t i = 0; i < properties.length(); i++) {
+            val = iter.read();
+            // This is the default placeholder value of MObjectState, when no
+            // properties are defined yet.
+            if (val.isUndefined())
+                continue;
+
+            MOZ_ALWAYS_TRUE(object->as<UnboxedPlainObject>().setValue(cx, properties[i], val));
+        }
+    } else {
+        RootedNativeObject nativeObject(cx, &object->as<NativeObject>());
+        MOZ_ASSERT(nativeObject->slotSpan() == numSlots());
+
+        for (size_t i = 0; i < numSlots(); i++) {
+            val = iter.read();
+            nativeObject->setSlot(i, val);
+        }
     }
 
     val.setObject(*object);
     iter.storeInstructionResult(val);
     return true;
 }
 
 bool
--- a/js/src/jit/ScalarReplacement.cpp
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -140,37 +140,25 @@ IsObjectEscaped(MInstruction* ins, JSObj
 {
     MOZ_ASSERT(ins->type() == MIRType_Object);
     MOZ_ASSERT(ins->isNewObject() || ins->isGuardShape() || ins->isCreateThisWithTemplate() ||
                ins->isNewCallObject() || ins->isFunctionEnvironment());
 
     JitSpewDef(JitSpew_Escape, "Check object\n", ins);
     JitSpewIndent spewIndent(JitSpew_Escape);
 
-    JSObject* obj = nullptr;
-    if (ins->isNewObject())
-        obj = ins->toNewObject()->templateObject();
-    else if (ins->isCreateThisWithTemplate())
-        obj = ins->toCreateThisWithTemplate()->templateObject();
-    else if (ins->isNewCallObject())
-        obj = ins->toNewCallObject()->templateObject();
-    else
-        obj = objDefault;
+    JSObject* obj = objDefault;
+    if (!obj)
+        obj = MObjectState::templateObjectOf(ins);
 
     if (!obj) {
         JitSpew(JitSpew_Escape, "No template object defined.");
         return true;
     }
 
-    // Don't optimize unboxed objects, which aren't handled by MObjectState.
-    if (obj->is<UnboxedPlainObject>()) {
-        JitSpew(JitSpew_Escape, "Template object is an unboxed plain object.");
-        return true;
-    }
-
     // 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 and 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()->isRecoverableOperand(*i)) {