Bug 936156 - Use different type objects when 'new' is used on different scripts with the same prototype, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 11 Dec 2013 10:05:03 -0800
changeset 160223 52021335eb42d2e8b0cf7aa094d19ca55d96e7e6
parent 160222 47b81e1366bcc4dc9ff38bd37d457c7fc6140e65
child 160224 ddab7d071d6934009375dff8ac568b1fea4cc5ec
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersjandem
bugs936156
milestone29.0a1
Bug 936156 - Use different type objects when 'new' is used on different scripts with the same prototype, r=jandem.
js/src/jit/IonAnalysis.cpp
js/src/jit/IonAnalysis.h
js/src/jscompartment.h
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsobj.cpp
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -2108,20 +2108,22 @@ AnalyzePoppedThis(JSContext *cx, types::
 static int
 CmpInstructions(const void *a, const void *b)
 {
     return (*static_cast<MInstruction * const *>(a))->id() -
            (*static_cast<MInstruction * const *>(b))->id();
 }
 
 bool
-jit::AnalyzeNewScriptProperties(JSContext *cx, HandleFunction fun,
+jit::AnalyzeNewScriptProperties(JSContext *cx, JSFunction *fun,
                                 types::TypeObject *type, HandleObject baseobj,
                                 Vector<types::TypeNewScript::Initializer> *initializerList)
 {
+    JS_ASSERT(cx->compartment()->activeAnalysis);
+
     // When invoking 'new' on the specified script, try to find some properties
     // which will definitely be added to the created object before it has a
     // chance to escape and be accessed elsewhere.
 
     if (fun->isInterpretedLazy() && !fun->getOrCreateScript(cx))
         return false;
 
     RootedScript script(cx, fun->nonLazyScript());
@@ -2131,18 +2133,16 @@ jit::AnalyzeNewScriptProperties(JSContex
 
     Vector<PropertyName *> accessedProperties(cx);
 
     LifoAlloc alloc(types::TypeZone::TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
 
     TempAllocator temp(&alloc);
     IonContext ictx(cx, &temp);
 
-    types::AutoEnterAnalysis enter(cx);
-
     if (!cx->compartment()->ensureJitCompartmentExists(cx))
         return Method_Error;
 
     if (!script->hasBaselineScript()) {
         MethodStatus status = BaselineCompile(cx, script);
         if (status == Method_Error)
             return false;
         if (status != Method_Compiled)
--- a/js/src/jit/IonAnalysis.h
+++ b/js/src/jit/IonAnalysis.h
@@ -124,16 +124,16 @@ class LinearSum
     void dump() const;
 
   private:
     Vector<LinearTerm, 2, IonAllocPolicy> terms_;
     int32_t constant_;
 };
 
 bool
-AnalyzeNewScriptProperties(JSContext *cx, HandleFunction fun,
+AnalyzeNewScriptProperties(JSContext *cx, JSFunction *fun,
                            types::TypeObject *type, HandleObject baseobj,
                            Vector<types::TypeNewScript::Initializer> *initializerList);
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_IonAnalysis_h */
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -242,19 +242,19 @@ struct JSCompartment
     void sweepBaseShapeTable();
 
     /* Set of initial shapes in the compartment. */
     js::InitialShapeSet          initialShapes;
     void sweepInitialShapeTable();
     void markAllInitialShapeTableEntries(JSTracer *trc);
 
     /* Set of default 'new' or lazy types in the compartment. */
-    js::types::TypeObjectSet     newTypeObjects;
-    js::types::TypeObjectSet     lazyTypeObjects;
-    void sweepNewTypeObjectTable(js::types::TypeObjectSet &table);
+    js::types::TypeObjectWithNewScriptSet newTypeObjects;
+    js::types::TypeObjectWithNewScriptSet lazyTypeObjects;
+    void sweepNewTypeObjectTable(js::types::TypeObjectWithNewScriptSet &table);
 
     /*
      * Hash table of all manually call site-cloned functions from within
      * self-hosted code. Cloning according to call site provides extra
      * sensitivity for type specialization and inlining.
      */
     js::CallsiteCloneTable callsiteClones;
     void sweepCallsiteClones();
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -1401,17 +1401,17 @@ ObjectStateChange(ExclusiveContext *cxAr
             }
         } else {
             JS_ASSERT(!types->constraintList);
         }
     }
 }
 
 static void
-CheckNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun);
+CheckNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun);
 
 namespace {
 
 class ConstraintDataFreezeConfiguredProperty
 {
   public:
     TypeObjectKey *object;
 
@@ -1435,18 +1435,17 @@ class ConstraintDataFreezeConfiguredProp
         // constraints in place which will mark those properties as configured
         // should the definite properties be invalidated.
         TypeObject *type = object->isSingleObject()
                            ? object->asSingleObject()->type()
                            : object->asTypeObject();
         if (type->flags & OBJECT_FLAG_NEW_SCRIPT_REGENERATE) {
             type->flags &= ~OBJECT_FLAG_NEW_SCRIPT_REGENERATE;
             if (type->hasNewScript()) {
-                RootedFunction fun(cx, type->newScript()->fun);
-                CheckNewScriptProperties(cx, type, fun);
+                CheckNewScriptProperties(cx, type, type->newScript()->fun);
             } else {
                 JS_ASSERT(type->flags & OBJECT_FLAG_ADDENDUM_CLEARED);
                 type->flags &= ~OBJECT_FLAG_NEW_SCRIPT_REGENERATE;
             }
         }
 
         return !property.maybeTypes()->configuredProperty();
     }
@@ -3235,17 +3234,17 @@ types::AddClearDefiniteFunctionUsesInScr
 }
 
 /*
  * Either make the newScript information for type when it is constructed
  * by the specified script, or regenerate the constraints for an existing
  * newScript on the type after they were cleared by a GC.
  */
 static void
-CheckNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun)
+CheckNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun)
 {
     JS_ASSERT(cx->compartment()->activeAnalysis);
 
 #ifdef JS_ION
     if (type->unknownProperties())
         return;
 
     /* Strawman object to add properties to and watch for duplicates. */
@@ -3674,161 +3673,158 @@ JSObject::makeLazyType(JSContext *cx, Ha
         type->flags |= OBJECT_FLAG_LENGTH_OVERFLOW;
 
     obj->type_ = type;
 
     return type;
 }
 
 /* static */ inline HashNumber
-TypeObjectEntry::hash(const Lookup &lookup)
+TypeObjectWithNewScriptEntry::hash(const Lookup &lookup)
 {
     return PointerHasher<JSObject *, 3>::hash(lookup.hashProto.raw()) ^
-           PointerHasher<const Class *, 3>::hash(lookup.clasp);
+           PointerHasher<const Class *, 3>::hash(lookup.clasp) ^
+           PointerHasher<JSFunction *, 3>::hash(lookup.newFunction);
 }
 
 /* static */ inline bool
-TypeObjectEntry::match(TypeObject *key, const Lookup &lookup)
+TypeObjectWithNewScriptEntry::match(const TypeObjectWithNewScriptEntry &key, const Lookup &lookup)
 {
-    return key->proto == lookup.matchProto.raw() && key->clasp == lookup.clasp;
+    return key.object->proto == lookup.matchProto.raw() &&
+           key.object->clasp == lookup.clasp &&
+           key.newFunction == lookup.newFunction;
 }
 
 #ifdef DEBUG
 bool
 JSObject::hasNewType(const Class *clasp, TypeObject *type)
 {
-    TypeObjectSet &table = compartment()->newTypeObjects;
+    TypeObjectWithNewScriptSet &table = compartment()->newTypeObjects;
 
     if (!table.initialized())
         return false;
 
-    TypeObjectSet::Ptr p = table.lookup(TypeObjectSet::Lookup(clasp, this));
-    return p && *p == type;
+    TypeObjectWithNewScriptSet::Ptr p = table.lookup(TypeObjectWithNewScriptSet::Lookup(clasp, this, nullptr));
+    return p && p->object == type;
 }
 #endif /* DEBUG */
 
 /* static */ bool
 JSObject::setNewTypeUnknown(JSContext *cx, const Class *clasp, HandleObject obj)
 {
     if (!obj->setFlag(cx, js::BaseShape::NEW_TYPE_UNKNOWN))
         return false;
 
     /*
      * If the object already has a new type, mark that type as unknown. It will
      * not have the SETS_MARKED_UNKNOWN bit set, so may require a type set
      * crawl if prototypes of the object change dynamically in the future.
      */
-    TypeObjectSet &table = cx->compartment()->newTypeObjects;
+    TypeObjectWithNewScriptSet &table = cx->compartment()->newTypeObjects;
     if (table.initialized()) {
-        if (TypeObjectSet::Ptr p = table.lookup(TypeObjectSet::Lookup(clasp, obj.get())))
-            MarkTypeObjectUnknownProperties(cx, *p);
+        if (TypeObjectWithNewScriptSet::Ptr p = table.lookup(TypeObjectWithNewScriptSet::Lookup(clasp, obj.get(), nullptr)))
+            MarkTypeObjectUnknownProperties(cx, p->object);
     }
 
     return true;
 }
 
 #ifdef JSGC_GENERATIONAL
 /*
  * This class is used to add a post barrier on the newTypeObjects set, as the
  * key is calculated from a prototype object which may be moved by generational
  * GC.
  */
 class NewTypeObjectsSetRef : public BufferableRef
 {
-    TypeObjectSet *set;
-    TypeObject    *typeObject;
-    JSObject      *proto;
+    TypeObjectWithNewScriptSet *set;
+    const Class *clasp;
+    JSObject *proto;
+    JSFunction *newFunction;
 
   public:
-    NewTypeObjectsSetRef(TypeObjectSet *s, TypeObject *t, JSObject *p)
-      : set(s), typeObject(t), proto(p) {}
+    NewTypeObjectsSetRef(TypeObjectWithNewScriptSet *s, const Class *clasp, JSObject *proto, JSFunction *newFunction)
+        : set(s), clasp(clasp), proto(proto), newFunction(newFunction)
+    {}
 
     void mark(JSTracer *trc) {
-        const Class *clasp = typeObject->clasp;
         JSObject *prior = proto;
         JS_SET_TRACING_LOCATION(trc, (void*)&*prior);
         Mark(trc, &proto, "newTypeObjects set prototype");
         if (prior == proto)
             return;
 
-        TypeObjectSet::Ptr p = set->lookup(TypeObjectSet::Lookup(clasp, prior, proto));
+        TypeObjectWithNewScriptSet::Ptr p = set->lookup(TypeObjectWithNewScriptSet::Lookup(clasp, prior, proto, newFunction));
         JS_ASSERT(p);  // newTypeObjects set must still contain original entry.
 
-        set->rekeyAs(TypeObjectSet::Lookup(clasp, prior, proto),
-                     TypeObjectSet::Lookup(clasp, proto), typeObject);
+        set->rekeyAs(TypeObjectWithNewScriptSet::Lookup(clasp, prior, proto, newFunction),
+                     TypeObjectWithNewScriptSet::Lookup(clasp, proto, newFunction), *p);
     }
 };
 #endif
 
 TypeObject *
-ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto_, JSFunction *fun_)
+ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto, JSFunction *fun)
 {
-    JS_ASSERT_IF(fun_, proto_.isObject());
-    JS_ASSERT_IF(proto_.isObject(), isInsideCurrentCompartment(proto_.toObject()));
-
-    TypeObjectSet &newTypeObjects = compartment_->newTypeObjects;
+    JS_ASSERT_IF(fun, proto.isObject());
+    JS_ASSERT_IF(proto.isObject(), isInsideCurrentCompartment(proto.toObject()));
+
+    TypeObjectWithNewScriptSet &newTypeObjects = compartment()->newTypeObjects;
 
     if (!newTypeObjects.initialized() && !newTypeObjects.init())
         return nullptr;
 
-    DependentAddPtr<TypeObjectSet> p(this, newTypeObjects,
-                                     TypeObjectSet::Lookup(clasp, proto_));
-    SkipRoot skipHash(this, &p); /* Prevent the hash from being poisoned. */
+    // Canonicalize new functions to use the original one associated with its script.
+    if (fun) {
+        if (fun->hasScript())
+            fun = fun->nonLazyScript()->function();
+        else if (fun->isInterpretedLazy())
+            fun = fun->lazyScript()->function();
+        else
+            fun = nullptr;
+    }
+
+    TypeObjectWithNewScriptSet::AddPtr p =
+        newTypeObjects.lookupForAdd(TypeObjectWithNewScriptSet::Lookup(clasp, proto, fun));
     if (p) {
-        TypeObject *type = *p;
+        TypeObject *type = p->object;
         JS_ASSERT(type->clasp == clasp);
-        JS_ASSERT(type->proto.get() == proto_.raw());
-
-        /*
-         * If set, the type's newScript indicates the script used to create
-         * all objects in existence which have this type. If there are objects
-         * in existence which are not created by calling 'new' on newScript,
-         * we must clear the new script information from the type and will not
-         * be able to assume any definite properties for instances of the type.
-         * This case is rare, but can happen if, for example, two scripted
-         * functions have the same value for their 'prototype' property, or if
-         * Object.create is called with a prototype object that is also the
-         * 'prototype' property of some scripted function.
-         */
-        if (type->hasNewScript() && type->newScript()->fun != fun_)
-            type->clearAddendum(this);
-
+        JS_ASSERT(type->proto.get() == proto.raw());
+        JS_ASSERT_IF(type->hasNewScript(), type->newScript()->fun == fun);
         return type;
     }
 
-    Rooted<TaggedProto> proto(this, proto_);
-    RootedFunction fun(this, fun_);
+    AutoEnterAnalysis enter(this);
 
     if (proto.isObject() && !proto.toObject()->setDelegate(this))
         return nullptr;
 
     bool markUnknown =
         proto.isObject()
         ? proto.toObject()->lastProperty()->hasObjectFlag(BaseShape::NEW_TYPE_UNKNOWN)
         : true;
 
-    RootedTypeObject type(this, compartment_->types.newTypeObject(this, clasp, proto, markUnknown));
+    Rooted<TaggedProto> protoRoot(this, proto);
+    TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot, markUnknown);
     if (!type)
         return nullptr;
 
-    if (!p.add(newTypeObjects, TypeObjectSet::Lookup(clasp, proto), type.get()))
+    if (!newTypeObjects.add(p, TypeObjectWithNewScriptEntry(type, fun)))
         return nullptr;
 
 #ifdef JSGC_GENERATIONAL
     if (proto.isObject() && hasNursery() && nursery().isInside(proto.toObject())) {
         asJSContext()->runtime()->gcStoreBuffer.putGeneric(
-            NewTypeObjectsSetRef(&newTypeObjects, type.get(), proto.toObject()));
+            NewTypeObjectsSetRef(&newTypeObjects, clasp, proto.toObject(), fun));
     }
 #endif
 
     if (!typeInferenceEnabled())
         return type;
 
-    AutoEnterAnalysis enter(this);
-
     if (proto.isObject()) {
         RootedObject obj(this, proto.toObject());
 
         if (fun)
             CheckNewScriptProperties(asJSContext(), type, fun);
 
         /*
          * Some builtin objects have slotful native properties baked in at
@@ -3873,35 +3869,35 @@ ExclusiveContext::getNewType(const Class
 
 TypeObject *
 ExclusiveContext::getLazyType(const Class *clasp, TaggedProto proto)
 {
     JS_ASSERT_IF(proto.isObject(), compartment() == proto.toObject()->compartment());
 
     AutoEnterAnalysis enter(this);
 
-    TypeObjectSet &table = compartment()->lazyTypeObjects;
+    TypeObjectWithNewScriptSet &table = compartment()->lazyTypeObjects;
 
     if (!table.initialized() && !table.init())
         return nullptr;
 
-    DependentAddPtr<TypeObjectSet> p(this, table, TypeObjectSet::Lookup(clasp, proto));
+    TypeObjectWithNewScriptSet::AddPtr p = table.lookupForAdd(TypeObjectWithNewScriptSet::Lookup(clasp, proto, nullptr));
     if (p) {
-        TypeObject *type = *p;
+        TypeObject *type = p->object;
         JS_ASSERT(type->lazy());
 
         return type;
     }
 
     Rooted<TaggedProto> protoRoot(this, proto);
     TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot, false);
     if (!type)
         return nullptr;
 
-    if (!p.add(table, TypeObjectSet::Lookup(clasp, protoRoot), type))
+    if (!table.add(p, TypeObjectWithNewScriptEntry(type, nullptr)))
         return nullptr;
 
     type->singleton = (JSObject *) TypeObject::LAZY_SINGLETON;
 
     return type;
 }
 
 /////////////////////////////////////////////////////////////////////
@@ -4167,29 +4163,35 @@ TypeCompartment::clearCompilerOutputs(Fr
     if (pendingRecompiles) {
         JS_ASSERT(pendingRecompiles->length() == 0);
         fop->delete_(pendingRecompiles);
         pendingRecompiles = nullptr;
     }
 }
 
 void
-JSCompartment::sweepNewTypeObjectTable(TypeObjectSet &table)
+JSCompartment::sweepNewTypeObjectTable(TypeObjectWithNewScriptSet &table)
 {
     gcstats::AutoPhase ap(runtimeFromMainThread()->gcStats,
                           gcstats::PHASE_SWEEP_TABLES_TYPE_OBJECT);
 
     JS_ASSERT(zone()->isGCSweeping());
     if (table.initialized()) {
-        for (TypeObjectSet::Enum e(table); !e.empty(); e.popFront()) {
-            TypeObject *type = e.front();
-            if (IsTypeObjectAboutToBeFinalized(&type))
+        for (TypeObjectWithNewScriptSet::Enum e(table); !e.empty(); e.popFront()) {
+            TypeObjectWithNewScriptEntry entry = e.front();
+            if (IsTypeObjectAboutToBeFinalized(entry.object.unsafeGet())) {
+                e.removeFront();
+            } else if (entry.newFunction && IsObjectAboutToBeFinalized(&entry.newFunction)) {
                 e.removeFront();
-            else if (type != e.front())
-                e.rekeyFront(TypeObjectSet::Lookup(type->clasp, type->proto.get()), type);
+            } else if (entry.object != e.front().object) {
+                TypeObjectWithNewScriptSet::Lookup lookup(entry.object->clasp,
+                                                          entry.object->proto.get(),
+                                                          entry.newFunction);
+                e.rekeyFront(lookup, entry);
+            }
         }
     }
 }
 
 TypeCompartment::~TypeCompartment()
 {
     js_free(pendingArray);
     js_delete(arrayTypeTable);
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -1074,43 +1074,61 @@ struct TypeObject : gc::BarrieredCell<Ty
     inline void setBasePropertyCount(uint32_t count);
 
     static void staticAsserts() {
         JS_STATIC_ASSERT(offsetof(TypeObject, proto) == offsetof(js::shadow::TypeObject, proto));
     }
 };
 
 /*
- * Entries for the per-compartment set of type objects which are the default
- * 'new' or the lazy types of some prototype.
+ * Entries for the per-compartment set of type objects which are 'new' types to
+ * use for some prototype and constructed with an optional script. This also
+ * includes entries for the set of lazy type objects in the compartment, which
+ * use a null script (though there are only a few of these per compartment).
  */
-struct TypeObjectEntry : DefaultHasher<ReadBarriered<TypeObject> >
+struct TypeObjectWithNewScriptEntry
 {
+    ReadBarriered<TypeObject> object;
+
+    // Note: This pointer is only used for equality and does not need a read barrier.
+    JSFunction *newFunction;
+
+    TypeObjectWithNewScriptEntry(TypeObject *object, JSFunction *newFunction)
+      : object(object), newFunction(newFunction)
+    {}
+
     struct Lookup {
         const Class *clasp;
         TaggedProto hashProto;
         TaggedProto matchProto;
+        JSFunction *newFunction;
 
-        Lookup(const Class *clasp, TaggedProto proto)
-          : clasp(clasp), hashProto(proto), matchProto(proto) {}
+        Lookup(const Class *clasp, TaggedProto proto, JSFunction *newFunction)
+          : clasp(clasp), hashProto(proto), matchProto(proto), newFunction(newFunction)
+        {}
 
 #ifdef JSGC_GENERATIONAL
         /*
          * For use by generational post barriers only.  Look up an entry whose
          * proto has been moved, but was hashed with the original value.
          */
-        Lookup(const Class *clasp, TaggedProto hashProto, TaggedProto matchProto)
-          : clasp(clasp), hashProto(hashProto), matchProto(matchProto) {}
+        Lookup(const Class *clasp, TaggedProto hashProto, TaggedProto matchProto, JSFunction *newFunction)
+            : clasp(clasp), hashProto(hashProto), matchProto(matchProto), newFunction(newFunction)
+        {}
 #endif
+
     };
 
     static inline HashNumber hash(const Lookup &lookup);
-    static inline bool match(TypeObject *key, const Lookup &lookup);
+    static inline bool match(const TypeObjectWithNewScriptEntry &key, const Lookup &lookup);
+    static void rekey(TypeObjectWithNewScriptEntry &k, const TypeObjectWithNewScriptEntry& newKey) { k = newKey; }
 };
-typedef HashSet<ReadBarriered<TypeObject>, TypeObjectEntry, SystemAllocPolicy> TypeObjectSet;
+typedef HashSet<TypeObjectWithNewScriptEntry,
+                TypeObjectWithNewScriptEntry,
+                SystemAllocPolicy> TypeObjectWithNewScriptSet;
 
 /* Whether to use a new type object when calling 'new' at script/pc. */
 bool
 UseNewType(JSContext *cx, JSScript *script, jsbytecode *pc);
 
 bool
 UseNewTypeForClone(JSFunction *fun);
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1443,17 +1443,16 @@ js::NewObjectWithClassProtoCommon(Exclus
 /*
  * Create a plain object with the specified type. This bypasses getNewType to
  * avoid losing creation site information for objects made by scripted 'new'.
  */
 JSObject *
 js::NewObjectWithType(JSContext *cx, HandleTypeObject type, JSObject *parent, gc::AllocKind allocKind,
                       NewObjectKind newKind)
 {
-    JS_ASSERT(type->proto->hasNewType(&JSObject::class_, type));
     JS_ASSERT(parent);
 
     JS_ASSERT(allocKind <= gc::FINALIZE_OBJECT_LAST);
     if (CanBeFinalizedInBackground(allocKind, &JSObject::class_))
         allocKind = GetBackgroundAllocKind(allocKind);
 
     NewObjectCache &cache = cx->runtime()->newObjectCache;