/* -*- 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/. */ /* Definitions related to javascript type inference. */ #ifndef vm_TypeInference_h #define vm_TypeInference_h #include "mozilla/MemoryReporting.h" #include "jsalloc.h" #include "jsfriendapi.h" #include "jstypes.h" #include "ds/IdValuePair.h" #include "ds/LifoAlloc.h" #include "gc/Barrier.h" #include "gc/Marking.h" #include "jit/IonTypes.h" #include "js/UbiNode.h" #include "js/Utility.h" #include "js/Vector.h" #include "vm/TaggedProto.h" namespace js { namespace jit { struct IonScript; class JitAllocPolicy; class TempAllocator; } // namespace jit struct TypeZone; class TypeConstraint; class TypeNewScript; class CompilerConstraintList; class HeapTypeSetKey; /* * Type inference memory management overview. * * Type information about the values observed within scripts and about the * contents of the heap is accumulated as the program executes. Compilation * accumulates constraints relating type information on the heap with the * compilations that should be invalidated when those types change. Type * information and constraints are allocated in the zone's typeLifoAlloc, * and on GC all data referring to live things is copied into a new allocator. * Thus, type set and constraints only hold weak references. */ /* Flags and other state stored in TypeSet::flags */ enum : uint32_t { TYPE_FLAG_UNDEFINED = 0x1, TYPE_FLAG_NULL = 0x2, TYPE_FLAG_BOOLEAN = 0x4, TYPE_FLAG_INT32 = 0x8, TYPE_FLAG_DOUBLE = 0x10, TYPE_FLAG_STRING = 0x20, TYPE_FLAG_SYMBOL = 0x40, TYPE_FLAG_LAZYARGS = 0x80, TYPE_FLAG_ANYOBJECT = 0x100, /* Mask containing all primitives */ TYPE_FLAG_PRIMITIVE = TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL | TYPE_FLAG_BOOLEAN | TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_STRING | TYPE_FLAG_SYMBOL, /* Mask/shift for the number of objects in objectSet */ TYPE_FLAG_OBJECT_COUNT_MASK = 0x3e00, TYPE_FLAG_OBJECT_COUNT_SHIFT = 9, TYPE_FLAG_OBJECT_COUNT_LIMIT = 7, TYPE_FLAG_DOMOBJECT_COUNT_LIMIT = TYPE_FLAG_OBJECT_COUNT_MASK >> TYPE_FLAG_OBJECT_COUNT_SHIFT, /* Whether the contents of this type set are totally unknown. */ TYPE_FLAG_UNKNOWN = 0x00004000, /* Mask of normal type flags on a type set. */ TYPE_FLAG_BASE_MASK = 0x000041ff, /* Additional flags for HeapTypeSet sets. */ /* * Whether the property has ever been deleted or reconfigured to behave * 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 = 0xfffc0000, TYPE_FLAG_DEFINITE_SHIFT = 18 }; typedef uint32_t TypeFlags; /* Flags and other state stored in ObjectGroup::Flags */ enum : uint32_t { /* Whether this group is associated with some allocation site. */ OBJECT_FLAG_FROM_ALLOCATION_SITE = 0x1, /* Whether this group is associated with a single object. */ OBJECT_FLAG_SINGLETON = 0x2, /* * Whether this group is used by objects whose singleton groups have not * been created yet. */ OBJECT_FLAG_LAZY_SINGLETON = 0x4, /* Mask/shift for the number of properties in propertySet */ OBJECT_FLAG_PROPERTY_COUNT_MASK = 0xfff8, OBJECT_FLAG_PROPERTY_COUNT_SHIFT = 3, OBJECT_FLAG_PROPERTY_COUNT_LIMIT = OBJECT_FLAG_PROPERTY_COUNT_MASK >> OBJECT_FLAG_PROPERTY_COUNT_SHIFT, /* Whether any objects this represents may have sparse indexes. */ OBJECT_FLAG_SPARSE_INDEXES = 0x00010000, /* Whether any objects this represents may not have packed dense elements. */ OBJECT_FLAG_NON_PACKED = 0x00020000, /* * Whether any objects this represents may be arrays whose length does not * fit in an int32. */ OBJECT_FLAG_LENGTH_OVERFLOW = 0x00040000, /* Whether any objects have been iterated over. */ OBJECT_FLAG_ITERATED = 0x00080000, /* Whether any object this represents may be frozen. */ OBJECT_FLAG_FROZEN = 0x00100000, /* * For the function on a run-once script, whether the function has actually * run multiple times. */ OBJECT_FLAG_RUNONCE_INVALIDATED = 0x00200000, /* * For a global object, whether any array buffers in this compartment with * typed object views have ever been detached. */ OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER = 0x00400000, /* * Whether objects with this type should be allocated directly in the * tenured heap. */ OBJECT_FLAG_PRE_TENURE = 0x00800000, /* Whether objects with this type might have copy on write elements. */ OBJECT_FLAG_COPY_ON_WRITE = 0x01000000, /* Whether this type has had its 'new' script cleared in the past. */ OBJECT_FLAG_NEW_SCRIPT_CLEARED = 0x02000000, /* * Whether all properties of this object are considered unknown. * If set, all other flags in DYNAMIC_MASK will also be set. */ OBJECT_FLAG_UNKNOWN_PROPERTIES = 0x04000000, /* Flags which indicate dynamic properties of represented objects. */ OBJECT_FLAG_DYNAMIC_MASK = 0x07ff0000, // Mask/shift for the kind of addendum attached to this group. OBJECT_FLAG_ADDENDUM_MASK = 0x38000000, OBJECT_FLAG_ADDENDUM_SHIFT = 27, // Mask/shift for this group's generation. If out of sync with the // TypeZone's generation, this group hasn't been swept yet. OBJECT_FLAG_GENERATION_MASK = 0x40000000, OBJECT_FLAG_GENERATION_SHIFT = 30, }; typedef uint32_t ObjectGroupFlags; class StackTypeSet; class HeapTypeSet; class TemporaryTypeSet; /* * Information about the set of types associated with an lvalue. There are * three kinds of type sets: * * - StackTypeSet are associated with TypeScripts, for arguments and values * observed at property reads. These are implicitly frozen on compilation * and only have constraints added to them which can trigger invalidation of * TypeNewScript information. * * - HeapTypeSet are associated with the properties of ObjectGroups. These * may have constraints added to them to trigger invalidation of either * compiled code or TypeNewScript information. * * - TemporaryTypeSet are created during compilation and do not outlive * that compilation. * * The contents of a type set completely describe the values that a particular * lvalue might have, except for the following cases: * * - If an object's prototype or class is dynamically mutated, its group will * change. Type sets containing the old group will not necessarily contain * the new group. When this occurs, the properties of the old and new group * will both be marked as unknown, which will prevent Ion from optimizing * based on the object's type information. * * - If an unboxed object is converted to a native object, its group will also * change and type sets containing the old group will not necessarily contain * the new group. Unlike the above case, this will not degrade property type * information, but Ion will no longer optimize unboxed objects with the old * group. */ class TypeSet { public: // Type set entry for either a JSObject with singleton type or a // non-singleton ObjectGroup. class ObjectKey { public: static intptr_t keyBits(ObjectKey* obj) { return (intptr_t) obj; } static ObjectKey* getKey(ObjectKey* obj) { return obj; } static inline ObjectKey* get(JSObject* obj); static inline ObjectKey* get(ObjectGroup* group); bool isGroup() { return (uintptr_t(this) & 1) == 0; } bool isSingleton() { return (uintptr_t(this) & 1) != 0; } inline ObjectGroup* group(); inline JSObject* singleton(); inline ObjectGroup* groupNoBarrier(); inline JSObject* singletonNoBarrier(); const Class* clasp(); TaggedProto proto(); TypeNewScript* newScript(); bool unknownProperties(); bool hasFlags(CompilerConstraintList* constraints, ObjectGroupFlags flags); bool hasStableClassAndProto(CompilerConstraintList* constraints); void watchStateChangeForInlinedCall(CompilerConstraintList* constraints); void watchStateChangeForTypedArrayData(CompilerConstraintList* constraints); HeapTypeSetKey property(jsid id); void ensureTrackedProperty(JSContext* cx, jsid id); ObjectGroup* maybeGroup(); JSCompartment* maybeCompartment(); } JS_HAZ_GC_POINTER; // Information about a single concrete type. We pack this into one word, // where small values are particular primitive or other singleton types and // larger values are either specific JS objects or object groups. class Type { friend class TypeSet; uintptr_t data; explicit Type(uintptr_t data) : data(data) {} public: uintptr_t raw() const { return data; } bool isPrimitive() const { return data < JSVAL_TYPE_OBJECT; } bool isPrimitive(JSValueType type) const { MOZ_ASSERT(type < JSVAL_TYPE_OBJECT); return (uintptr_t) type == data; } JSValueType primitive() const { MOZ_ASSERT(isPrimitive()); return (JSValueType) data; } bool isMagicArguments() const { return primitive() == JSVAL_TYPE_MAGIC; } bool isSomeObject() const { return data == JSVAL_TYPE_OBJECT || data > JSVAL_TYPE_UNKNOWN; } bool isAnyObject() const { return data == JSVAL_TYPE_OBJECT; } bool isUnknown() const { return data == JSVAL_TYPE_UNKNOWN; } /* Accessors for types that are either JSObject or ObjectGroup. */ bool isObject() const { MOZ_ASSERT(!isAnyObject() && !isUnknown()); return data > JSVAL_TYPE_UNKNOWN; } bool isObjectUnchecked() const { return data > JSVAL_TYPE_UNKNOWN; } inline ObjectKey* objectKey() const; /* Accessors for JSObject types */ bool isSingleton() const { return isObject() && !!(data & 1); } bool isSingletonUnchecked() const { return isObjectUnchecked() && !!(data & 1); } inline JSObject* singleton() const; inline JSObject* singletonNoBarrier() const; /* Accessors for ObjectGroup types */ bool isGroup() const { return isObject() && !(data & 1); } bool isGroupUnchecked() const { return isObjectUnchecked() && !(data & 1); } inline ObjectGroup* group() const; inline ObjectGroup* groupNoBarrier() const; inline void trace(JSTracer* trc); JSCompartment* maybeCompartment(); bool operator == (Type o) const { return data == o.data; } bool operator != (Type o) const { return data != o.data; } } JS_HAZ_GC_POINTER; static inline Type UndefinedType() { return Type(JSVAL_TYPE_UNDEFINED); } static inline Type NullType() { return Type(JSVAL_TYPE_NULL); } static inline Type BooleanType() { return Type(JSVAL_TYPE_BOOLEAN); } static inline Type Int32Type() { return Type(JSVAL_TYPE_INT32); } static inline Type DoubleType() { return Type(JSVAL_TYPE_DOUBLE); } static inline Type StringType() { return Type(JSVAL_TYPE_STRING); } static inline Type SymbolType() { return Type(JSVAL_TYPE_SYMBOL); } static inline Type MagicArgType() { return Type(JSVAL_TYPE_MAGIC); } static inline Type AnyObjectType() { return Type(JSVAL_TYPE_OBJECT); } static inline Type UnknownType() { return Type(JSVAL_TYPE_UNKNOWN); } static inline Type PrimitiveType(JSValueType type) { MOZ_ASSERT(type < JSVAL_TYPE_UNKNOWN); return Type(type); } static inline Type ObjectType(JSObject* obj); static inline Type ObjectType(ObjectGroup* group); static inline Type ObjectType(ObjectKey* key); static const char* NonObjectTypeString(Type type); static const char* TypeString(Type type); static const char* ObjectGroupString(ObjectGroup* group); protected: /* Flags for this type set. */ TypeFlags flags; /* Possible objects this type set can represent. */ ObjectKey** objectSet; public: TypeSet() : flags(0), objectSet(nullptr) {} void print(FILE* fp = stderr); /* Whether this set contains a specific type. */ inline bool hasType(Type type) const; TypeFlags baseFlags() const { return flags & TYPE_FLAG_BASE_MASK; } bool unknown() const { return !!(flags & TYPE_FLAG_UNKNOWN); } bool unknownObject() const { return !!(flags & (TYPE_FLAG_UNKNOWN | TYPE_FLAG_ANYOBJECT)); } bool empty() const { return !baseFlags() && !baseObjectCount(); } bool hasAnyFlag(TypeFlags flags) const { MOZ_ASSERT((flags & TYPE_FLAG_BASE_MASK) == flags); return !!(baseFlags() & flags); } 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 { MOZ_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); /* Return the intersection of the 2 TypeSets. The result should not be modified further */ static TemporaryTypeSet* intersectSets(TemporaryTypeSet* a, TemporaryTypeSet* b, LifoAlloc* alloc); /* * Returns a copy of TypeSet a excluding/removing the types in TypeSet b. * TypeSet b can only contain primitives or be any object. No support for * specific objects. The result should not be modified further. */ static TemporaryTypeSet* removeSet(TemporaryTypeSet* a, TemporaryTypeSet* b, LifoAlloc* alloc); /* Add a type to this set using the specified allocator. */ void addType(Type type, LifoAlloc* alloc); /* Get a list of all types in this set. */ typedef Vector TypeList; template bool enumerateTypes(TypeListT* list) const; /* * Iterate through the objects in this set. getObjectCount overapproximates * in the hash case (see SET_ARRAY_SIZE in TypeInference-inl.h), and * getObject may return nullptr. */ inline unsigned getObjectCount() const; inline ObjectKey* getObject(unsigned i) const; inline JSObject* getSingleton(unsigned i) const; inline ObjectGroup* getGroup(unsigned i) const; inline JSObject* getSingletonNoBarrier(unsigned i) const; inline ObjectGroup* getGroupNoBarrier(unsigned i) const; /* The Class of an object in this set. */ inline const Class* getObjectClass(unsigned i) const; bool canSetDefinite(unsigned slot) { // Note: the cast is required to work around an MSVC issue. return (slot + 1) <= (unsigned(TYPE_FLAG_DEFINITE_MASK) >> TYPE_FLAG_DEFINITE_SHIFT); } void setDefinite(unsigned slot) { MOZ_ASSERT(canSetDefinite(slot)); flags |= ((slot + 1) << TYPE_FLAG_DEFINITE_SHIFT); MOZ_ASSERT(definiteSlot() == slot); } /* Whether any values in this set might have the specified type. */ bool mightBeMIRType(jit::MIRType type) const; /* * Get whether this type set is known to be a subset of other. * This variant doesn't freeze constraints. That variant is called knownSubset */ bool isSubset(const TypeSet* other) const; /* * Get whether the objects in this TypeSet are a subset of the objects * in other. */ bool objectsAreSubset(TypeSet* other); /* Whether this TypeSet contains exactly the same types as other. */ bool equals(const TypeSet* other) const { return this->isSubset(other) && other->isSubset(this); } bool objectsIntersect(const TypeSet* other) const; /* Forward all types in this set to the specified constraint. */ bool addTypesToConstraint(JSContext* cx, TypeConstraint* constraint); // Clone a type set into an arbitrary allocator. TemporaryTypeSet* clone(LifoAlloc* alloc) const; // |*result| is not even partly initialized when this function is called: // this function placement-new's its contents into existence. bool cloneIntoUninitialized(LifoAlloc* alloc, TemporaryTypeSet* result) const; // Create a new TemporaryTypeSet where undefined and/or null has been filtered out. TemporaryTypeSet* filter(LifoAlloc* alloc, bool filterUndefined, bool filterNull) const; // Create a new TemporaryTypeSet where the type has been set to object. TemporaryTypeSet* cloneObjectsOnly(LifoAlloc* alloc); TemporaryTypeSet* cloneWithoutObjects(LifoAlloc* alloc); JSCompartment* maybeCompartment(); // Trigger a read barrier on all the contents of a type set. static void readBarrier(const TypeSet* types); protected: uint32_t baseObjectCount() const { return (flags & TYPE_FLAG_OBJECT_COUNT_MASK) >> TYPE_FLAG_OBJECT_COUNT_SHIFT; } inline void setBaseObjectCount(uint32_t count); void clearObjects(); public: static inline Type GetValueType(const Value& val); static inline bool IsUntrackedValue(const Value& val); // Get the type of a possibly optimized out or uninitialized let value. // This generally only happens on unconditional type monitors on bailing // out of Ion, such as for argument and local types. static inline Type GetMaybeUntrackedValueType(const Value& val); static bool IsTypeMarked(JSRuntime* rt, Type* v); static bool IsTypeAllocatedDuringIncremental(Type v); static bool IsTypeAboutToBeFinalized(Type* v); } JS_HAZ_GC_POINTER; /* * A constraint which listens to additions to a type set and propagates those * changes to other type sets. */ class TypeConstraint { public: /* Next constraint listening to the same type set. */ TypeConstraint* next; TypeConstraint() : next(nullptr) {} /* 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, TypeSet::Type type) = 0; /* * For constraints attached to an object property's type set, mark the * 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. */ virtual void newObjectState(JSContext* cx, ObjectGroup* group) {} /* * If the data this constraint refers to is still live, copy it into the * zone's new allocator. Type constraints only hold weak references. */ virtual bool sweep(TypeZone& zone, TypeConstraint** res) = 0; /* The associated compartment, if any. */ virtual JSCompartment* maybeCompartment() = 0; }; // If there is an OOM while sweeping types, the type information is deoptimized // so that it stays correct (i.e. overapproximates the possible types in the // zone), but constraints might not have been triggered on the deoptimization // or even copied over completely. In this case, destroy all JIT code and new // script information in the zone, the only things whose correctness depends on // the type constraints. class AutoClearTypeInferenceStateOnOOM { Zone* zone; bool oom; public: explicit AutoClearTypeInferenceStateOnOOM(Zone* zone) : zone(zone), oom(false) {} ~AutoClearTypeInferenceStateOnOOM(); void setOOM() { oom = true; } bool hadOOM() const { return oom; } }; /* Superclass common to stack and heap type sets. */ class ConstraintTypeSet : public TypeSet { public: /* Chain of constraints which propagate changes out from this type set. */ TypeConstraint* constraintList; ConstraintTypeSet() : constraintList(nullptr) {} /* * Add a type to this set, calling any constraint handlers if this is a new * possible type. */ void addType(ExclusiveContext* cx, Type type); // Trigger a post barrier when writing to this set, if necessary. // addType(cx, type) takes care of this automatically. void postWriteBarrier(ExclusiveContext* cx, Type type); /* Add a new constraint to this set. */ bool addConstraint(JSContext* cx, TypeConstraint* constraint, bool callExisting = true); inline void sweep(JS::Zone* zone, AutoClearTypeInferenceStateOnOOM& oom); inline void trace(JS::Zone* zone, JSTracer* trc); }; class StackTypeSet : public ConstraintTypeSet { public: }; class HeapTypeSet : public ConstraintTypeSet { inline void newPropertyState(ExclusiveContext* cx); public: /* Mark this type set as representing a non-data property. */ inline void setNonDataProperty(ExclusiveContext* cx); /* 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); }; CompilerConstraintList* NewCompilerConstraintList(jit::TempAllocator& alloc); class TemporaryTypeSet : public TypeSet { public: TemporaryTypeSet() {} TemporaryTypeSet(LifoAlloc* alloc, Type type); TemporaryTypeSet(uint32_t flags, ObjectKey** objectSet) { this->flags = flags; this->objectSet = objectSet; } TemporaryTypeSet(LifoAlloc* alloc, jit::MIRType type) : TemporaryTypeSet(alloc, PrimitiveType(ValueTypeFromMIRType(type))) { MOZ_ASSERT(type != jit::MIRType::Value); } /* * Constraints for JIT compilation. * * Methods for JIT compilation. These must be used when a script is * currently being compiled (see AutoEnterCompilation) and will add * constraints ensuring that if the return value change in the future due * to new type information, the script's jitcode will be discarded. */ /* Get any type tag which all values in this set must have. */ jit::MIRType getKnownMIRType(); bool isMagicArguments() { return getKnownMIRType() == jit::MIRType::MagicOptimizedArguments; } /* Whether this value may be an object. */ bool maybeObject() { return unknownObject() || baseObjectCount() > 0; } /* * Whether this typeset represents a potentially sentineled object value: * the value may be an object or null or undefined. * Returns false if the value cannot ever be an object. */ bool objectOrSentinel() { TypeFlags flags = TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL | TYPE_FLAG_ANYOBJECT; if (baseFlags() & (~flags & TYPE_FLAG_BASE_MASK)) return false; return hasAnyFlag(TYPE_FLAG_ANYOBJECT) || baseObjectCount() > 0; } /* Whether the type set contains objects with any of a set of flags. */ bool hasObjectFlags(CompilerConstraintList* constraints, ObjectGroupFlags flags); /* Get the class shared by all objects in this set, or nullptr. */ const Class* getKnownClass(CompilerConstraintList* constraints); /* Result returned from forAllClasses */ enum ForAllResult { EMPTY=1, // Set empty ALL_TRUE, // Set not empty and predicate returned true for all classes ALL_FALSE, // Set not empty and predicate returned false for all classes MIXED, // Set not empty and predicate returned false for some classes // and true for others, or set contains an unknown or non-object // type }; /* Apply func to the members of the set and return an appropriate result. * The iteration may end early if the result becomes known early. */ ForAllResult forAllClasses(CompilerConstraintList* constraints, bool (*func)(const Class* clasp)); /* * Returns true if all objects in this set have the same prototype, and * assigns this object to *proto. The proto can be nullptr. */ bool getCommonPrototype(CompilerConstraintList* constraints, JSObject** proto); /* Whether the buffer mapped by a TypedArray is shared memory or not */ enum TypedArraySharedness { UnknownSharedness=1, // We can't determine sharedness KnownShared, // We know for sure the buffer is shared KnownUnshared // We know for sure the buffer is unshared }; /* Get the typed array type of all objects in this set, or Scalar::MaxTypedArrayViewType. * If there is such a common type and sharedness is not nullptr then * *sharedness is set to what we know about the sharedness of the memory. */ Scalar::Type getTypedArrayType(CompilerConstraintList* constraints, TypedArraySharedness* sharedness = nullptr); /* Whether all objects have JSCLASS_IS_DOMJSCLASS set. */ bool isDOMClass(CompilerConstraintList* constraints); /* Whether clasp->isCallable() is true for one or more objects in this set. */ bool maybeCallable(CompilerConstraintList* constraints); /* Whether clasp->emulatesUndefined() is true for one or more objects in this set. */ bool maybeEmulatesUndefined(CompilerConstraintList* constraints); /* Get the single value which can appear in this type set, otherwise nullptr. */ JSObject* maybeSingleton(); /* Whether any objects in the type set needs a barrier on id. */ bool propertyNeedsBarrier(CompilerConstraintList* constraints, jsid id); /* * 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. */ AlwaysConvertToDoubles, /* Some types in the set should use eager double conversion. */ MaybeConvertToDoubles, /* No types should use eager double conversion. */ DontConvertToDoubles, /* Some types should use eager double conversion, others cannot. */ AmbiguousDoubleConversion }; /* * Whether known double optimizations are possible for element accesses on * objects in this type set. */ DoubleConversion convertDoubleElements(CompilerConstraintList* constraints); private: void getTypedArraySharedness(CompilerConstraintList* constraints, TypedArraySharedness* sharedness); }; bool AddClearDefiniteGetterSetterForPrototypeChain(JSContext* cx, ObjectGroup* group, HandleId id); bool AddClearDefiniteFunctionUsesInScript(JSContext* cx, ObjectGroup* group, JSScript* script, JSScript* calleeScript); // For groups where only a small number of objects have been allocated, this // structure keeps track of all objects in the group. Once COUNT objects have // been allocated, this structure is cleared and the objects are analyzed, to // perform the new script properties analyses or determine if an unboxed // representation can be used. class PreliminaryObjectArray { public: static const uint32_t COUNT = 20; private: // All objects with the type which have been allocated. The pointers in // this array are weak. JSObject* objects[COUNT] = {}; // zeroes public: PreliminaryObjectArray() = default; void registerNewObject(PlainObject* res); void unregisterObject(PlainObject* obj); JSObject* get(size_t i) const { MOZ_ASSERT(i < COUNT); return objects[i]; } bool full() const; bool empty() const; void sweep(); }; class PreliminaryObjectArrayWithTemplate : public PreliminaryObjectArray { HeapPtr shape_; public: explicit PreliminaryObjectArrayWithTemplate(Shape* shape) : shape_(shape) {} void clear() { shape_.init(nullptr); } Shape* shape() { return shape_; } void maybeAnalyze(ExclusiveContext* cx, ObjectGroup* group, bool force = false); void trace(JSTracer* trc); static void writeBarrierPre(PreliminaryObjectArrayWithTemplate* preliminaryObjects); }; // New script properties analyses overview. // // When constructing objects using 'new' on a script, we attempt to determine // the properties which that object will eventually have. This is done via two // analyses. One of these, the definite properties analysis, is static, and the // other, the acquired properties analysis, is dynamic. As objects are // constructed using 'new' on some script to create objects of group G, our // analysis strategy is as follows: // // - When the first objects are created, no analysis is immediately performed. // Instead, all objects of group G are accumulated in an array. // // - After a certain number of such objects have been created, the definite // properties analysis is performed. This analyzes the body of the // constructor script and any other functions it calls to look for properties // which will definitely be added by the constructor in a particular order, // creating an object with shape S. // // - The properties in S are compared with the greatest common prefix P of the // shapes of the objects that have been created. If P has more properties // than S, the acquired properties analysis is performed. // // - The acquired properties analysis marks all properties in P as definite // in G, and creates a new group IG for objects which are partially // initialized. Objects of group IG are initially created with shape S, and if // they are later given shape P, their group can be changed to G. // // For objects which are rarely created, the definite properties analysis can // be triggered after only one or a few objects have been allocated, when code // being Ion compiled might access them. In this case type information in the // constructor might not be good enough for the definite properties analysis to // compute useful information, but the acquired properties analysis will still // be able to identify definite properties in this case. // // This layered approach is designed to maximize performance on easily // analyzable code, while still allowing us to determine definite properties // robustly when code consistently adds the same properties to objects, but in // complex ways which can't be understood statically. class TypeNewScript { public: struct Initializer { enum Kind { SETPROP, SETPROP_FRAME, DONE } kind; uint32_t offset; Initializer(Kind kind, uint32_t offset) : kind(kind), offset(offset) {} }; private: // Scripted function which this information was computed for. HeapPtr function_ = {}; // Any preliminary objects with the type. The analyses are not performed // until this array is cleared. PreliminaryObjectArray* preliminaryObjects = nullptr; // After the new script properties analyses have been performed, a template // object to use for newly constructed objects. The shape of this object // reflects all definite properties the object will have, and the // allocation kind to use. This is null if the new objects have an unboxed // layout, in which case the UnboxedLayout provides the initial structure // of the object. HeapPtr templateObject_ = {}; // Order in which definite properties become initialized. We need this in // case the definite properties are invalidated (such as by adding a setter // to an object on the prototype chain) while an object is in the middle of // being initialized, so we can walk the stack and fixup any objects which // look for in-progress objects which were prematurely set with an incorrect // shape. Property assignments in inner frames are preceded by a series of // SETPROP_FRAME entries specifying the stack down to the frame containing // the write. Initializer* initializerList = nullptr; // If there are additional properties found by the acquired properties // analysis which were not found by the definite properties analysis, this // shape contains all such additional properties (plus the definite // properties). When an object of this group acquires this shape, it is // fully initialized and its group can be changed to initializedGroup. HeapPtr initializedShape_ = {}; // Group with definite properties set for all properties found by // both the definite and acquired properties analyses. HeapPtr initializedGroup_ = {}; public: TypeNewScript() = default; ~TypeNewScript() { js_delete(preliminaryObjects); js_free(initializerList); } void clear() { function_.init(nullptr); templateObject_.init(nullptr); initializedShape_.init(nullptr); initializedGroup_.init(nullptr); } static void writeBarrierPre(TypeNewScript* newScript); bool analyzed() const { return preliminaryObjects == nullptr; } PlainObject* templateObject() const { return templateObject_; } Shape* initializedShape() const { return initializedShape_; } ObjectGroup* initializedGroup() const { return initializedGroup_; } JSFunction* function() const { return function_; } void trace(JSTracer* trc); void sweep(); void registerNewObject(PlainObject* res); bool maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, bool force = false); bool rollbackPartiallyInitializedObjects(JSContext* cx, ObjectGroup* group); static bool make(JSContext* cx, ObjectGroup* group, JSFunction* fun); static TypeNewScript* makeNativeVersion(JSContext* cx, TypeNewScript* newScript, PlainObject* templateObject); size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; }; /* Is this a reasonable PC to be doing inlining on? */ inline bool isInlinableCall(jsbytecode* pc); bool ClassCanHaveExtraProperties(const Class* clasp); /* Persistent type information for a script, retained across GCs. */ class TypeScript { friend class ::JSScript; // Variable-size array StackTypeSet typeArray_[1]; public: /* Array of type sets for variables and JOF_TYPESET ops. */ StackTypeSet* typeArray() const { // Ensure typeArray_ is the last data member of TypeScript. JS_STATIC_ASSERT(sizeof(TypeScript) == sizeof(typeArray_) + offsetof(TypeScript, typeArray_)); return const_cast(typeArray_); } static inline size_t SizeIncludingTypeArray(size_t arraySize) { // Ensure typeArray_ is the last data member of TypeScript. JS_STATIC_ASSERT(sizeof(TypeScript) == sizeof(StackTypeSet) + offsetof(TypeScript, typeArray_)); return offsetof(TypeScript, typeArray_) + arraySize * sizeof(StackTypeSet); } static inline unsigned NumTypeSets(JSScript* script); static inline StackTypeSet* ThisTypes(JSScript* script); static inline StackTypeSet* ArgTypes(JSScript* script, unsigned i); /* Get the type set for values observed at an opcode. */ static inline StackTypeSet* BytecodeTypes(JSScript* script, jsbytecode* pc); template static inline TYPESET* BytecodeTypes(JSScript* script, jsbytecode* pc, uint32_t* bytecodeMap, uint32_t* hint, TYPESET* typeArray); /* * Monitor a bytecode pushing any value. This must be called for any opcode * which is JOF_TYPESET, and where either the script has not been analyzed * by type inference or where the pc has type barriers. For simplicity, we * always monitor JOF_TYPESET opcodes in the interpreter and stub calls, * and only look at barriers when generating JIT code for the script. */ static inline void Monitor(JSContext* cx, JSScript* script, jsbytecode* pc, const js::Value& val); static inline void Monitor(JSContext* cx, JSScript* script, jsbytecode* pc, TypeSet::Type type); static inline void Monitor(JSContext* cx, const js::Value& rval); /* Monitor an assignment at a SETELEM on a non-integer identifier. */ static inline void MonitorAssign(JSContext* cx, HandleObject obj, jsid id); /* Add a type for a variable in a script. */ static inline void SetThis(JSContext* cx, JSScript* script, TypeSet::Type type); static inline void SetThis(JSContext* cx, JSScript* script, const js::Value& value); static inline void SetArgument(JSContext* cx, JSScript* script, unsigned arg, TypeSet::Type type); static inline void SetArgument(JSContext* cx, JSScript* script, unsigned arg, const js::Value& value); /* * Freeze all the stack type sets in a script, for a compilation. Returns * copies of the type sets which will be checked against the actual ones * under FinishCompilation, to detect any type changes. */ static bool FreezeTypeSets(CompilerConstraintList* constraints, JSScript* script, TemporaryTypeSet** pThisTypes, TemporaryTypeSet** pArgTypes, TemporaryTypeSet** pBytecodeTypes); static void Purge(JSContext* cx, HandleScript script); void destroy(); size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { return mallocSizeOf(this); } #ifdef DEBUG void printTypes(JSContext* cx, HandleScript script) const; #endif }; void FillBytecodeTypeMap(JSScript* script, uint32_t* bytecodeMap); class RecompileInfo; // Allocate a CompilerOutput for a finished compilation and generate the type // constraints for the compilation. Sets |isValidOut| based on whether the type // constraints still hold. bool FinishCompilation(JSContext* cx, HandleScript script, CompilerConstraintList* constraints, RecompileInfo* precompileInfo, bool* isValidOut); // Update the actual types in any scripts queried by constraints with any // speculative types added during the definite properties analysis. void FinishDefinitePropertiesAnalysis(JSContext* cx, CompilerConstraintList* constraints); // Representation of a heap type property which may or may not be instantiated. // Heap properties for singleton types are instantiated lazily as they are used // by the compiler, but this is only done on the main thread. If we are // compiling off thread and use a property which has not yet been instantiated, // it will be treated as empty and non-configured and will be instantiated when // rejoining to the main thread. If it is in fact not empty, the compilation // will fail; to avoid this, we try to instantiate singleton property types // during generation of baseline caches. class HeapTypeSetKey { friend class TypeSet::ObjectKey; // Object and property being accessed. TypeSet::ObjectKey* object_; jsid id_; // If instantiated, the underlying heap type set. HeapTypeSet* maybeTypes_; public: HeapTypeSetKey() : object_(nullptr), id_(JSID_EMPTY), maybeTypes_(nullptr) {} TypeSet::ObjectKey* object() const { return object_; } jsid id() const { return id_; } HeapTypeSet* maybeTypes() const { return maybeTypes_; } bool instantiate(JSContext* cx); void freeze(CompilerConstraintList* constraints); jit::MIRType knownMIRType(CompilerConstraintList* constraints); bool nonData(CompilerConstraintList* constraints); bool nonWritable(CompilerConstraintList* constraints); bool isOwnProperty(CompilerConstraintList* constraints, bool allowEmptyTypesForGlobal = false); bool knownSubset(CompilerConstraintList* constraints, const HeapTypeSetKey& other); JSObject* singleton(CompilerConstraintList* constraints); bool needsBarrier(CompilerConstraintList* constraints); bool constant(CompilerConstraintList* constraints, Value* valOut); bool couldBeConstant(CompilerConstraintList* constraints); }; /* * 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. */ class CompilerOutput { // If this compilation has not been invalidated, the associated script and // kind of compilation being performed. JSScript* script_; // Whether this compilation is about to be invalidated. bool pendingInvalidation_ : 1; // During sweeping, the list of compiler outputs is compacted and invalidated // outputs are removed. This gives the new index for a valid compiler output. uint32_t sweepIndex_ : 31; public: static const uint32_t INVALID_SWEEP_INDEX = static_cast(1 << 31) - 1; CompilerOutput() : script_(nullptr), pendingInvalidation_(false), sweepIndex_(INVALID_SWEEP_INDEX) {} explicit CompilerOutput(JSScript* script) : script_(script), pendingInvalidation_(false), sweepIndex_(INVALID_SWEEP_INDEX) {} JSScript* script() const { return script_; } inline jit::IonScript* ion() const; bool isValid() const { return script_ != nullptr; } void invalidate() { script_ = nullptr; } void setPendingInvalidation() { pendingInvalidation_ = true; } bool pendingInvalidation() { return pendingInvalidation_; } void setSweepIndex(uint32_t index) { if (index >= INVALID_SWEEP_INDEX) MOZ_CRASH(); sweepIndex_ = index; } uint32_t sweepIndex() { MOZ_ASSERT(sweepIndex_ != INVALID_SWEEP_INDEX); return sweepIndex_; } }; class RecompileInfo { // Index in the TypeZone's compilerOutputs or sweepCompilerOutputs arrays, // depending on the generation value. uint32_t outputIndex : 31; // If out of sync with the TypeZone's generation, this index is for the // zone's sweepCompilerOutputs rather than compilerOutputs. uint32_t generation : 1; public: RecompileInfo(uint32_t outputIndex, uint32_t generation) : outputIndex(outputIndex), generation(generation) {} RecompileInfo() : outputIndex(JS_BITMASK(31)), generation(0) {} CompilerOutput* compilerOutput(TypeZone& types) const; CompilerOutput* compilerOutput(JSContext* cx) const; bool shouldSweep(TypeZone& types); }; // The RecompileInfoVector has a MinInlineCapacity of one so that invalidating a // single IonScript doesn't require an allocation. typedef Vector RecompileInfoVector; struct AutoEnterAnalysis; struct TypeZone { JS::Zone* zone_; /* Pool for type information in this zone. */ static const size_t TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 8 * 1024; LifoAlloc typeLifoAlloc; // Current generation for sweeping. uint32_t generation : 1; /* * All Ion compilations that have occured in this zone, for indexing via * RecompileInfo. This includes both valid and invalid compilations, though * invalidated compilations are swept on GC. */ typedef Vector CompilerOutputVector; CompilerOutputVector* compilerOutputs; // During incremental sweeping, allocator holding the old type information // for the zone. LifoAlloc sweepTypeLifoAlloc; // During incremental sweeping, the old compiler outputs for use by // recompile indexes with a stale generation. CompilerOutputVector* sweepCompilerOutputs; // During incremental sweeping, whether to try to destroy all type // information attached to scripts. bool sweepReleaseTypes; // The topmost AutoEnterAnalysis on the stack, if there is one. AutoEnterAnalysis* activeAnalysis; explicit TypeZone(JS::Zone* zone); ~TypeZone(); JS::Zone* zone() const { return zone_; } void beginSweep(FreeOp* fop, bool releaseTypes, AutoClearTypeInferenceStateOnOOM& oom); void endSweep(JSRuntime* rt); void clearAllNewScriptsOnOOM(); /* Mark a script as needing recompilation once inference has finished. */ void addPendingRecompile(JSContext* cx, const RecompileInfo& info); void addPendingRecompile(JSContext* cx, JSScript* script); void processPendingRecompiles(FreeOp* fop, RecompileInfoVector& recompiles); }; enum SpewChannel { ISpewOps, /* ops: New constraints and types. */ ISpewResult, /* result: Final type sets. */ SPEW_COUNT }; #ifdef DEBUG bool InferSpewActive(SpewChannel channel); const char * InferSpewColorReset(); const char * InferSpewColor(TypeConstraint* constraint); const char * InferSpewColor(TypeSet* types); #define InferSpew(channel, ...) if (InferSpewActive(channel)) { InferSpewImpl(__VA_ARGS__); } else {} void InferSpewImpl(const char* fmt, ...) MOZ_FORMAT_PRINTF(1, 2); /* Check that the type property for id in group contains value. */ bool ObjectGroupHasProperty(JSContext* cx, ObjectGroup* group, jsid id, const Value& value); #else inline const char * InferSpewColorReset() { return nullptr; } inline const char * InferSpewColor(TypeConstraint* constraint) { return nullptr; } inline const char * InferSpewColor(TypeSet* types) { return nullptr; } #define InferSpew(channel, ...) do {} while (0) #endif // Prints type information for a context if spew is enabled or force is set. void PrintTypes(JSContext* cx, JSCompartment* comp, bool force); } /* namespace js */ // JS::ubi::Nodes can point to object groups; they're js::gc::Cell instances // with no associated compartment. namespace JS { namespace ubi { template<> class Concrete : TracerConcrete { protected: explicit Concrete(js::ObjectGroup *ptr) : TracerConcrete(ptr) { } public: static void construct(void *storage, js::ObjectGroup *ptr) { new (storage) Concrete(ptr); } Size size(mozilla::MallocSizeOf mallocSizeOf) const override; const char16_t* typeName() const override { return concreteTypeName; } static const char16_t concreteTypeName[]; }; } // namespace ubi } // namespace JS #endif /* vm_TypeInference_h */