blob: 7e647c0fe25126d7ea51a0d7d910aba29bad5dde [file] [log] [blame]
/* -*- 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,
/* For a global object, whether flags were set on the RegExpStatics. */
OBJECT_FLAG_REGEXP_FLAGS_SET = 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 been neutered.
*/
OBJECT_FLAG_TYPED_OBJECT_NEUTERED = 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);
void watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList* constraints);
HeapTypeSetKey property(jsid id);
void ensureTrackedProperty(JSContext* cx, jsid id);
ObjectGroup* maybeGroup();
};
// 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 : public JS::Traceable
{
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;
static void trace(Type* v, JSTracer* trc) {
MarkTypeUnbarriered(trc, v, "TypeSet::Type");
}
bool operator == (Type o) const { return data == o.data; }
bool operator != (Type o) const { return data != o.data; }
};
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);
#ifdef DEBUG
static const char* TypeString(Type type);
static const char* ObjectGroupString(ObjectGroup* group);
#else
static const char* TypeString(Type type) { return nullptr; }
static const char* ObjectGroupString(ObjectGroup* group) { return nullptr; }
#endif
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<Type, 1, SystemAllocPolicy> TypeList;
template <class TypeListT> 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;
bool clone(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);
// 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 void MarkTypeRoot(JSTracer* trc, Type* v, const char* name);
static void MarkTypeUnbarriered(JSTracer* trc, Type* v, const char* name);
static bool IsTypeMarked(Type* v);
static bool IsTypeAllocatedDuringIncremental(Type v);
static bool IsTypeAboutToBeFinalized(Type* v);
};
/*
* 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;
};
// 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;
}
/*
* 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];
public:
PreliminaryObjectArray() {
mozilla::PodZero(this);
}
void registerNewObject(JSObject* res);
void unregisterObject(JSObject* 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
{
RelocatablePtrShape 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.
RelocatablePtrFunction function_;
// Any preliminary objects with the type. The analyses are not performed
// until this array is cleared.
PreliminaryObjectArray* preliminaryObjects;
// 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.
RelocatablePtrPlainObject 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;
// 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.
RelocatablePtrShape initializedShape_;
// Group with definite properties set for all properties found by
// both the definite and acquired properties analyses.
RelocatablePtrObjectGroup initializedGroup_;
public:
TypeNewScript() { mozilla::PodZero(this); }
~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<StackTypeSet*>(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 <typename TYPESET>
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);
// Reset any CompilerOutput present for a script.
void
InvalidateCompilerOutputsForScript(JSContext* cx, HandleScript script);
// 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<uint32_t>(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);
};
typedef Vector<RecompileInfo, 0, SystemAllocPolicy> 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<CompilerOutput, 4, SystemAllocPolicy> 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
const char * InferSpewColorReset();
const char * InferSpewColor(TypeConstraint* constraint);
const char * InferSpewColor(TypeSet* types);
void InferSpew(SpewChannel which, const char* fmt, ...);
/* 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; }
inline void InferSpew(SpewChannel which, const char* fmt, ...) {}
#endif
/* Print a warning, dump state and abort the program. */
MOZ_NORETURN MOZ_COLD void TypeFailure(JSContext* cx, const char* fmt, ...);
// 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<>
struct Concrete<js::ObjectGroup> : TracerConcrete<js::ObjectGroup> {
Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
protected:
explicit Concrete(js::ObjectGroup *ptr) : TracerConcrete<js::ObjectGroup>(ptr) { }
public:
static void construct(void *storage, js::ObjectGroup *ptr) { new (storage) Concrete(ptr); }
};
} // namespace ubi
} // namespace JS
#endif /* vm_TypeInference_h */