/* -*- 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/. */ #ifndef vm_ObjectGroup_h #define vm_ObjectGroup_h #include "jsbytecode.h" #include "jsfriendapi.h" #include "ds/IdValuePair.h" #include "gc/Barrier.h" #include "js/CharacterEncoding.h" #include "js/GCHashTable.h" #include "vm/TaggedProto.h" #include "vm/TypeInference.h" namespace js { class TypeDescr; class UnboxedLayout; class PreliminaryObjectArrayWithTemplate; class TypeNewScript; class HeapTypeSet; class AutoClearTypeInferenceStateOnOOM; class CompilerConstraintList; namespace gc { void MergeCompartments(JSCompartment* source, JSCompartment* target); } // namespace gc /* * The NewObjectKind allows an allocation site to specify the type properties * and lifetime requirements that must be fixed at allocation time. */ enum NewObjectKind { /* This is the default. Most objects are generic. */ GenericObject, /* * Singleton objects are treated specially by the type system. This flag * ensures that the new object is automatically set up correctly as a * singleton and is allocated in the tenured heap. */ SingletonObject, /* * CrossCompartmentWrappers use the common Proxy class, but are allowed * to have nursery lifetime. */ NurseryAllocatedProxy, /* * Objects which will not benefit from being allocated in the nursery * (e.g. because they are known to have a long lifetime) may be allocated * with this kind to place them immediately into the tenured generation. */ TenuredObject }; /* * Lazy object groups overview. * * Object groups which represent at most one JS object are constructed lazily. * These include groups for native functions, standard classes, scripted * functions defined at the top level of global/eval scripts, objects which * dynamically become the prototype of some other object, and in some other * cases. Typical web workloads often create many windows (and many copies of * standard natives) and many scripts, with comparatively few non-singleton * groups. * * We can recover the type information for the object from examining it, * so don't normally track the possible types of its properties as it is * updated. Property type sets for the object are only constructed when an * analyzed script attaches constraints to it: the script is querying that * property off the object or another which delegates to it, and the analysis * information is sensitive to changes in the property's type. Future changes * to the property (whether those uncovered by analysis or those occurring * in the VM) will treat these properties like those of any other object group. */ /* Type information about an object accessed by a script. */ class ObjectGroup : public gc::TenuredCell { friend void gc::MergeCompartments(JSCompartment* source, JSCompartment* target); /* Class shared by objects in this group. */ const Class* clasp_; /* Prototype shared by objects in this group. */ GCPtr proto_; /* Compartment shared by objects in this group. */ JSCompartment* compartment_; public: const Class* clasp() const { return clasp_; } void setClasp(const Class* clasp) { MOZ_ASSERT(JS::StringIsASCII(clasp->name)); clasp_ = clasp; } bool hasDynamicPrototype() const { return proto_.isDynamic(); } const GCPtr& proto() const { return proto_; } GCPtr& proto() { return proto_; } void setProto(TaggedProto proto); void setProtoUnchecked(TaggedProto proto); bool singleton() const { return flagsDontCheckGeneration() & OBJECT_FLAG_SINGLETON; } bool lazy() const { bool res = flagsDontCheckGeneration() & OBJECT_FLAG_LAZY_SINGLETON; MOZ_ASSERT_IF(res, singleton()); return res; } JSCompartment* compartment() const { return compartment_; } JSCompartment* maybeCompartment() const { return compartment(); } private: /* Flags for this group. */ ObjectGroupFlags flags_; // Kinds of addendums which can be attached to ObjectGroups. enum AddendumKind { Addendum_None, // When used by interpreted function, the addendum stores the // canonical JSFunction object. Addendum_InterpretedFunction, // When used by the 'new' group when constructing an interpreted // function, the addendum stores a TypeNewScript. Addendum_NewScript, // For some plain objects, the addendum stores a PreliminaryObjectArrayWithTemplate. Addendum_PreliminaryObjects, // When objects in this group have an unboxed representation, the // addendum stores an UnboxedLayout (which might have a TypeNewScript // as well, if the group is also constructed using 'new'). Addendum_UnboxedLayout, // If this group is used by objects that have been converted from an // unboxed representation and/or have the same allocation kind as such // objects, the addendum points to that unboxed group. Addendum_OriginalUnboxedGroup, // When used by typed objects, the addendum stores a TypeDescr. Addendum_TypeDescr }; // If non-null, holds additional information about this object, whose // format is indicated by the object's addendum kind. void* addendum_; void setAddendum(AddendumKind kind, void* addendum, bool writeBarrier = true); AddendumKind addendumKind() const { return (AddendumKind) ((flags_ & OBJECT_FLAG_ADDENDUM_MASK) >> OBJECT_FLAG_ADDENDUM_SHIFT); } TypeNewScript* newScriptDontCheckGeneration() const { if (addendumKind() == Addendum_NewScript) return reinterpret_cast(addendum_); return nullptr; } TypeNewScript* anyNewScript(); void detachNewScript(bool writeBarrier, ObjectGroup* replacement); ObjectGroupFlags flagsDontCheckGeneration() const { return flags_; } public: inline ObjectGroupFlags flags(); inline void addFlags(ObjectGroupFlags flags); inline void clearFlags(ObjectGroupFlags flags); inline TypeNewScript* newScript(); void setNewScript(TypeNewScript* newScript) { setAddendum(Addendum_NewScript, newScript); } inline PreliminaryObjectArrayWithTemplate* maybePreliminaryObjects(); PreliminaryObjectArrayWithTemplate* maybePreliminaryObjectsDontCheckGeneration() { if (addendumKind() == Addendum_PreliminaryObjects) return reinterpret_cast(addendum_); return nullptr; } void setPreliminaryObjects(PreliminaryObjectArrayWithTemplate* preliminaryObjects) { setAddendum(Addendum_PreliminaryObjects, preliminaryObjects); } void detachPreliminaryObjects() { MOZ_ASSERT(maybePreliminaryObjectsDontCheckGeneration()); setAddendum(Addendum_None, nullptr); } bool hasUnanalyzedPreliminaryObjects() { return (newScriptDontCheckGeneration() && !newScriptDontCheckGeneration()->analyzed()) || maybePreliminaryObjectsDontCheckGeneration(); } inline UnboxedLayout* maybeUnboxedLayout(); inline UnboxedLayout& unboxedLayout(); UnboxedLayout* maybeUnboxedLayoutDontCheckGeneration() const { if (addendumKind() == Addendum_UnboxedLayout) return reinterpret_cast(addendum_); return nullptr; } UnboxedLayout& unboxedLayoutDontCheckGeneration() const { MOZ_ASSERT(addendumKind() == Addendum_UnboxedLayout); return *maybeUnboxedLayoutDontCheckGeneration(); } void setUnboxedLayout(UnboxedLayout* layout) { setAddendum(Addendum_UnboxedLayout, layout); } ObjectGroup* maybeOriginalUnboxedGroup() const { if (addendumKind() == Addendum_OriginalUnboxedGroup) return reinterpret_cast(addendum_); return nullptr; } void setOriginalUnboxedGroup(ObjectGroup* group) { setAddendum(Addendum_OriginalUnboxedGroup, group); } TypeDescr* maybeTypeDescr() { // Note: there is no need to sweep when accessing the type descriptor // of an object, as it is strongly held and immutable. if (addendumKind() == Addendum_TypeDescr) return reinterpret_cast(addendum_); return nullptr; } TypeDescr& typeDescr() { MOZ_ASSERT(addendumKind() == Addendum_TypeDescr); return *maybeTypeDescr(); } void setTypeDescr(TypeDescr* descr) { setAddendum(Addendum_TypeDescr, descr); } JSFunction* maybeInterpretedFunction() { // Note: as with type descriptors, there is no need to sweep when // accessing the interpreted function associated with an object. if (addendumKind() == Addendum_InterpretedFunction) return reinterpret_cast(addendum_); return nullptr; } void setInterpretedFunction(JSFunction* fun) { setAddendum(Addendum_InterpretedFunction, fun); } class Property { public: // Identifier for this property, JSID_VOID for the aggregate integer // index property, or JSID_EMPTY for properties holding constraints // listening to changes in the group's state. GCPtrId id; // Possible own types for this property. HeapTypeSet types; explicit Property(jsid id) : id(id) {} Property(const Property& o) : id(o.id.get()), types(o.types) {} static uint32_t keyBits(jsid id) { return uint32_t(JSID_BITS(id)); } static jsid getKey(Property* p) { return p->id; } }; private: /* * Properties of this object. * * The type sets in the properties of a group describe the possible values * that can be read out of that property in actual JS objects. In native * objects, property types account for plain data properties (those with a * slot and no getter or setter hook) and dense elements. In typed objects * and unboxed objects, property types account for object and value * properties and elements in the object, and expando properties in unboxed * objects. * * For accesses on these properties, the correspondence is as follows: * * 1. If the group has unknownProperties(), the possible properties and * value types for associated JSObjects are unknown. * * 2. Otherwise, for any |obj| in |group|, and any |id| which is a property * in |obj|, before obj->getProperty(id) the property in |group| for * |id| must reflect the result of the getProperty. * * There are several exceptions to this: * * 1. For properties of global JS objects which are undefined at the point * where the property was (lazily) generated, the property type set will * remain empty, and the 'undefined' type will only be added after a * subsequent assignment or deletion. After these properties have been * assigned a defined value, the only way they can become undefined * again is after such an assign or deletion. * * 2. Array lengths are special cased by the compiler and VM and are not * reflected in property types. * * 3. In typed objects (but not unboxed objects), the initial values of * properties (null pointers and undefined values) are not reflected in * the property types. These values are always possible when reading the * property. * * We establish these by using write barriers on calls to setProperty and * defineProperty which are on native properties, and on any jitcode which * might update the property with a new type. */ Property** propertySet; public: inline ObjectGroup(const Class* clasp, TaggedProto proto, JSCompartment* comp, ObjectGroupFlags initialFlags); inline bool hasAnyFlags(ObjectGroupFlags flags); inline bool hasAllFlags(ObjectGroupFlags flags); bool hasAllFlagsDontCheckGeneration(ObjectGroupFlags flags) { MOZ_ASSERT((flags & OBJECT_FLAG_DYNAMIC_MASK) == flags); return (this->flagsDontCheckGeneration() & flags) == flags; } inline bool unknownProperties(); bool unknownPropertiesDontCheckGeneration() { MOZ_ASSERT_IF(flagsDontCheckGeneration() & OBJECT_FLAG_UNKNOWN_PROPERTIES, hasAllFlagsDontCheckGeneration(OBJECT_FLAG_DYNAMIC_MASK)); return !!(flagsDontCheckGeneration() & OBJECT_FLAG_UNKNOWN_PROPERTIES); } inline bool shouldPreTenure(); gc::InitialHeap initialHeap(CompilerConstraintList* constraints); inline bool canPreTenure(); inline bool fromAllocationSite(); inline void setShouldPreTenure(ExclusiveContext* cx); /* * Get or create a property of this object. Only call this for properties which * a script accesses explicitly. */ inline HeapTypeSet* getProperty(ExclusiveContext* cx, JSObject* obj, jsid id); /* Get a property only if it already exists. */ inline HeapTypeSet* maybeGetProperty(jsid id); /* * Iterate through the group's properties. getPropertyCount overapproximates * in the hash case (see SET_ARRAY_SIZE in TypeInference-inl.h), and * getProperty may return nullptr. */ inline unsigned getPropertyCount(); inline Property* getProperty(unsigned i); /* Helpers */ void updateNewPropertyTypes(ExclusiveContext* cx, JSObject* obj, jsid id, HeapTypeSet* types); void addDefiniteProperties(ExclusiveContext* cx, Shape* shape); bool matchDefiniteProperties(HandleObject obj); void markPropertyNonData(ExclusiveContext* cx, JSObject* obj, jsid id); void markPropertyNonWritable(ExclusiveContext* cx, JSObject* obj, jsid id); void markStateChange(ExclusiveContext* cx); void setFlags(ExclusiveContext* cx, ObjectGroupFlags flags); void markUnknown(ExclusiveContext* cx); void maybeClearNewScriptOnOOM(); void clearNewScript(ExclusiveContext* cx, ObjectGroup* replacement = nullptr); void print(); inline void clearProperties(); void traceChildren(JSTracer* trc); inline bool needsSweep(); inline void maybeSweep(AutoClearTypeInferenceStateOnOOM* oom); private: void sweep(AutoClearTypeInferenceStateOnOOM* oom); uint32_t generation() { return (flags_ & OBJECT_FLAG_GENERATION_MASK) >> OBJECT_FLAG_GENERATION_SHIFT; } public: void setGeneration(uint32_t generation) { MOZ_ASSERT(generation <= (OBJECT_FLAG_GENERATION_MASK >> OBJECT_FLAG_GENERATION_SHIFT)); flags_ &= ~OBJECT_FLAG_GENERATION_MASK; flags_ |= generation << OBJECT_FLAG_GENERATION_SHIFT; } size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; void finalize(FreeOp* fop); static const JS::TraceKind TraceKind = JS::TraceKind::ObjectGroup; static inline uint32_t offsetOfClasp() { return offsetof(ObjectGroup, clasp_); } static inline uint32_t offsetOfProto() { return offsetof(ObjectGroup, proto_); } static inline uint32_t offsetOfAddendum() { return offsetof(ObjectGroup, addendum_); } static inline uint32_t offsetOfFlags() { return offsetof(ObjectGroup, flags_); } const ObjectGroupFlags* addressOfFlags() const { return &flags_; } // Get the bit pattern stored in an object's addendum when it has an // original unboxed group. static inline int32_t addendumOriginalUnboxedGroupValue() { return Addendum_OriginalUnboxedGroup << OBJECT_FLAG_ADDENDUM_SHIFT; } inline uint32_t basePropertyCount(); private: inline void setBasePropertyCount(uint32_t count); static void staticAsserts() { JS_STATIC_ASSERT(offsetof(ObjectGroup, proto_) == offsetof(js::shadow::ObjectGroup, proto)); } public: // Whether to make a deep cloned singleton when cloning fun. static bool useSingletonForClone(JSFunction* fun); // Whether to make a singleton when calling 'new' at script/pc. static bool useSingletonForNewObject(JSContext* cx, JSScript* script, jsbytecode* pc); // Whether to make a singleton object at an allocation site. static bool useSingletonForAllocationSite(JSScript* script, jsbytecode* pc, JSProtoKey key); static bool useSingletonForAllocationSite(JSScript* script, jsbytecode* pc, const Class* clasp); // Static accessors for ObjectGroupCompartment NewTable. static ObjectGroup* defaultNewGroup(ExclusiveContext* cx, const Class* clasp, TaggedProto proto, JSObject* associated = nullptr); static ObjectGroup* lazySingletonGroup(ExclusiveContext* cx, const Class* clasp, TaggedProto proto); static void setDefaultNewGroupUnknown(JSContext* cx, const js::Class* clasp, JS::HandleObject obj); #ifdef DEBUG static bool hasDefaultNewGroup(JSObject* proto, const Class* clasp, ObjectGroup* group); #endif // Static accessors for ObjectGroupCompartment ArrayObjectTable and PlainObjectTable. enum class NewArrayKind { Normal, // Specialize array group based on its element type. CopyOnWrite, // Make an array with copy-on-write elements. UnknownIndex // Make an array with an unknown element type. }; // Create an ArrayObject or UnboxedArrayObject with the specified elements // and a group specialized for the elements. static JSObject* newArrayObject(ExclusiveContext* cx, const Value* vp, size_t length, NewObjectKind newKind, NewArrayKind arrayKind = NewArrayKind::Normal); // Create a PlainObject or UnboxedPlainObject with the specified properties // and a group specialized for those properties. static JSObject* newPlainObject(ExclusiveContext* cx, IdValuePair* properties, size_t nproperties, NewObjectKind newKind); // Static accessors for ObjectGroupCompartment AllocationSiteTable. // Get a non-singleton group to use for objects created at the specified // allocation site. static ObjectGroup* allocationSiteGroup(JSContext* cx, JSScript* script, jsbytecode* pc, JSProtoKey key, HandleObject proto = nullptr); // Get a non-singleton group to use for objects created in a JSNative call. static ObjectGroup* callingAllocationSiteGroup(JSContext* cx, JSProtoKey key, HandleObject proto = nullptr); // Set the group or singleton-ness of an object created for an allocation site. static bool setAllocationSiteObjectGroup(JSContext* cx, HandleScript script, jsbytecode* pc, HandleObject obj, bool singleton); static ArrayObject* getOrFixupCopyOnWriteObject(JSContext* cx, HandleScript script, jsbytecode* pc); static ArrayObject* getCopyOnWriteObject(JSScript* script, jsbytecode* pc); // Returns false if not found. static bool findAllocationSite(JSContext* cx, ObjectGroup* group, JSScript** script, uint32_t* offset); private: static ObjectGroup* defaultNewGroup(JSContext* cx, JSProtoKey key); }; // Structure used to manage the groups in a compartment. class ObjectGroupCompartment { friend class ObjectGroup; class NewTable; // Set of default 'new' or lazy groups in the compartment. NewTable* defaultNewTable; NewTable* lazyTable; struct ArrayObjectKey; using ArrayObjectTable = js::GCRekeyableHashMap; struct PlainObjectKey; struct PlainObjectEntry; struct PlainObjectTableSweepPolicy { static bool needsSweep(PlainObjectKey* key, PlainObjectEntry* entry); }; using PlainObjectTable = JS::GCHashMap; // Tables for managing groups common to the contents of large script // singleton objects and JSON objects. These are vanilla ArrayObjects and // PlainObjects, so we distinguish the groups of different ones by looking // at the types of their properties. // // All singleton/JSON arrays which have the same prototype, are homogenous // and of the same element type will share a group. All singleton/JSON // objects which have the same shape and property types will also share a // group. We don't try to collate arrays or objects with type mismatches. ArrayObjectTable* arrayObjectTable; PlainObjectTable* plainObjectTable; struct AllocationSiteKey; class AllocationSiteTable; // Table for referencing types of objects keyed to an allocation site. AllocationSiteTable* allocationSiteTable; public: struct NewEntry; ObjectGroupCompartment(); ~ObjectGroupCompartment(); void replaceAllocationSiteGroup(JSScript* script, jsbytecode* pc, JSProtoKey kind, ObjectGroup* group); void removeDefaultNewGroup(const Class* clasp, TaggedProto proto, JSObject* associated); void replaceDefaultNewGroup(const Class* clasp, TaggedProto proto, JSObject* associated, ObjectGroup* group); static ObjectGroup* makeGroup(ExclusiveContext* cx, const Class* clasp, Handle proto, ObjectGroupFlags initialFlags = 0); void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t* allocationSiteTables, size_t* arrayGroupTables, size_t* plainObjectGroupTables, size_t* compartmentTables); void clearTables(); void sweep(FreeOp* fop); #ifdef JSGC_HASH_TABLE_CHECKS void checkTablesAfterMovingGC() { checkNewTableAfterMovingGC(defaultNewTable); checkNewTableAfterMovingGC(lazyTable); } #endif void fixupTablesAfterMovingGC() { fixupNewTableAfterMovingGC(defaultNewTable); fixupNewTableAfterMovingGC(lazyTable); } private: #ifdef JSGC_HASH_TABLE_CHECKS void checkNewTableAfterMovingGC(NewTable* table); #endif void fixupNewTableAfterMovingGC(NewTable* table); }; PlainObject* NewPlainObjectWithProperties(ExclusiveContext* cx, IdValuePair* properties, size_t nproperties, NewObjectKind newKind); bool CombineArrayElementTypes(ExclusiveContext* cx, JSObject* newObj, const Value* compare, size_t ncompare); bool CombinePlainObjectPropertyTypes(ExclusiveContext* cx, JSObject* newObj, const Value* compare, size_t ncompare); } // namespace js #endif /* vm_ObjectGroup_h */