Bug 894596 - Bake constant valued object properties into jitcode when possible, r=jandem, patch mostly written by djvj.
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 26 Aug 2014 12:30:36 -0700
changeset 201740 3a545eb9828b
parent 201739 b005b4e38525
child 201741 894b7372561d
push id27380
push userkwierso@gmail.com
push dateWed, 27 Aug 2014 00:50:11 +0000
treeherdermozilla-central@83289be2cd12 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem, patch
bugs894596
milestone34.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 894596 - Bake constant valued object properties into jitcode when possible, r=jandem, patch mostly written by djvj.
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/vm/Shape.h
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -7537,17 +7537,17 @@ IonBuilder::addTypedArrayLengthAndData(M
         void *data = tarr->viewData();
         // Bug 979449 - Optimistically embed the elements and use TI to
         //              invalidate if we move them.
 #ifdef JSGC_GENERATIONAL
         bool isTenured = !tarr->runtimeFromMainThread()->gc.nursery.isInside(data);
 #else
         bool isTenured = true;
 #endif
-        if (isTenured) {
+        if (isTenured && tarr->hasSingletonType()) {
             // The 'data' pointer can change in rare circumstances
             // (ArrayBufferObject::changeContents).
             types::TypeObjectKey *tarrType = types::TypeObjectKey::get(tarr);
             if (!tarrType->unknownProperties()) {
                 tarrType->watchStateChangeForTypedArrayData(constraints());
 
                 obj->setImplicitlyUsedUnchecked();
 
@@ -8672,16 +8672,20 @@ IonBuilder::storeSlot(MDefinition *obj, 
 
 bool
 IonBuilder::jsop_getprop(PropertyName *name)
 {
     bool emitted = false;
 
     MDefinition *obj = current->pop();
 
+    // Try to optimize to a specific constant.
+    if (!getPropTryInferredConstant(&emitted, obj, name) || emitted)
+        return emitted;
+
     // Try to optimize arguments.length.
     if (!getPropTryArgumentsLength(&emitted, obj) || emitted)
         return emitted;
 
     // Try to optimize arguments.callee.
     if (!getPropTryArgumentsCallee(&emitted, obj, name) || emitted)
         return emitted;
 
@@ -8762,16 +8766,38 @@ IonBuilder::checkIsDefinitelyOptimizedAr
         return true;
     }
 
     *isOptimizedArgs = true;
     return true;
 }
 
 bool
+IonBuilder::getPropTryInferredConstant(bool *emitted, MDefinition *obj, PropertyName *name)
+{
+    JS_ASSERT(*emitted == false);
+
+    // Need a result typeset to optimize.
+    types::TemporaryTypeSet *objTypes = obj->resultTypeSet();
+    if (!objTypes)
+        return true;
+
+    Value constVal = UndefinedValue();
+    if (objTypes->propertyIsConstant(constraints(), NameToId(name), &constVal)) {
+        spew("Optimized constant property");
+        obj->setImplicitlyUsedUnchecked();
+        if (!pushConstant(constVal))
+            return false;
+        *emitted = true;
+    }
+
+    return true;
+}
+
+bool
 IonBuilder::getPropTryArgumentsLength(bool *emitted, MDefinition *obj)
 {
     JS_ASSERT(*emitted == false);
 
     bool isOptimizedArgs = false;
     if (!checkIsDefinitelyOptimizedArguments(obj, &isOptimizedArgs))
         return false;
     if (!isOptimizedArgs)
@@ -9375,23 +9401,26 @@ IonBuilder::jsop_setprop(PropertyName *n
     types::TemporaryTypeSet *objTypes = obj->resultTypeSet();
     bool barrier = PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &obj, name, &value,
                                                  /* canModify = */ true);
 
     // Try to emit stores to known binary data blocks
     if (!setPropTryTypedObject(&emitted, obj, name, value) || emitted)
         return emitted;
 
-    // Try to emit store from definite slots.
-    if (!setPropTryDefiniteSlot(&emitted, obj, name, value, barrier, objTypes) || emitted)
-        return emitted;
-
-    // Try to emit a monomorphic/polymorphic store based on baseline caches.
-    if (!setPropTryInlineAccess(&emitted, obj, name, value, barrier, objTypes) || emitted)
-        return emitted;
+    // Do not emit optimized stores to slots that may be constant.
+    if (objTypes && !objTypes->propertyMightBeConstant(constraints(), NameToId(name))) {
+        // Try to emit store from definite slots.
+        if (!setPropTryDefiniteSlot(&emitted, obj, name, value, barrier, objTypes) || emitted)
+            return emitted;
+
+        // Try to emit a monomorphic/polymorphic store based on baseline caches.
+        if (!setPropTryInlineAccess(&emitted, obj, name, value, barrier, objTypes) || emitted)
+            return emitted;
+    }
 
     // Emit a polymorphic cache.
     return setPropTryCache(&emitted, obj, name, value, barrier, objTypes);
 }
 
 bool
 IonBuilder::setPropTryCommonSetter(bool *emitted, MDefinition *obj,
                                    PropertyName *name, MDefinition *value)
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -394,16 +394,17 @@ class IonBuilder : public MIRGenerator
                    MIRType slotType = MIRType_None);
     bool storeSlot(MDefinition *obj, Shape *shape, MDefinition *value, bool needsBarrier,
                    MIRType slotType = MIRType_None);
 
     MDefinition *tryInnerizeWindow(MDefinition *obj);
 
     // jsop_getprop() helpers.
     bool checkIsDefinitelyOptimizedArguments(MDefinition *obj, bool *isOptimizedArgs);
+    bool getPropTryInferredConstant(bool *emitted, MDefinition *obj, PropertyName *name);
     bool getPropTryArgumentsLength(bool *emitted, MDefinition *obj);
     bool getPropTryArgumentsCallee(bool *emitted, MDefinition *obj, PropertyName *name);
     bool getPropTryConstant(bool *emitted, MDefinition *obj, PropertyName *name,
                             types::TemporaryTypeSet *types);
     bool getPropTryDefiniteSlot(bool *emitted, MDefinition *obj, PropertyName *name,
                                 BarrierKind barrier, types::TemporaryTypeSet *types);
     bool getPropTryCommonGetter(bool *emitted, MDefinition *obj, PropertyName *name,
                                 types::TemporaryTypeSet *types);
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -1749,16 +1749,79 @@ HeapTypeSetKey::nonWritable(CompilerCons
     LifoAlloc *alloc = constraints->alloc();
 
     typedef CompilerConstraintInstance<ConstraintDataFreezePropertyState> T;
     constraints->add(alloc->new_<T>(alloc, *this,
                                     ConstraintDataFreezePropertyState(ConstraintDataFreezePropertyState::NON_WRITABLE)));
     return false;
 }
 
+namespace {
+
+class ConstraintDataConstantProperty
+{
+  public:
+    explicit ConstraintDataConstantProperty() {}
+
+    const char *kind() { return "constantProperty"; }
+
+    bool invalidateOnNewType(Type type) { return false; }
+    bool invalidateOnNewPropertyState(TypeSet *property) {
+        return property->nonConstantProperty();
+    }
+    bool invalidateOnNewObjectState(TypeObject *object) { return false; }
+
+    bool constraintHolds(JSContext *cx,
+                         const HeapTypeSetKey &property, TemporaryTypeSet *expected)
+    {
+        return !invalidateOnNewPropertyState(property.maybeTypes());
+    }
+
+    bool shouldSweep() { return false; }
+};
+
+} /* anonymous namespace */
+
+bool
+HeapTypeSetKey::constant(CompilerConstraintList *constraints, Value *valOut)
+{
+    if (nonData(constraints))
+        return false;
+
+    if (!maybeTypes())
+        return false;
+
+    if (maybeTypes()->nonConstantProperty())
+        return false;
+
+    // Only singleton object properties can be marked as constants.
+    JS_ASSERT(object()->singleton());
+
+    // Get the current value of the property.
+    Shape *shape = object()->singleton()->nativeLookupPure(id());
+    if (!shape)
+        return false;
+    Value val = object()->singleton()->nativeGetSlot(shape->slot());
+
+    // If the value is a pointer to an object in the nursery, don't optimize.
+    if (val.isGCThing() && IsInsideNursery(val.toGCThing()))
+        return false;
+
+    // If the value is a string that's not atomic, don't optimize.
+    if (val.isString() && !val.toString()->isAtom())
+        return false;
+
+    *valOut = val;
+
+    LifoAlloc *alloc = constraints->alloc();
+    typedef CompilerConstraintInstance<ConstraintDataConstantProperty> T;
+    constraints->add(alloc->new_<T>(alloc, *this, ConstraintDataConstantProperty()));
+    return true;
+}
+
 bool
 TemporaryTypeSet::filtersType(const TemporaryTypeSet *other, Type filteredType) const
 {
     if (other->unknown())
         return unknown();
 
     for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) {
         Type type = Type::PrimitiveType(TypeFlagPrimitive(flag));
@@ -1776,16 +1839,105 @@ TemporaryTypeSet::filtersType(const Temp
             if (type != filteredType && !hasType(type))
                 return false;
         }
     }
 
     return true;
 }
 
+namespace {
+
+// A constraint that never triggers recompilation.
+class ConstraintDataInert
+{
+  public:
+    explicit ConstraintDataInert() {}
+
+    const char *kind() { return "inert"; }
+
+    bool invalidateOnNewType(Type type) { return false; }
+    bool invalidateOnNewPropertyState(TypeSet *property) { return false; }
+    bool invalidateOnNewObjectState(TypeObject *object) { return false; }
+
+    bool constraintHolds(JSContext *cx,
+                         const HeapTypeSetKey &property, TemporaryTypeSet *expected)
+    {
+        return true;
+    }
+
+    bool shouldSweep() { return false; }
+};
+
+} /* anonymous namespace */
+
+bool
+TemporaryTypeSet::propertyMightBeConstant(CompilerConstraintList *constraints, jsid id)
+{
+    if (unknownObject())
+        return true;
+
+    for (size_t i = 0; i < getObjectCount(); i++) {
+        TypeObjectKey *type = getObject(i);
+
+        // Type sets are only marked as constants when they are lazily
+        // constructed from the properties of singleton typed objects. So watch
+        // for the cases when a property either already is or might be marked
+        // as constant in the future.
+
+        if (!type || !type->isSingleObject())
+            continue;
+
+        if (type->unknownProperties())
+            return true;
+
+        HeapTypeSetKey property = type->property(id);
+        if (!property.maybeTypes() || !property.maybeTypes()->nonConstantProperty())
+            return true;
+    }
+
+    // It is possible for a property that was not marked as constant to
+    // 'become' one, if we throw away the type property during a GC and
+    // regenerate it with the constant flag set. TypeObject::sweep only removes
+    // type properties if they have no constraints attached to them, so add
+    // inert constraints to pin these properties in place.
+
+    LifoAlloc *alloc = constraints->alloc();
+    for (size_t i = 0; i < getObjectCount(); i++) {
+        TypeObjectKey *type = getObject(i);
+
+        if (!type || !type->isSingleObject())
+            continue;
+
+        HeapTypeSetKey property = type->property(id);
+
+        typedef CompilerConstraintInstance<ConstraintDataInert> T;
+        constraints->add(alloc->new_<T>(alloc, property, ConstraintDataInert()));
+    }
+
+    return false;
+}
+
+bool
+TemporaryTypeSet::propertyIsConstant(CompilerConstraintList *constraints, jsid id, Value *valOut)
+{
+    JS_ASSERT(valOut);
+
+    JSObject *singleton = getSingleton();
+    if (!singleton)
+        return false;
+
+    TypeObjectKey *type = TypeObjectKey::get(singleton);
+    if (type->unknownProperties())
+        return false;
+
+    HeapTypeSetKey property = type->property(id);
+    return property.constant(constraints, valOut);
+}
+
 TemporaryTypeSet::DoubleConversion
 TemporaryTypeSet::convertDoubleElements(CompilerConstraintList *constraints)
 {
     if (unknownObject() || !getObjectCount())
         return AmbiguousDoubleConversion;
 
     bool alwaysConvert = true;
     bool maybeConvert = false;
@@ -2756,16 +2908,18 @@ TypeObject::setProto(JSContext *cx, Tagg
 
     setProtoUnchecked(proto);
 }
 
 static inline void
 UpdatePropertyType(ExclusiveContext *cx, HeapTypeSet *types, JSObject *obj, Shape *shape,
                    bool indexed)
 {
+    JS_ASSERT(obj->hasSingletonType() && !obj->hasLazyType());
+
     if (!shape->writable())
         types->setNonWritableProperty(cx);
 
     if (shape->hasGetterValue() || shape->hasSetterValue()) {
         types->setNonDataProperty(cx);
         types->TypeSet::addType(Type::UnknownType(), &cx->typeLifoAlloc());
     } else if (shape->hasDefaultGetter() && shape->hasSlot()) {
         if (!indexed && types->canSetDefinite(shape->slot()))
@@ -2777,28 +2931,38 @@ UpdatePropertyType(ExclusiveContext *cx,
          * Don't add initial undefined types for properties of global objects
          * that are not collated into the JSID_VOID property (see propertySet
          * comment).
          */
         if (indexed || !value.isUndefined() || !CanHaveEmptyPropertyTypesForOwnProperty(obj)) {
             Type type = GetValueType(value);
             types->TypeSet::addType(type, &cx->typeLifoAlloc());
         }
+
+        if (indexed || shape->hadOverwrite()) {
+            types->setNonConstantProperty(cx);
+        } else {
+            InferSpew(ISpewOps, "typeSet: %sT%p%s property %s %s - setConstant",
+                      InferSpewColor(types), types, InferSpewColorReset(),
+                      TypeObjectString(obj->type()), TypeIdString(shape->propid()));
+        }
     }
 }
 
 void
 TypeObject::updateNewPropertyTypes(ExclusiveContext *cx, jsid id, HeapTypeSet *types)
 {
     InferSpew(ISpewOps, "typeSet: %sT%p%s property %s %s",
               InferSpewColor(types), types, InferSpewColorReset(),
               TypeObjectString(this), TypeIdString(id));
 
-    if (!singleton() || !singleton()->isNative())
+    if (!singleton() || !singleton()->isNative()) {
+        types->setNonConstantProperty(cx);
         return;
+    }
 
     /*
      * Fill the property in with any type the object already has in an own
      * property. We are only interested in plain native properties and
      * dense elements which don't go through a barrier when read by the VM
      * or jitcode.
      */
 
@@ -2890,17 +3054,27 @@ TypeObject::matchDefiniteProperties(Hand
 static inline void
 InlineAddTypeProperty(ExclusiveContext *cx, TypeObject *obj, jsid id, Type type)
 {
     JS_ASSERT(id == IdToTypeId(id));
 
     AutoEnterAnalysis enter(cx);
 
     HeapTypeSet *types = obj->getProperty(cx, id);
-    if (!types || types->hasType(type))
+    if (!types)
+        return;
+
+    // Clear any constant flag if it exists.
+    if (!types->nonConstantProperty()) {
+        InferSpew(ISpewOps, "constantMutated: %sT%p%s %s",
+                  InferSpewColor(types), types, InferSpewColorReset(), TypeString(type));
+        types->setNonConstantProperty(cx);
+    }
+
+    if (types->hasType(type))
         return;
 
     InferSpew(ISpewOps, "externalType: property %s %s: %s",
               TypeObjectString(obj), TypeIdString(id), TypeString(type));
     types->addType(cx, type);
 }
 
 void
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -353,17 +353,17 @@ public:
     /* Debugging name for this kind of constraint. */
     virtual const char *kind() = 0;
 
     /* Register a new type for the set this constraint is listening to. */
     virtual void newType(JSContext *cx, TypeSet *source, Type type) = 0;
 
     /*
      * For constraints attached to an object property's type set, mark the
-     * property as having its configuration changed.
+     * property as having changed somehow.
      */
     virtual void newPropertyState(JSContext *cx, TypeSet *source) {}
 
     /*
      * For constraints attached to the JSID_EMPTY type set on an object,
      * indicate a change in one of the object's dynamic property flags or other
      * state.
      */
@@ -412,27 +412,30 @@ enum MOZ_ENUM_TYPE(uint32_t) {
      * differently from a plain data property, other than making the property
      * non-writable.
      */
     TYPE_FLAG_NON_DATA_PROPERTY = 0x00008000,
 
     /* Whether the property has ever been made non-writable. */
     TYPE_FLAG_NON_WRITABLE_PROPERTY = 0x00010000,
 
+    /* Whether the property might not be constant. */
+    TYPE_FLAG_NON_CONSTANT_PROPERTY = 0x00020000,
+
     /*
      * Whether the property is definitely in a particular slot on all objects
      * from which it has not been deleted or reconfigured. For singletons
      * this may be a fixed or dynamic slot, and for other objects this will be
      * a fixed slot.
      *
      * If the property is definite, mask and shift storing the slot + 1.
      * Otherwise these bits are clear.
      */
-    TYPE_FLAG_DEFINITE_MASK       = 0xfffe0000,
-    TYPE_FLAG_DEFINITE_SHIFT      = 17
+    TYPE_FLAG_DEFINITE_MASK       = 0xfffc0000,
+    TYPE_FLAG_DEFINITE_SHIFT      = 18
 };
 typedef uint32_t TypeFlags;
 
 /* Flags and other state stored in TypeObject::flags */
 enum MOZ_ENUM_TYPE(uint32_t) {
     /* Whether this type object is associated with some allocation site. */
     OBJECT_FLAG_FROM_ALLOCATION_SITE  = 0x1,
 
@@ -553,16 +556,19 @@ class TypeSet
     }
 
     bool nonDataProperty() const {
         return flags & TYPE_FLAG_NON_DATA_PROPERTY;
     }
     bool nonWritableProperty() const {
         return flags & TYPE_FLAG_NON_WRITABLE_PROPERTY;
     }
+    bool nonConstantProperty() const {
+        return flags & TYPE_FLAG_NON_CONSTANT_PROPERTY;
+    }
     bool definiteProperty() const { return flags & TYPE_FLAG_DEFINITE_MASK; }
     unsigned definiteSlot() const {
         JS_ASSERT(definiteProperty());
         return (flags >> TYPE_FLAG_DEFINITE_SHIFT) - 1;
     }
 
     /* Join two type sets into a new set. The result should not be modified further. */
     static TemporaryTypeSet *unionSets(TypeSet *a, TypeSet *b, LifoAlloc *alloc);
@@ -671,16 +677,19 @@ class HeapTypeSet : public ConstraintTyp
 
   public:
     /* Mark this type set as representing a non-data property. */
     inline void setNonDataProperty(ExclusiveContext *cx);
     inline void setNonDataPropertyIgnoringConstraints(); // Variant for use during GC.
 
     /* Mark this type set as representing a non-writable property. */
     inline void setNonWritableProperty(ExclusiveContext *cx);
+
+    // Mark this type set as being non-constant.
+    inline void setNonConstantProperty(ExclusiveContext *cx);
 };
 
 class CompilerConstraintList;
 
 CompilerConstraintList *
 NewCompilerConstraintList(jit::TempAllocator &alloc);
 
 class TemporaryTypeSet : public TypeSet
@@ -761,16 +770,20 @@ class TemporaryTypeSet : public TypeSet
     bool maybeEmulatesUndefined();
 
     /* Get the single value which can appear in this type set, otherwise nullptr. */
     JSObject *getSingleton();
 
     /* Whether any objects in the type set needs a barrier on id. */
     bool propertyNeedsBarrier(CompilerConstraintList *constraints, jsid id);
 
+    /* Whether any objects in the type set might treat id as a constant property. */
+    bool propertyMightBeConstant(CompilerConstraintList *constraints, jsid id);
+    bool propertyIsConstant(CompilerConstraintList *constraints, jsid id, Value *valOut);
+
     /*
      * Whether this set contains all types in other, except (possibly) the
      * specified type.
      */
     bool filtersType(const TemporaryTypeSet *other, Type type) const;
 
     enum DoubleConversion {
         /* All types in the set should use eager double conversion. */
@@ -1399,16 +1412,17 @@ class HeapTypeSetKey
     void freeze(CompilerConstraintList *constraints);
     jit::MIRType knownMIRType(CompilerConstraintList *constraints);
     bool nonData(CompilerConstraintList *constraints);
     bool nonWritable(CompilerConstraintList *constraints);
     bool isOwnProperty(CompilerConstraintList *constraints);
     bool knownSubset(CompilerConstraintList *constraints, const HeapTypeSetKey &other);
     JSObject *singleton(CompilerConstraintList *constraints);
     bool needsBarrier(CompilerConstraintList *constraints);
+    bool constant(CompilerConstraintList *constraints, Value *valOut);
 };
 
 /*
  * Information about the result of the compilation of a script.  This structure
  * stored in the TypeCompartment is indexed by the RecompileInfo. This
  * indirection enables the invalidation of all constraints related to the same
  * compilation.
  */
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -1103,16 +1103,26 @@ HeapTypeSet::setNonWritableProperty(Excl
 {
     if (flags & TYPE_FLAG_NON_WRITABLE_PROPERTY)
         return;
 
     flags |= TYPE_FLAG_NON_WRITABLE_PROPERTY;
     newPropertyState(cx);
 }
 
+inline void
+HeapTypeSet::setNonConstantProperty(ExclusiveContext *cx)
+{
+    if (flags & TYPE_FLAG_NON_CONSTANT_PROPERTY)
+        return;
+
+    flags |= TYPE_FLAG_NON_CONSTANT_PROPERTY;
+    newPropertyState(cx);
+}
+
 inline unsigned
 TypeSet::getObjectCount() const
 {
     JS_ASSERT(!unknownObject());
     uint32_t count = baseObjectCount();
     if (count > SET_ARRAY_SIZE)
         return HashSetCapacity(count);
     return count;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -4051,20 +4051,20 @@ CallAddPropertyHookDense(typename Execut
 template <ExecutionMode mode>
 static bool
 UpdateShapeTypeAndValue(typename ExecutionModeTraits<mode>::ExclusiveContextType cx,
                         JSObject *obj, Shape *shape, const Value &value)
 {
     jsid id = shape->propid();
     if (shape->hasSlot()) {
         if (mode == ParallelExecution) {
-            if (!obj->nativeSetSlotIfHasType(shape, value))
+            if (!obj->nativeSetSlotIfHasType(shape, value, /* overwriting = */ false))
                 return false;
         } else {
-            obj->nativeSetSlotWithType(cx->asExclusiveContext(), shape, value);
+            obj->nativeSetSlotWithType(cx->asExclusiveContext(), shape, value, /* overwriting = */ false);
         }
     }
     if (!shape->hasSlot() || !shape->hasDefaultGetter() || !shape->hasDefaultSetter()) {
         if (mode == ParallelExecution) {
             if (!IsTypePropertyIdMarkedNonData(obj, id))
                 return false;
         } else {
             MarkTypePropertyNonData(cx->asExclusiveContext(), obj, id);
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -413,19 +413,20 @@ class JSObject : public js::ObjectImpl
                                    uint32_t slotSpan);
 
     void nativeSetSlot(uint32_t slot, const js::Value &value) {
         JS_ASSERT(isNative());
         JS_ASSERT(slot < slotSpan());
         return setSlot(slot, value);
     }
 
-    inline bool nativeSetSlotIfHasType(js::Shape *shape, const js::Value &value);
+    inline bool nativeSetSlotIfHasType(js::Shape *shape, const js::Value &value,
+                                       bool overwriting = true);
     inline void nativeSetSlotWithType(js::ExclusiveContext *cx, js::Shape *shape,
-                                      const js::Value &value);
+                                      const js::Value &value, bool overwriting = true);
 
     inline const js::Value &getReservedSlot(uint32_t index) const {
         JS_ASSERT(index < JSSLOT_FREE(getClass()));
         return getSlot(index);
     }
 
     const js::HeapSlot &getReservedSlotRef(uint32_t index) const {
         JS_ASSERT(index < JSSLOT_FREE(getClass()));
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -660,29 +660,37 @@ JSObject::hasProperty(JSContext *cx, js:
         *foundp = false;  /* initialize to shut GCC up */
         return false;
     }
     *foundp = !!prop;
     return true;
 }
 
 inline bool
-JSObject::nativeSetSlotIfHasType(js::Shape *shape, const js::Value &value)
+JSObject::nativeSetSlotIfHasType(js::Shape *shape, const js::Value &value, bool overwriting)
 {
     if (!js::types::HasTypePropertyId(this, shape->propid(), value))
         return false;
     nativeSetSlot(shape->slot(), value);
+
+    if (overwriting)
+        shape->setOverwritten();
+
     return true;
 }
 
 inline void
 JSObject::nativeSetSlotWithType(js::ExclusiveContext *cx, js::Shape *shape,
-                                const js::Value &value)
+                                const js::Value &value, bool overwriting)
 {
     nativeSetSlot(shape->slot(), value);
+
+    if (overwriting)
+        shape->setOverwritten();
+
     js::types::AddTypePropertyId(cx, this, shape->propid(), value);
 }
 
 /* static */ inline bool
 JSObject::getElement(JSContext *cx, js::HandleObject obj, js::HandleObject receiver,
                      uint32_t index, js::MutableHandleValue vp)
 {
     js::ElementIdOp op = obj->getOps()->getElement;
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -818,17 +818,23 @@ class Shape : public gc::BarrieredCell<S
      */
     enum {
         /* Property is placeholder for a non-native class. */
         NON_NATIVE      = 0x01,
 
         /* Property stored in per-object dictionary, not shared property tree. */
         IN_DICTIONARY   = 0x02,
 
-        UNUSED_BITS     = 0x3C
+        /*
+         * Slotful property was stored to more than once. This is used as a
+         * hint for type inference.
+         */
+        OVERWRITTEN     = 0x04,
+
+        UNUSED_BITS     = 0x38
     };
 
     /* Get a shape identical to this one, without parent/kids information. */
     inline Shape(const StackShape &other, uint32_t nfixed);
 
     /* Used by EmptyShape (see jsscopeinlines.h). */
     inline Shape(UnownedBaseShape *base, uint32_t nfixed);
 
@@ -877,16 +883,23 @@ class Shape : public gc::BarrieredCell<S
     }
 
     Value setterOrUndefined() const {
         return (hasSetterValue() && base()->setterObj)
                ? ObjectValue(*base()->setterObj)
                : UndefinedValue();
     }
 
+    void setOverwritten() {
+        flags |= OVERWRITTEN;
+    }
+    bool hadOverwrite() const {
+        return flags & OVERWRITTEN;
+    }
+
     void update(PropertyOp getter, StrictPropertyOp setter, uint8_t attrs);
 
     bool matches(const Shape *other) const {
         return propid_.get() == other->propid_.get() &&
                matchesParamsAfterId(other->base(), other->maybeSlot(), other->attrs, other->flags);
     }
 
     inline bool matches(const StackShape &other) const;