blob: 2bf9a3792119baddabc26385df8a11e5ec5361f6 [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/. */
#include "vm/TypeInference-inl.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/PodOperations.h"
#include "mozilla/SizePrintfMacros.h"
#include "jsapi.h"
#include "jscntxt.h"
#include "jsgc.h"
#include "jshashutil.h"
#include "jsobj.h"
#include "jsprf.h"
#include "jsscript.h"
#include "jsstr.h"
#include "gc/Marking.h"
#include "jit/BaselineJIT.h"
#include "jit/CompileInfo.h"
#include "jit/Ion.h"
#include "jit/IonAnalysis.h"
#include "jit/JitCompartment.h"
#include "jit/OptimizationTracking.h"
#include "js/MemoryMetrics.h"
#include "vm/HelperThreads.h"
#include "vm/Opcodes.h"
#include "vm/Shape.h"
#include "vm/Time.h"
#include "vm/UnboxedObject.h"
#include "jsatominlines.h"
#include "jsscriptinlines.h"
#include "vm/NativeObject-inl.h"
using namespace js;
using namespace js::gc;
using mozilla::DebugOnly;
using mozilla::Maybe;
using mozilla::PodArrayZero;
using mozilla::PodCopy;
using mozilla::PodZero;
#ifdef DEBUG
static inline jsid
id___proto__(JSContext* cx)
{
return NameToId(cx->names().proto);
}
static inline jsid
id_constructor(JSContext* cx)
{
return NameToId(cx->names().constructor);
}
static inline jsid
id_caller(JSContext* cx)
{
return NameToId(cx->names().caller);
}
const char*
js::TypeIdStringImpl(jsid id)
{
if (JSID_IS_VOID(id))
return "(index)";
if (JSID_IS_EMPTY(id))
return "(new)";
if (JSID_IS_SYMBOL(id))
return "(symbol)";
static char bufs[4][100];
static unsigned which = 0;
which = (which + 1) & 3;
PutEscapedString(bufs[which], 100, JSID_TO_FLAT_STRING(id), 0);
return bufs[which];
}
#endif
/////////////////////////////////////////////////////////////////////
// Logging
/////////////////////////////////////////////////////////////////////
/* static */ const char*
TypeSet::NonObjectTypeString(TypeSet::Type type)
{
if (type.isPrimitive()) {
switch (type.primitive()) {
case JSVAL_TYPE_UNDEFINED:
return "void";
case JSVAL_TYPE_NULL:
return "null";
case JSVAL_TYPE_BOOLEAN:
return "bool";
case JSVAL_TYPE_INT32:
return "int";
case JSVAL_TYPE_DOUBLE:
return "float";
case JSVAL_TYPE_STRING:
return "string";
case JSVAL_TYPE_SYMBOL:
return "symbol";
case JSVAL_TYPE_MAGIC:
return "lazyargs";
default:
MOZ_CRASH("Bad type");
}
}
if (type.isUnknown())
return "unknown";
MOZ_ASSERT(type.isAnyObject());
return "object";
}
#ifdef DEBUG
static bool InferSpewActive(SpewChannel channel)
{
static bool active[SPEW_COUNT];
static bool checked = false;
if (!checked) {
checked = true;
PodArrayZero(active);
const char* env = js_sb_getenv("INFERFLAGS");
if (!env)
return false;
if (strstr(env, "ops"))
active[ISpewOps] = true;
if (strstr(env, "result"))
active[ISpewResult] = true;
if (strstr(env, "full")) {
for (unsigned i = 0; i < SPEW_COUNT; i++)
active[i] = true;
}
}
return active[channel];
}
static bool InferSpewColorable()
{
/* Only spew colors on xterm-color to not screw up emacs. */
static bool colorable = false;
static bool checked = false;
if (!checked) {
checked = true;
const char* env = js_sb_getenv("TERM");
if (!env)
return false;
if (strcmp(env, "xterm-color") == 0 || strcmp(env, "xterm-256color") == 0)
colorable = true;
}
return colorable;
}
const char*
js::InferSpewColorReset()
{
if (!InferSpewColorable())
return "";
return "\x1b[0m";
}
const char*
js::InferSpewColor(TypeConstraint* constraint)
{
/* Type constraints are printed out using foreground colors. */
static const char * const colors[] = { "\x1b[31m", "\x1b[32m", "\x1b[33m",
"\x1b[34m", "\x1b[35m", "\x1b[36m",
"\x1b[37m" };
if (!InferSpewColorable())
return "";
return colors[DefaultHasher<TypeConstraint*>::hash(constraint) % 7];
}
const char*
js::InferSpewColor(TypeSet* types)
{
/* Type sets are printed out using bold colors. */
static const char * const colors[] = { "\x1b[1;31m", "\x1b[1;32m", "\x1b[1;33m",
"\x1b[1;34m", "\x1b[1;35m", "\x1b[1;36m",
"\x1b[1;37m" };
if (!InferSpewColorable())
return "";
return colors[DefaultHasher<TypeSet*>::hash(types) % 7];
}
/* static */ const char*
TypeSet::TypeString(TypeSet::Type type)
{
if (type.isPrimitive() || type.isUnknown() || type.isAnyObject())
return NonObjectTypeString(type);
static char bufs[4][40];
static unsigned which = 0;
which = (which + 1) & 3;
if (type.isSingleton())
JS_snprintf(bufs[which], 40, "<0x%p>", (void*) type.singletonNoBarrier());
else
JS_snprintf(bufs[which], 40, "[0x%p]", (void*) type.groupNoBarrier());
return bufs[which];
}
/* static */ const char*
TypeSet::ObjectGroupString(ObjectGroup* group)
{
return TypeString(TypeSet::ObjectType(group));
}
void
js::InferSpew(SpewChannel channel, const char* fmt, ...)
{
if (!InferSpewActive(channel))
return;
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "[infer] ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
bool
js::ObjectGroupHasProperty(JSContext* cx, ObjectGroup* group, jsid id, const Value& value)
{
/*
* Check the correctness of the type information in the object's property
* against an actual value.
*/
if (!group->unknownProperties() && !value.isUndefined()) {
id = IdToTypeId(id);
/* Watch for properties which inference does not monitor. */
if (id == id___proto__(cx) || id == id_constructor(cx) || id == id_caller(cx))
return true;
TypeSet::Type type = TypeSet::GetValueType(value);
AutoEnterAnalysis enter(cx);
/*
* We don't track types for properties inherited from prototypes which
* haven't yet been accessed during analysis of the inheriting object.
* Don't do the property instantiation now.
*/
TypeSet* types = group->maybeGetProperty(id);
if (!types)
return true;
// Type set guards might miss when an object's group changes and its
// properties become unknown.
if (value.isObject()) {
if (types->unknownObject())
return true;
for (size_t i = 0; i < types->getObjectCount(); i++) {
if (TypeSet::ObjectKey* key = types->getObject(i)) {
if (key->unknownProperties())
return true;
}
}
JSObject* obj = &value.toObject();
if (!obj->hasLazyGroup() && obj->group()->maybeOriginalUnboxedGroup())
return true;
}
if (!types->hasType(type)) {
TypeFailure(cx, "Missing type in object %s %s: %s",
TypeSet::ObjectGroupString(group), TypeIdString(id),
TypeSet::TypeString(type));
}
}
return true;
}
#endif
void
js::TypeFailure(JSContext* cx, const char* fmt, ...)
{
char msgbuf[1024]; /* Larger error messages will be truncated */
char errbuf[1024];
va_list ap;
va_start(ap, fmt);
JS_vsnprintf(errbuf, sizeof(errbuf), fmt, ap);
va_end(ap);
JS_snprintf(msgbuf, sizeof(msgbuf), "[infer failure] %s", errbuf);
/* Dump type state, even if INFERFLAGS is unset. */
PrintTypes(cx, cx->compartment(), true);
MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__);
MOZ_CRASH();
}
/////////////////////////////////////////////////////////////////////
// TypeSet
/////////////////////////////////////////////////////////////////////
TemporaryTypeSet::TemporaryTypeSet(LifoAlloc* alloc, Type type)
{
if (type.isUnknown()) {
flags |= TYPE_FLAG_BASE_MASK;
} else if (type.isPrimitive()) {
flags = PrimitiveTypeFlag(type.primitive());
if (flags == TYPE_FLAG_DOUBLE)
flags |= TYPE_FLAG_INT32;
} else if (type.isAnyObject()) {
flags |= TYPE_FLAG_ANYOBJECT;
} else if (type.isGroup() && type.group()->unknownProperties()) {
flags |= TYPE_FLAG_ANYOBJECT;
} else {
setBaseObjectCount(1);
objectSet = reinterpret_cast<ObjectKey**>(type.objectKey());
if (type.isGroup()) {
ObjectGroup* ngroup = type.group();
if (ngroup->newScript() && ngroup->newScript()->initializedGroup())
addType(ObjectType(ngroup->newScript()->initializedGroup()), alloc);
}
}
}
bool
TypeSet::mightBeMIRType(jit::MIRType type) const
{
if (unknown())
return true;
if (type == jit::MIRType_Object)
return unknownObject() || baseObjectCount() != 0;
switch (type) {
case jit::MIRType_Undefined:
return baseFlags() & TYPE_FLAG_UNDEFINED;
case jit::MIRType_Null:
return baseFlags() & TYPE_FLAG_NULL;
case jit::MIRType_Boolean:
return baseFlags() & TYPE_FLAG_BOOLEAN;
case jit::MIRType_Int32:
return baseFlags() & TYPE_FLAG_INT32;
case jit::MIRType_Float32: // Fall through, there's no JSVAL for Float32.
case jit::MIRType_Double:
return baseFlags() & TYPE_FLAG_DOUBLE;
case jit::MIRType_String:
return baseFlags() & TYPE_FLAG_STRING;
case jit::MIRType_Symbol:
return baseFlags() & TYPE_FLAG_SYMBOL;
case jit::MIRType_MagicOptimizedArguments:
return baseFlags() & TYPE_FLAG_LAZYARGS;
case jit::MIRType_MagicHole:
case jit::MIRType_MagicIsConstructing:
// These magic constants do not escape to script and are not observed
// in the type sets.
//
// The reason we can return false here is subtle: if Ion is asking the
// type set if it has seen such a magic constant, then the MIR in
// question is the most generic type, MIRType_Value. A magic constant
// could only be emitted by a MIR of MIRType_Value if that MIR is a
// phi, and we check that different magic constants do not flow to the
// same join point in GuessPhiType.
return false;
default:
MOZ_CRASH("Bad MIR type");
}
}
bool
TypeSet::objectsAreSubset(TypeSet* other)
{
if (other->unknownObject())
return true;
if (unknownObject())
return false;
for (unsigned i = 0; i < getObjectCount(); i++) {
ObjectKey* key = getObject(i);
if (!key)
continue;
if (!other->hasType(ObjectType(key)))
return false;
}
return true;
}
bool
TypeSet::isSubset(const TypeSet* other) const
{
if ((baseFlags() & other->baseFlags()) != baseFlags())
return false;
if (unknownObject()) {
MOZ_ASSERT(other->unknownObject());
} else {
for (unsigned i = 0; i < getObjectCount(); i++) {
ObjectKey* key = getObject(i);
if (!key)
continue;
if (!other->hasType(ObjectType(key)))
return false;
}
}
return true;
}
bool
TypeSet::objectsIntersect(const TypeSet* other) const
{
if (unknownObject() || other->unknownObject())
return true;
for (unsigned i = 0; i < getObjectCount(); i++) {
ObjectKey* key = getObject(i);
if (!key)
continue;
if (other->hasType(ObjectType(key)))
return true;
}
return false;
}
template <class TypeListT>
bool
TypeSet::enumerateTypes(TypeListT* list) const
{
/* If any type is possible, there's no need to worry about specifics. */
if (flags & TYPE_FLAG_UNKNOWN)
return list->append(UnknownType());
/* Enqueue type set members stored as bits. */
for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) {
if (flags & flag) {
Type type = PrimitiveType(TypeFlagPrimitive(flag));
if (!list->append(type))
return false;
}
}
/* If any object is possible, skip specifics. */
if (flags & TYPE_FLAG_ANYOBJECT)
return list->append(AnyObjectType());
/* Enqueue specific object types. */
unsigned count = getObjectCount();
for (unsigned i = 0; i < count; i++) {
ObjectKey* key = getObject(i);
if (key) {
if (!list->append(ObjectType(key)))
return false;
}
}
return true;
}
template bool TypeSet::enumerateTypes<TypeSet::TypeList>(TypeList* list) const;
template bool TypeSet::enumerateTypes<jit::TempTypeList>(jit::TempTypeList* list) const;
inline bool
TypeSet::addTypesToConstraint(JSContext* cx, TypeConstraint* constraint)
{
/*
* Build all types in the set into a vector before triggering the
* constraint, as doing so may modify this type set.
*/
TypeList types;
if (!enumerateTypes(&types))
return false;
for (unsigned i = 0; i < types.length(); i++)
constraint->newType(cx, this, types[i]);
return true;
}
bool
ConstraintTypeSet::addConstraint(JSContext* cx, TypeConstraint* constraint, bool callExisting)
{
if (!constraint) {
/* OOM failure while constructing the constraint. */
return false;
}
MOZ_ASSERT(cx->zone()->types.activeAnalysis);
InferSpew(ISpewOps, "addConstraint: %sT%p%s %sC%p%s %s",
InferSpewColor(this), this, InferSpewColorReset(),
InferSpewColor(constraint), constraint, InferSpewColorReset(),
constraint->kind());
MOZ_ASSERT(constraint->next == nullptr);
constraint->next = constraintList;
constraintList = constraint;
if (callExisting)
return addTypesToConstraint(cx, constraint);
return true;
}
void
TypeSet::clearObjects()
{
setBaseObjectCount(0);
objectSet = nullptr;
}
void
TypeSet::addType(Type type, LifoAlloc* alloc)
{
if (unknown())
return;
if (type.isUnknown()) {
flags |= TYPE_FLAG_BASE_MASK;
clearObjects();
MOZ_ASSERT(unknown());
return;
}
if (type.isPrimitive()) {
TypeFlags flag = PrimitiveTypeFlag(type.primitive());
if (flags & flag)
return;
/* If we add float to a type set it is also considered to contain int. */
if (flag == TYPE_FLAG_DOUBLE)
flag |= TYPE_FLAG_INT32;
flags |= flag;
return;
}
if (flags & TYPE_FLAG_ANYOBJECT)
return;
if (type.isAnyObject())
goto unknownObject;
{
uint32_t objectCount = baseObjectCount();
ObjectKey* key = type.objectKey();
ObjectKey** pentry = TypeHashSet::Insert<ObjectKey*, ObjectKey, ObjectKey>
(*alloc, objectSet, objectCount, key);
if (!pentry)
goto unknownObject;
if (*pentry)
return;
*pentry = key;
setBaseObjectCount(objectCount);
// Limit the number of objects we track. There is a different limit
// depending on whether the set only contains DOM objects, which can
// have many different classes and prototypes but are still optimizable
// by IonMonkey.
if (objectCount >= TYPE_FLAG_OBJECT_COUNT_LIMIT) {
JS_STATIC_ASSERT(TYPE_FLAG_DOMOBJECT_COUNT_LIMIT >= TYPE_FLAG_OBJECT_COUNT_LIMIT);
// Examining the entire type set is only required when we first hit
// the normal object limit.
if (objectCount == TYPE_FLAG_OBJECT_COUNT_LIMIT) {
for (unsigned i = 0; i < objectCount; i++) {
const Class* clasp = getObjectClass(i);
if (clasp && !clasp->isDOMClass())
goto unknownObject;
}
}
// Make sure the newly added object is also a DOM object.
if (!key->clasp()->isDOMClass())
goto unknownObject;
// Limit the number of DOM objects.
if (objectCount == TYPE_FLAG_DOMOBJECT_COUNT_LIMIT)
goto unknownObject;
}
}
if (type.isGroup()) {
ObjectGroup* ngroup = type.group();
MOZ_ASSERT(!ngroup->singleton());
if (ngroup->unknownProperties())
goto unknownObject;
// If we add a partially initialized group to a type set, add the
// corresponding fully initialized group, as an object's group may change
// from the former to the latter via the acquired properties analysis.
if (ngroup->newScript() && ngroup->newScript()->initializedGroup())
addType(ObjectType(ngroup->newScript()->initializedGroup()), alloc);
}
if (false) {
unknownObject:
flags |= TYPE_FLAG_ANYOBJECT;
clearObjects();
}
}
// This class is used for post barriers on type set contents. The only times
// when type sets contain nursery references is when a nursery object has its
// group dynamically changed to a singleton. In such cases the type set will
// need to be traced at the next minor GC.
//
// There is no barrier used for TemporaryTypeSets. These type sets are only
// used during Ion compilation, and if some ConstraintTypeSet contains nursery
// pointers then any number of TemporaryTypeSets might as well. Thus, if there
// are any such ConstraintTypeSets in existence, all off thread Ion
// compilations are canceled by the next minor GC.
class TypeSetRef : public BufferableRef
{
Zone* zone;
ConstraintTypeSet* types;
public:
TypeSetRef(Zone* zone, ConstraintTypeSet* types)
: zone(zone), types(types)
{}
void trace(JSTracer* trc) override {
types->trace(zone, trc);
}
};
void
ConstraintTypeSet::postWriteBarrier(ExclusiveContext* cx, Type type)
{
if (type.isSingletonUnchecked() && IsInsideNursery(type.singletonNoBarrier())) {
JSRuntime* rt = cx->asJSContext()->runtime();
rt->gc.storeBuffer.putGeneric(TypeSetRef(cx->zone(), this));
rt->gc.storeBuffer.setShouldCancelIonCompilations();
}
}
void
ConstraintTypeSet::addType(ExclusiveContext* cxArg, Type type)
{
MOZ_ASSERT(cxArg->zone()->types.activeAnalysis);
if (hasType(type))
return;
TypeSet::addType(type, &cxArg->typeLifoAlloc());
if (type.isObjectUnchecked() && unknownObject())
type = AnyObjectType();
postWriteBarrier(cxArg, type);
InferSpew(ISpewOps, "addType: %sT%p%s %s",
InferSpewColor(this), this, InferSpewColorReset(),
TypeString(type));
/* Propagate the type to all constraints. */
if (JSContext* cx = cxArg->maybeJSContext()) {
TypeConstraint* constraint = constraintList;
while (constraint) {
constraint->newType(cx, this, type);
constraint = constraint->next;
}
} else {
MOZ_ASSERT(!constraintList);
}
}
void
TypeSet::print(FILE* fp)
{
if (!fp)
fp = stderr;
if (flags & TYPE_FLAG_NON_DATA_PROPERTY)
fprintf(fp, " [non-data]");
if (flags & TYPE_FLAG_NON_WRITABLE_PROPERTY)
fprintf(fp, " [non-writable]");
if (definiteProperty())
fprintf(fp, " [definite:%d]", definiteSlot());
if (baseFlags() == 0 && !baseObjectCount()) {
fprintf(fp, " missing");
return;
}
if (flags & TYPE_FLAG_UNKNOWN)
fprintf(fp, " unknown");
if (flags & TYPE_FLAG_ANYOBJECT)
fprintf(fp, " object");
if (flags & TYPE_FLAG_UNDEFINED)
fprintf(fp, " void");
if (flags & TYPE_FLAG_NULL)
fprintf(fp, " null");
if (flags & TYPE_FLAG_BOOLEAN)
fprintf(fp, " bool");
if (flags & TYPE_FLAG_INT32)
fprintf(fp, " int");
if (flags & TYPE_FLAG_DOUBLE)
fprintf(fp, " float");
if (flags & TYPE_FLAG_STRING)
fprintf(fp, " string");
if (flags & TYPE_FLAG_SYMBOL)
fprintf(fp, " symbol");
if (flags & TYPE_FLAG_LAZYARGS)
fprintf(fp, " lazyargs");
uint32_t objectCount = baseObjectCount();
if (objectCount) {
fprintf(fp, " object[%u]", objectCount);
unsigned count = getObjectCount();
for (unsigned i = 0; i < count; i++) {
ObjectKey* key = getObject(i);
if (key)
fprintf(fp, " %s", TypeString(ObjectType(key)));
}
}
}
/* static */ void
TypeSet::readBarrier(const TypeSet* types)
{
if (types->unknownObject())
return;
for (unsigned i = 0; i < types->getObjectCount(); i++) {
if (ObjectKey* key = types->getObject(i)) {
if (key->isSingleton())
(void) key->singleton();
else
(void) key->group();
}
}
}
/* static */ bool
TypeSet::IsTypeMarked(TypeSet::Type* v)
{
bool rv;
if (v->isSingletonUnchecked()) {
JSObject* obj = v->singletonNoBarrier();
rv = IsMarkedUnbarriered(&obj);
*v = TypeSet::ObjectType(obj);
} else if (v->isGroupUnchecked()) {
ObjectGroup* group = v->groupNoBarrier();
rv = IsMarkedUnbarriered(&group);
*v = TypeSet::ObjectType(group);
} else {
rv = true;
}
return rv;
}
/* static */ bool
TypeSet::IsTypeAllocatedDuringIncremental(TypeSet::Type v)
{
bool rv;
if (v.isSingletonUnchecked()) {
JSObject* obj = v.singletonNoBarrier();
rv = obj->isTenured() && obj->asTenured().arenaHeader()->allocatedDuringIncremental;
} else if (v.isGroupUnchecked()) {
ObjectGroup* group = v.groupNoBarrier();
rv = group->arenaHeader()->allocatedDuringIncremental;
} else {
rv = false;
}
return rv;
}
static inline bool
IsObjectKeyAboutToBeFinalized(TypeSet::ObjectKey** keyp)
{
TypeSet::ObjectKey* key = *keyp;
bool isAboutToBeFinalized;
if (key->isGroup()) {
ObjectGroup* group = key->groupNoBarrier();
isAboutToBeFinalized = IsAboutToBeFinalizedUnbarriered(&group);
if (!isAboutToBeFinalized)
*keyp = TypeSet::ObjectKey::get(group);
} else {
MOZ_ASSERT(key->isSingleton());
JSObject* singleton = key->singletonNoBarrier();
isAboutToBeFinalized = IsAboutToBeFinalizedUnbarriered(&singleton);
if (!isAboutToBeFinalized)
*keyp = TypeSet::ObjectKey::get(singleton);
}
return isAboutToBeFinalized;
}
bool
TypeSet::IsTypeAboutToBeFinalized(TypeSet::Type* v)
{
bool isAboutToBeFinalized;
if (v->isObjectUnchecked()) {
TypeSet::ObjectKey* key = v->objectKey();
isAboutToBeFinalized = IsObjectKeyAboutToBeFinalized(&key);
if (!isAboutToBeFinalized)
*v = TypeSet::ObjectType(key);
} else {
isAboutToBeFinalized = false;
}
return isAboutToBeFinalized;
}
bool
TypeSet::clone(LifoAlloc* alloc, TemporaryTypeSet* result) const
{
MOZ_ASSERT(result->empty());
unsigned objectCount = baseObjectCount();
unsigned capacity = (objectCount >= 2) ? TypeHashSet::Capacity(objectCount) : 0;
ObjectKey** newSet;
if (capacity) {
newSet = alloc->newArray<ObjectKey*>(capacity);
if (!newSet)
return false;
PodCopy(newSet, objectSet, capacity);
}
new(result) TemporaryTypeSet(flags, capacity ? newSet : objectSet);
return true;
}
TemporaryTypeSet*
TypeSet::clone(LifoAlloc* alloc) const
{
TemporaryTypeSet* res = alloc->new_<TemporaryTypeSet>();
if (!res || !clone(alloc, res))
return nullptr;
return res;
}
TemporaryTypeSet*
TypeSet::cloneObjectsOnly(LifoAlloc* alloc)
{
TemporaryTypeSet* res = clone(alloc);
if (!res)
return nullptr;
res->flags &= ~TYPE_FLAG_BASE_MASK | TYPE_FLAG_ANYOBJECT;
return res;
}
TemporaryTypeSet*
TypeSet::cloneWithoutObjects(LifoAlloc* alloc)
{
TemporaryTypeSet* res = alloc->new_<TemporaryTypeSet>();
if (!res)
return nullptr;
res->flags = flags & ~TYPE_FLAG_ANYOBJECT;
res->setBaseObjectCount(0);
return res;
}
/* static */ TemporaryTypeSet*
TypeSet::unionSets(TypeSet* a, TypeSet* b, LifoAlloc* alloc)
{
TemporaryTypeSet* res = alloc->new_<TemporaryTypeSet>(a->baseFlags() | b->baseFlags(),
static_cast<ObjectKey**>(nullptr));
if (!res)
return nullptr;
if (!res->unknownObject()) {
for (size_t i = 0; i < a->getObjectCount() && !res->unknownObject(); i++) {
if (ObjectKey* key = a->getObject(i))
res->addType(ObjectType(key), alloc);
}
for (size_t i = 0; i < b->getObjectCount() && !res->unknownObject(); i++) {
if (ObjectKey* key = b->getObject(i))
res->addType(ObjectType(key), alloc);
}
}
return res;
}
/* static */ TemporaryTypeSet*
TypeSet::removeSet(TemporaryTypeSet* input, TemporaryTypeSet* removal, LifoAlloc* alloc)
{
// Only allow removal of primitives and the "AnyObject" flag.
MOZ_ASSERT(!removal->unknown());
MOZ_ASSERT_IF(!removal->unknownObject(), removal->getObjectCount() == 0);
uint32_t flags = input->baseFlags() & ~removal->baseFlags();
TemporaryTypeSet* res =
alloc->new_<TemporaryTypeSet>(flags, static_cast<ObjectKey**>(nullptr));
if (!res)
return nullptr;
res->setBaseObjectCount(0);
if (removal->unknownObject() || input->unknownObject())
return res;
for (size_t i = 0; i < input->getObjectCount(); i++) {
if (!input->getObject(i))
continue;
res->addType(TypeSet::ObjectType(input->getObject(i)), alloc);
}
return res;
}
/* static */ TemporaryTypeSet*
TypeSet::intersectSets(TemporaryTypeSet* a, TemporaryTypeSet* b, LifoAlloc* alloc)
{
TemporaryTypeSet* res;
res = alloc->new_<TemporaryTypeSet>(a->baseFlags() & b->baseFlags(),
static_cast<ObjectKey**>(nullptr));
if (!res)
return nullptr;
res->setBaseObjectCount(0);
if (res->unknownObject())
return res;
MOZ_ASSERT(!a->unknownObject() || !b->unknownObject());
if (a->unknownObject()) {
for (size_t i = 0; i < b->getObjectCount(); i++) {
if (b->getObject(i))
res->addType(ObjectType(b->getObject(i)), alloc);
}
return res;
}
if (b->unknownObject()) {
for (size_t i = 0; i < a->getObjectCount(); i++) {
if (a->getObject(i))
res->addType(ObjectType(a->getObject(i)), alloc);
}
return res;
}
MOZ_ASSERT(!a->unknownObject() && !b->unknownObject());
for (size_t i = 0; i < a->getObjectCount(); i++) {
for (size_t j = 0; j < b->getObjectCount(); j++) {
if (b->getObject(j) != a->getObject(i))
continue;
if (!b->getObject(j))
continue;
res->addType(ObjectType(b->getObject(j)), alloc);
break;
}
}
return res;
}
/////////////////////////////////////////////////////////////////////
// Compiler constraints
/////////////////////////////////////////////////////////////////////
// Compiler constraints overview
//
// Constraints generated during Ion compilation capture assumptions made about
// heap properties that will trigger invalidation of the resulting Ion code if
// the constraint is violated. Constraints can only be attached to type sets on
// the main thread, so to allow compilation to occur almost entirely off thread
// the generation is split into two phases.
//
// During compilation, CompilerConstraint values are constructed in a list,
// recording the heap property type set which was read from and its expected
// contents, along with the assumption made about those contents.
//
// At the end of compilation, when linking the result on the main thread, the
// list of compiler constraints are read and converted to type constraints and
// attached to the type sets. If the property type sets have changed so that the
// assumptions no longer hold then the compilation is aborted and its result
// discarded.
// Superclass of all constraints generated during Ion compilation. These may
// be allocated off the main thread, using the current JIT context's allocator.
class CompilerConstraint
{
public:
// Property being queried by the compiler.
HeapTypeSetKey property;
// Contents of the property at the point when the query was performed. This
// may differ from the actual property types later in compilation as the
// main thread performs side effects.
TemporaryTypeSet* expected;
CompilerConstraint(LifoAlloc* alloc, const HeapTypeSetKey& property)
: property(property),
expected(property.maybeTypes() ? property.maybeTypes()->clone(alloc) : nullptr)
{}
// Generate the type constraint recording the assumption made by this
// compilation. Returns true if the assumption originally made still holds.
virtual bool generateTypeConstraint(JSContext* cx, RecompileInfo recompileInfo) = 0;
};
class js::CompilerConstraintList
{
public:
struct FrozenScript
{
JSScript* script;
TemporaryTypeSet* thisTypes;
TemporaryTypeSet* argTypes;
TemporaryTypeSet* bytecodeTypes;
};
private:
// OOM during generation of some constraint.
bool failed_;
// Allocator used for constraints.
LifoAlloc* alloc_;
// Constraints generated on heap properties.
Vector<CompilerConstraint*, 0, jit::JitAllocPolicy> constraints;
// Scripts whose stack type sets were frozen for the compilation.
Vector<FrozenScript, 1, jit::JitAllocPolicy> frozenScripts;
public:
explicit CompilerConstraintList(jit::TempAllocator& alloc)
: failed_(false),
alloc_(alloc.lifoAlloc()),
constraints(alloc),
frozenScripts(alloc)
{}
void add(CompilerConstraint* constraint) {
if (!constraint || !constraints.append(constraint))
setFailed();
}
void freezeScript(JSScript* script,
TemporaryTypeSet* thisTypes,
TemporaryTypeSet* argTypes,
TemporaryTypeSet* bytecodeTypes)
{
FrozenScript entry;
entry.script = script;
entry.thisTypes = thisTypes;
entry.argTypes = argTypes;
entry.bytecodeTypes = bytecodeTypes;
if (!frozenScripts.append(entry))
setFailed();
}
size_t length() {
return constraints.length();
}
CompilerConstraint* get(size_t i) {
return constraints[i];
}
size_t numFrozenScripts() {
return frozenScripts.length();
}
const FrozenScript& frozenScript(size_t i) {
return frozenScripts[i];
}
bool failed() {
return failed_;
}
void setFailed() {
failed_ = true;
}
LifoAlloc* alloc() const {
return alloc_;
}
};
CompilerConstraintList*
js::NewCompilerConstraintList(jit::TempAllocator& alloc)
{
return alloc.lifoAlloc()->new_<CompilerConstraintList>(alloc);
}
/* static */ bool
TypeScript::FreezeTypeSets(CompilerConstraintList* constraints, JSScript* script,
TemporaryTypeSet** pThisTypes,
TemporaryTypeSet** pArgTypes,
TemporaryTypeSet** pBytecodeTypes)
{
LifoAlloc* alloc = constraints->alloc();
StackTypeSet* existing = script->types()->typeArray();
size_t count = NumTypeSets(script);
TemporaryTypeSet* types = alloc->newArrayUninitialized<TemporaryTypeSet>(count);
if (!types)
return false;
PodZero(types, count);
for (size_t i = 0; i < count; i++) {
if (!existing[i].clone(alloc, &types[i]))
return false;
}
*pThisTypes = types + (ThisTypes(script) - existing);
*pArgTypes = (script->functionNonDelazifying() && script->functionNonDelazifying()->nargs())
? (types + (ArgTypes(script, 0) - existing))
: nullptr;
*pBytecodeTypes = types;
constraints->freezeScript(script, *pThisTypes, *pArgTypes, *pBytecodeTypes);
return true;
}
namespace {
template <typename T>
class CompilerConstraintInstance : public CompilerConstraint
{
T data;
public:
CompilerConstraintInstance<T>(LifoAlloc* alloc, const HeapTypeSetKey& property, const T& data)
: CompilerConstraint(alloc, property), data(data)
{}
bool generateTypeConstraint(JSContext* cx, RecompileInfo recompileInfo);
};
// Constraint generated from a CompilerConstraint when linking the compilation.
template <typename T>
class TypeCompilerConstraint : public TypeConstraint
{
// Compilation which this constraint may invalidate.
RecompileInfo compilation;
T data;
public:
TypeCompilerConstraint<T>(RecompileInfo compilation, const T& data)
: compilation(compilation), data(data)
{}
const char* kind() { return data.kind(); }
void newType(JSContext* cx, TypeSet* source, TypeSet::Type type) {
if (data.invalidateOnNewType(type))
cx->zone()->types.addPendingRecompile(cx, compilation);
}
void newPropertyState(JSContext* cx, TypeSet* source) {
if (data.invalidateOnNewPropertyState(source))
cx->zone()->types.addPendingRecompile(cx, compilation);
}
void newObjectState(JSContext* cx, ObjectGroup* group) {
// Note: Once the object has unknown properties, no more notifications
// will be sent on changes to its state, so always invalidate any
// associated compilations.
if (group->unknownProperties() || data.invalidateOnNewObjectState(group))
cx->zone()->types.addPendingRecompile(cx, compilation);
}
bool sweep(TypeZone& zone, TypeConstraint** res) {
if (data.shouldSweep() || compilation.shouldSweep(zone))
return false;
*res = zone.typeLifoAlloc.new_<TypeCompilerConstraint<T> >(compilation, data);
return true;
}
};
template <typename T>
bool
CompilerConstraintInstance<T>::generateTypeConstraint(JSContext* cx, RecompileInfo recompileInfo)
{
if (property.object()->unknownProperties())
return false;
if (!property.instantiate(cx))
return false;
if (!data.constraintHolds(cx, property, expected))
return false;
return property.maybeTypes()->addConstraint(cx, cx->typeLifoAlloc().new_<TypeCompilerConstraint<T> >(recompileInfo, data),
/* callExisting = */ false);
}
} /* anonymous namespace */
const Class*
TypeSet::ObjectKey::clasp()
{
return isGroup() ? group()->clasp() : singleton()->getClass();
}
TaggedProto
TypeSet::ObjectKey::proto()
{
return isGroup() ? group()->proto() : singleton()->getTaggedProto();
}
TypeNewScript*
TypeSet::ObjectKey::newScript()
{
if (isGroup() && group()->newScript())
return group()->newScript();
return nullptr;
}
ObjectGroup*
TypeSet::ObjectKey::maybeGroup()
{
if (isGroup())
return group();
if (!singleton()->hasLazyGroup())
return singleton()->group();
return nullptr;
}
bool
TypeSet::ObjectKey::unknownProperties()
{
if (ObjectGroup* group = maybeGroup())
return group->unknownProperties();
return false;
}
HeapTypeSetKey
TypeSet::ObjectKey::property(jsid id)
{
MOZ_ASSERT(!unknownProperties());
HeapTypeSetKey property;
property.object_ = this;
property.id_ = id;
if (ObjectGroup* group = maybeGroup())
property.maybeTypes_ = group->maybeGetProperty(id);
return property;
}
void
TypeSet::ObjectKey::ensureTrackedProperty(JSContext* cx, jsid id)
{
// If we are accessing a lazily defined property which actually exists in
// the VM and has not been instantiated yet, instantiate it now if we are
// on the main thread and able to do so.
if (!JSID_IS_VOID(id) && !JSID_IS_EMPTY(id)) {
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
if (isSingleton()) {
JSObject* obj = singleton();
if (obj->isNative() && obj->as<NativeObject>().containsPure(id))
EnsureTrackPropertyTypes(cx, obj, id);
}
}
}
void
js::EnsureTrackPropertyTypes(JSContext* cx, JSObject* obj, jsid id)
{
id = IdToTypeId(id);
if (obj->isSingleton()) {
AutoEnterAnalysis enter(cx);
if (obj->hasLazyGroup()) {
AutoEnterOOMUnsafeRegion oomUnsafe;
if (!obj->getGroup(cx)) {
oomUnsafe.crash("Could not allocate ObjectGroup in EnsureTrackPropertyTypes");
return;
}
}
if (!obj->group()->unknownProperties() && !obj->group()->getProperty(cx, obj, id)) {
MOZ_ASSERT(obj->group()->unknownProperties());
return;
}
}
MOZ_ASSERT(obj->group()->unknownProperties() || TrackPropertyTypes(cx, obj, id));
}
bool
HeapTypeSetKey::instantiate(JSContext* cx)
{
if (maybeTypes())
return true;
if (object()->isSingleton() && !object()->singleton()->getGroup(cx)) {
cx->clearPendingException();
return false;
}
JSObject* obj = object()->isSingleton() ? object()->singleton() : nullptr;
maybeTypes_ = object()->maybeGroup()->getProperty(cx, obj, id());
return maybeTypes_ != nullptr;
}
static bool
CheckFrozenTypeSet(JSContext* cx, TemporaryTypeSet* frozen, StackTypeSet* actual)
{
// Return whether the types frozen for a script during compilation are
// still valid. Also check for any new types added to the frozen set during
// compilation, and add them to the actual stack type sets. These new types
// indicate places where the compiler relaxed its possible inputs to be
// more tolerant of potential new types.
if (!actual->isSubset(frozen))
return false;
if (!frozen->isSubset(actual)) {
TypeSet::TypeList list;
frozen->enumerateTypes(&list);
for (size_t i = 0; i < list.length(); i++)
actual->addType(cx, list[i]);
}
return true;
}
namespace {
/*
* As for TypeConstraintFreeze, but describes an implicit freeze constraint
* added for stack types within a script. Applies to all compilations of the
* script, not just a single one.
*/
class TypeConstraintFreezeStack : public TypeConstraint
{
JSScript* script_;
public:
explicit TypeConstraintFreezeStack(JSScript* script)
: script_(script)
{}
const char* kind() { return "freezeStack"; }
void newType(JSContext* cx, TypeSet* source, TypeSet::Type type) {
/*
* Unlike TypeConstraintFreeze, triggering this constraint once does
* not disable it on future changes to the type set.
*/
cx->zone()->types.addPendingRecompile(cx, script_);
}
bool sweep(TypeZone& zone, TypeConstraint** res) {
if (IsAboutToBeFinalizedUnbarriered(&script_))
return false;
*res = zone.typeLifoAlloc.new_<TypeConstraintFreezeStack>(script_);
return true;
}
};
} /* anonymous namespace */
bool
js::FinishCompilation(JSContext* cx, HandleScript script, CompilerConstraintList* constraints,
RecompileInfo* precompileInfo, bool* isValidOut)
{
if (constraints->failed())
return false;
CompilerOutput co(script);
TypeZone& types = cx->zone()->types;
if (!types.compilerOutputs) {
types.compilerOutputs = cx->new_<TypeZone::CompilerOutputVector>();
if (!types.compilerOutputs)
return false;
}
#ifdef DEBUG
for (size_t i = 0; i < types.compilerOutputs->length(); i++) {
const CompilerOutput& co = (*types.compilerOutputs)[i];
MOZ_ASSERT_IF(co.isValid(), co.script() != script);
}
#endif
uint32_t index = types.compilerOutputs->length();
if (!types.compilerOutputs->append(co)) {
ReportOutOfMemory(cx);
return false;
}
*precompileInfo = RecompileInfo(index, types.generation);
bool succeeded = true;
for (size_t i = 0; i < constraints->length(); i++) {
CompilerConstraint* constraint = constraints->get(i);
if (!constraint->generateTypeConstraint(cx, *precompileInfo))
succeeded = false;
}
for (size_t i = 0; i < constraints->numFrozenScripts(); i++) {
const CompilerConstraintList::FrozenScript& entry = constraints->frozenScript(i);
if (!entry.script->types()) {
succeeded = false;
break;
}
// It could happen that one of the compiled scripts was made a
// debuggee mid-compilation (e.g., via setting a breakpoint). If so,
// throw away the compilation.
if (entry.script->isDebuggee()) {
succeeded = false;
break;
}
if (!CheckFrozenTypeSet(cx, entry.thisTypes, TypeScript::ThisTypes(entry.script)))
succeeded = false;
unsigned nargs = entry.script->functionNonDelazifying()
? entry.script->functionNonDelazifying()->nargs()
: 0;
for (size_t i = 0; i < nargs; i++) {
if (!CheckFrozenTypeSet(cx, &entry.argTypes[i], TypeScript::ArgTypes(entry.script, i)))
succeeded = false;
}
for (size_t i = 0; i < entry.script->nTypeSets(); i++) {
if (!CheckFrozenTypeSet(cx, &entry.bytecodeTypes[i], &entry.script->types()->typeArray()[i]))
succeeded = false;
}
// If necessary, add constraints to trigger invalidation on the script
// after any future changes to the stack type sets.
if (entry.script->hasFreezeConstraints())
continue;
entry.script->setHasFreezeConstraints();
size_t count = TypeScript::NumTypeSets(entry.script);
StackTypeSet* array = entry.script->types()->typeArray();
for (size_t i = 0; i < count; i++) {
if (!array[i].addConstraint(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeStack>(entry.script), false))
succeeded = false;
}
}
if (!succeeded || types.compilerOutputs->back().pendingInvalidation()) {
types.compilerOutputs->back().invalidate();
script->resetWarmUpCounter();
*isValidOut = false;
return true;
}
*isValidOut = true;
return true;
}
void
js::InvalidateCompilerOutputsForScript(JSContext* cx, HandleScript script)
{
TypeZone& types = cx->zone()->types;
if (types.compilerOutputs) {
for (auto& co : *types.compilerOutputs) {
if (co.script() == script)
co.invalidate();
}
}
}
static void
CheckDefinitePropertiesTypeSet(JSContext* cx, TemporaryTypeSet* frozen, StackTypeSet* actual)
{
// The definite properties analysis happens on the main thread, so no new
// types can have been added to actual. The analysis may have updated the
// contents of |frozen| though with new speculative types, and these need
// to be reflected in |actual| for AddClearDefiniteFunctionUsesInScript
// to work.
if (!frozen->isSubset(actual)) {
TypeSet::TypeList list;
frozen->enumerateTypes(&list);
for (size_t i = 0; i < list.length(); i++)
actual->addType(cx, list[i]);
}
}
void
js::FinishDefinitePropertiesAnalysis(JSContext* cx, CompilerConstraintList* constraints)
{
#ifdef DEBUG
// Assert no new types have been added to the StackTypeSets. Do this before
// calling CheckDefinitePropertiesTypeSet, as it may add new types to the
// StackTypeSets and break these invariants if a script is inlined more
// than once. See also CheckDefinitePropertiesTypeSet.
for (size_t i = 0; i < constraints->numFrozenScripts(); i++) {
const CompilerConstraintList::FrozenScript& entry = constraints->frozenScript(i);
JSScript* script = entry.script;
MOZ_ASSERT(script->types());
MOZ_ASSERT(TypeScript::ThisTypes(script)->isSubset(entry.thisTypes));
unsigned nargs = entry.script->functionNonDelazifying()
? entry.script->functionNonDelazifying()->nargs()
: 0;
for (size_t j = 0; j < nargs; j++)
MOZ_ASSERT(TypeScript::ArgTypes(script, j)->isSubset(&entry.argTypes[j]));
for (size_t j = 0; j < script->nTypeSets(); j++)
MOZ_ASSERT(script->types()->typeArray()[j].isSubset(&entry.bytecodeTypes[j]));
}
#endif
for (size_t i = 0; i < constraints->numFrozenScripts(); i++) {
const CompilerConstraintList::FrozenScript& entry = constraints->frozenScript(i);
JSScript* script = entry.script;
if (!script->types())
MOZ_CRASH();
CheckDefinitePropertiesTypeSet(cx, entry.thisTypes, TypeScript::ThisTypes(script));
unsigned nargs = script->functionNonDelazifying()
? script->functionNonDelazifying()->nargs()
: 0;
for (size_t j = 0; j < nargs; j++)
CheckDefinitePropertiesTypeSet(cx, &entry.argTypes[j], TypeScript::ArgTypes(script, j));
for (size_t j = 0; j < script->nTypeSets(); j++)
CheckDefinitePropertiesTypeSet(cx, &entry.bytecodeTypes[j], &script->types()->typeArray()[j]);
}
}
namespace {
// Constraint which triggers recompilation of a script if any type is added to a type set. */
class ConstraintDataFreeze
{
public:
ConstraintDataFreeze() {}
const char* kind() { return "freeze"; }
bool invalidateOnNewType(TypeSet::Type type) { return true; }
bool invalidateOnNewPropertyState(TypeSet* property) { return true; }
bool invalidateOnNewObjectState(ObjectGroup* group) { return false; }
bool constraintHolds(JSContext* cx,
const HeapTypeSetKey& property, TemporaryTypeSet* expected)
{
return expected
? property.maybeTypes()->isSubset(expected)
: property.maybeTypes()->empty();
}
bool shouldSweep() { return false; }
};
} /* anonymous namespace */
void
HeapTypeSetKey::freeze(CompilerConstraintList* constraints)
{
LifoAlloc* alloc = constraints->alloc();
typedef CompilerConstraintInstance<ConstraintDataFreeze> T;
constraints->add(alloc->new_<T>(alloc, *this, ConstraintDataFreeze()));
}
static inline jit::MIRType
GetMIRTypeFromTypeFlags(TypeFlags flags)
{
switch (flags) {
case TYPE_FLAG_UNDEFINED:
return jit::MIRType_Undefined;
case TYPE_FLAG_NULL:
return jit::MIRType_Null;
case TYPE_FLAG_BOOLEAN:
return jit::MIRType_Boolean;
case TYPE_FLAG_INT32:
return jit::MIRType_Int32;
case (TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE):
return jit::MIRType_Double;
case TYPE_FLAG_STRING:
return jit::MIRType_String;
case TYPE_FLAG_SYMBOL:
return jit::MIRType_Symbol;
case TYPE_FLAG_LAZYARGS:
return jit::MIRType_MagicOptimizedArguments;
case TYPE_FLAG_ANYOBJECT:
return jit::MIRType_Object;
default:
return jit::MIRType_Value;
}
}
jit::MIRType
TemporaryTypeSet::getKnownMIRType()
{
TypeFlags flags = baseFlags();
jit::MIRType type;
if (baseObjectCount())
type = flags ? jit::MIRType_Value : jit::MIRType_Object;
else
type = GetMIRTypeFromTypeFlags(flags);
/*
* If the type set is totally empty then it will be treated as unknown,
* but we still need to record the dependency as adding a new type can give
* it a definite type tag. This is not needed if there are enough types
* that the exact tag is unknown, as it will stay unknown as more types are
* added to the set.
*/
DebugOnly<bool> empty = flags == 0 && baseObjectCount() == 0;
MOZ_ASSERT_IF(empty, type == jit::MIRType_Value);
return type;
}
jit::MIRType
HeapTypeSetKey::knownMIRType(CompilerConstraintList* constraints)
{
TypeSet* types = maybeTypes();
if (!types || types->unknown())
return jit::MIRType_Value;
TypeFlags flags = types->baseFlags() & ~TYPE_FLAG_ANYOBJECT;
jit::MIRType type;
if (types->unknownObject() || types->getObjectCount())
type = flags ? jit::MIRType_Value : jit::MIRType_Object;
else
type = GetMIRTypeFromTypeFlags(flags);
if (type != jit::MIRType_Value)
freeze(constraints);
/*
* If the type set is totally empty then it will be treated as unknown,
* but we still need to record the dependency as adding a new type can give
* it a definite type tag. This is not needed if there are enough types
* that the exact tag is unknown, as it will stay unknown as more types are
* added to the set.
*/
MOZ_ASSERT_IF(types->empty(), type == jit::MIRType_Value);
return type;
}
bool
HeapTypeSetKey::isOwnProperty(CompilerConstraintList* constraints,
bool allowEmptyTypesForGlobal/* = false*/)
{
if (maybeTypes() && (!maybeTypes()->empty() || maybeTypes()->nonDataProperty()))
return true;
if (object()->isSingleton()) {
JSObject* obj = object()->singleton();
MOZ_ASSERT(CanHaveEmptyPropertyTypesForOwnProperty(obj) == obj->is<GlobalObject>());
if (!allowEmptyTypesForGlobal) {
if (CanHaveEmptyPropertyTypesForOwnProperty(obj))
return true;
}
}
freeze(constraints);
return false;
}
bool
HeapTypeSetKey::knownSubset(CompilerConstraintList* constraints, const HeapTypeSetKey& other)
{
if (!maybeTypes() || maybeTypes()->empty()) {
freeze(constraints);
return true;
}
if (!other.maybeTypes() || !maybeTypes()->isSubset(other.maybeTypes()))
return false;
freeze(constraints);
return true;
}
JSObject*
TemporaryTypeSet::maybeSingleton()
{
if (baseFlags() != 0 || baseObjectCount() != 1)
return nullptr;
return getSingleton(0);
}
JSObject*
HeapTypeSetKey::singleton(CompilerConstraintList* constraints)
{
HeapTypeSet* types = maybeTypes();
if (!types || types->nonDataProperty() || types->baseFlags() != 0 || types->getObjectCount() != 1)
return nullptr;
JSObject* obj = types->getSingleton(0);
if (obj)
freeze(constraints);
return obj;
}
bool
HeapTypeSetKey::needsBarrier(CompilerConstraintList* constraints)
{
TypeSet* types = maybeTypes();
if (!types)
return false;
bool result = types->unknownObject()
|| types->getObjectCount() > 0
|| types->hasAnyFlag(TYPE_FLAG_STRING | TYPE_FLAG_SYMBOL);
if (!result)
freeze(constraints);
return result;
}
namespace {
// Constraint which triggers recompilation if an object acquires particular flags.
class ConstraintDataFreezeObjectFlags
{
public:
// Flags we are watching for on this object.
ObjectGroupFlags flags;
explicit ConstraintDataFreezeObjectFlags(ObjectGroupFlags flags)
: flags(flags)
{
MOZ_ASSERT(flags);
}
const char* kind() { return "freezeObjectFlags"; }
bool invalidateOnNewType(TypeSet::Type type) { return false; }
bool invalidateOnNewPropertyState(TypeSet* property) { return false; }
bool invalidateOnNewObjectState(ObjectGroup* group) {
return group->hasAnyFlags(flags);
}
bool constraintHolds(JSContext* cx,
const HeapTypeSetKey& property, TemporaryTypeSet* expected)
{
return !invalidateOnNewObjectState(property.object()->maybeGroup());
}
bool shouldSweep() { return false; }
};
} /* anonymous namespace */
bool
TypeSet::ObjectKey::hasFlags(CompilerConstraintList* constraints, ObjectGroupFlags flags)
{
MOZ_ASSERT(flags);
if (ObjectGroup* group = maybeGroup()) {
if (group->hasAnyFlags(flags))
return true;
}
HeapTypeSetKey objectProperty = property(JSID_EMPTY);
LifoAlloc* alloc = constraints->alloc();
typedef CompilerConstraintInstance<ConstraintDataFreezeObjectFlags> T;
constraints->add(alloc->new_<T>(alloc, objectProperty, ConstraintDataFreezeObjectFlags(flags)));
return false;
}
bool
TypeSet::ObjectKey::hasStableClassAndProto(CompilerConstraintList* constraints)
{
return !hasFlags(constraints, OBJECT_FLAG_UNKNOWN_PROPERTIES);
}
bool
TemporaryTypeSet::hasObjectFlags(CompilerConstraintList* constraints, ObjectGroupFlags flags)
{
if (unknownObject())
return true;
/*
* Treat type sets containing no objects as having all object flags,
* to spare callers from having to check this.
*/
if (baseObjectCount() == 0)
return true;
unsigned count = getObjectCount();
for (unsigned i = 0; i < count; i++) {
ObjectKey* key = getObject(i);
if (key && key->hasFlags(constraints, flags))
return true;
}
return false;
}
gc::InitialHeap
ObjectGroup::initialHeap(CompilerConstraintList* constraints)
{
// If this object is not required to be pretenured but could be in the
// future, add a constraint to trigger recompilation if the requirement
// changes.
if (shouldPreTenure())
return gc::TenuredHeap;
if (!canPreTenure())
return gc::DefaultHeap;
HeapTypeSetKey objectProperty = TypeSet::ObjectKey::get(this)->property(JSID_EMPTY);
LifoAlloc* alloc = constraints->alloc();
typedef CompilerConstraintInstance<ConstraintDataFreezeObjectFlags> T;
constraints->add(alloc->new_<T>(alloc, objectProperty, ConstraintDataFreezeObjectFlags(OBJECT_FLAG_PRE_TENURE)));
return gc::DefaultHeap;
}
namespace {
// Constraint which triggers recompilation on any type change in an inlined
// script. The freeze constraints added to stack type sets will only directly
// invalidate the script containing those stack type sets. To invalidate code
// for scripts into which the base script was inlined, ObjectStateChange is used.
class ConstraintDataFreezeObjectForInlinedCall
{
public:
ConstraintDataFreezeObjectForInlinedCall()
{}
const char* kind() { return "freezeObjectForInlinedCall"; }
bool invalidateOnNewType(TypeSet::Type type) { return false; }
bool invalidateOnNewPropertyState(TypeSet* property) { return false; }
bool invalidateOnNewObjectState(ObjectGroup* group) {
// We don't keep track of the exact dependencies the caller has on its
// inlined scripts' type sets, so always invalidate the caller.
return true;
}
bool constraintHolds(JSContext* cx,
const HeapTypeSetKey& property, TemporaryTypeSet* expected)
{
return true;
}
bool shouldSweep() { return false; }
};
// Constraint which triggers recompilation when a typed array's data becomes
// invalid.
class ConstraintDataFreezeObjectForTypedArrayData
{
NativeObject* obj;
uintptr_t viewData;
uint32_t length;
public:
explicit ConstraintDataFreezeObjectForTypedArrayData(TypedArrayObject& tarray)
: obj(&tarray),
viewData(tarray.viewDataEither().unwrapValue()),
length(tarray.length())
{
MOZ_ASSERT(tarray.isSingleton());
}
const char* kind() { return "freezeObjectForTypedArrayData"; }
bool invalidateOnNewType(TypeSet::Type type) { return false; }
bool invalidateOnNewPropertyState(TypeSet* property) { return false; }
bool invalidateOnNewObjectState(ObjectGroup* group) {
MOZ_ASSERT(obj->group() == group);
TypedArrayObject& tarr = obj->as<TypedArrayObject>();
return tarr.viewDataEither().unwrapValue() != viewData || tarr.length() != length;
}
bool constraintHolds(JSContext* cx,
const HeapTypeSetKey& property, TemporaryTypeSet* expected)
{
return !invalidateOnNewObjectState(property.object()->maybeGroup());
}
bool shouldSweep() {
// Note: |viewData| is only used for equality testing.
return IsAboutToBeFinalizedUnbarriered(&obj);
}
};
// Constraint which triggers recompilation if an unboxed object in some group
// is converted to a native object.
class ConstraintDataFreezeObjectForUnboxedConvertedToNative
{
public:
ConstraintDataFreezeObjectForUnboxedConvertedToNative()
{}
const char* kind() { return "freezeObjectForUnboxedConvertedToNative"; }
bool invalidateOnNewType(TypeSet::Type type) { return false; }
bool invalidateOnNewPropertyState(TypeSet* property) { return false; }
bool invalidateOnNewObjectState(ObjectGroup* group) {
return group->unboxedLayout().nativeGroup() != nullptr;
}
bool constraintHolds(JSContext* cx,
const HeapTypeSetKey& property, TemporaryTypeSet* expected)
{
return !invalidateOnNewObjectState(property.object()->maybeGroup());
}
bool shouldSweep() { return false; }
};
} /* anonymous namespace */
void
TypeSet::ObjectKey::watchStateChangeForInlinedCall(CompilerConstraintList* constraints)
{
HeapTypeSetKey objectProperty = property(JSID_EMPTY);
LifoAlloc* alloc = constraints->alloc();
typedef CompilerConstraintInstance<ConstraintDataFreezeObjectForInlinedCall> T;
constraints->add(alloc->new_<T>(alloc, objectProperty, ConstraintDataFreezeObjectForInlinedCall()));
}
void
TypeSet::ObjectKey::watchStateChangeForTypedArrayData(CompilerConstraintList* constraints)
{
TypedArrayObject& tarray = singleton()->as<TypedArrayObject>();
HeapTypeSetKey objectProperty = property(JSID_EMPTY);
LifoAlloc* alloc = constraints->alloc();
typedef CompilerConstraintInstance<ConstraintDataFreezeObjectForTypedArrayData> T;
constraints->add(alloc->new_<T>(alloc, objectProperty,
ConstraintDataFreezeObjectForTypedArrayData(tarray)));
}
void
TypeSet::ObjectKey::watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList* constraints)
{
HeapTypeSetKey objectProperty = property(JSID_EMPTY);
LifoAlloc* alloc = constraints->alloc();
typedef CompilerConstraintInstance<ConstraintDataFreezeObjectForUnboxedConvertedToNative> T;
constraints->add(alloc->new_<T>(alloc, objectProperty,
ConstraintDataFreezeObjectForUnboxedConvertedToNative()));
}
static void
ObjectStateChange(ExclusiveContext* cxArg, ObjectGroup* group, bool markingUnknown)
{
if (group->unknownProperties())
return;
/* All constraints listening to state changes are on the empty id. */
HeapTypeSet* types = group->maybeGetProperty(JSID_EMPTY);
/* Mark as unknown after getting the types, to avoid assertion. */
if (markingUnknown)
group->addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES);
if (types) {
if (JSContext* cx = cxArg->maybeJSContext()) {
TypeConstraint* constraint = types->constraintList;
while (constraint) {
constraint->newObjectState(cx, group);
constraint = constraint->next;
}
} else {
MOZ_ASSERT(!types->constraintList);
}
}
}
namespace {
class ConstraintDataFreezePropertyState
{
public:
enum Which {
NON_DATA,
NON_WRITABLE
} which;
explicit ConstraintDataFreezePropertyState(Which which)
: which(which)
{}
const char* kind() { return (which == NON_DATA) ? "freezeNonDataProperty" : "freezeNonWritableProperty"; }
bool invalidateOnNewType(TypeSet::Type type) { return false; }
bool invalidateOnNewPropertyState(TypeSet* property) {
return (which == NON_DATA)
? property->nonDataProperty()
: property->nonWritableProperty();
}
bool invalidateOnNewObjectState(ObjectGroup* group) { return false; }
bool constraintHolds(JSContext* cx,
const HeapTypeSetKey& property, TemporaryTypeSet* expected)
{
return !invalidateOnNewPropertyState(property.maybeTypes());
}
bool shouldSweep() { return false; }
};
} /* anonymous namespace */
bool
HeapTypeSetKey::nonData(CompilerConstraintList* constraints)
{
if (maybeTypes() && maybeTypes()->nonDataProperty())
return true;
LifoAlloc* alloc = constraints->alloc();
typedef CompilerConstraintInstance<ConstraintDataFreezePropertyState> T;
constraints->add(alloc->new_<T>(alloc, *this,
ConstraintDataFreezePropertyState(ConstraintDataFreezePropertyState::NON_DATA)));
return false;
}
bool
HeapTypeSetKey::nonWritable(CompilerConstraintList* constraints)
{
if (maybeTypes() && maybeTypes()->nonWritableProperty())
return true;
LifoAlloc* alloc = constraints->alloc();
typedef CompilerConstraintInstance<ConstraintDataFreezePropertyState> T;
constraints->add(alloc->new_<T>(alloc, *this,
ConstraintDataFreezePropertyState(ConstraintDataFreezePropertyState::NON_WRITABLE)));
return false;
}
namespace {
class ConstraintDataConstantProperty
{
public:
explicit ConstraintDataConstantProperty() {}
const char* kind() { return "constantProperty"; }
bool invalidateOnNewType(TypeSet::Type type) { return false; }
bool invalidateOnNewPropertyState(TypeSet* property) {
return property->nonConstantProperty();
}
bool invalidateOnNewObjectState(ObjectGroup* group) { return false; }
bool constraintHolds(JSContext* cx,
const HeapTypeSetKey& property, TemporaryTypeSet* expected)
{
return !invalidateOnNewPropertyState(property.maybeTypes());
}
bool shouldSweep() { return false; }
};
} /* anonymous namespace */
bool
HeapTypeSetKey::constant(CompilerConstraintList* constraints, Value* valOut)
{
if (nonData(constraints))
return false;
// Only singleton object properties can be marked as constants.
JSObject* obj = object()->singleton();
if (!obj || !obj->isNative())
return false;
if (maybeTypes() && maybeTypes()->nonConstantProperty())
return false;
// Get the current value of the property.
Shape* shape = obj->as<NativeObject>().lookupPure(id());
if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot() || shape->hadOverwrite())
return false;
Value val = obj->as<NativeObject>().getSlot(shape->slot());
// If the value is a pointer to an object in the nursery, don't optimize.
if (val.isGCThing() && IsInsideNursery(val.toGCThing()))
return false;
// If the value is a string that's not atomic, don't optimize.
if (val.isString() && !val.toString()->isAtom())
return false;
*valOut = val;
LifoAlloc* alloc = constraints->alloc();
typedef CompilerConstraintInstance<ConstraintDataConstantProperty> T;
constraints->add(alloc->new_<T>(alloc, *this, ConstraintDataConstantProperty()));
return true;
}
// A constraint that never triggers recompilation.
class ConstraintDataInert
{
public:
explicit ConstraintDataInert() {}
const char* kind() { return "inert"; }
bool invalidateOnNewType(TypeSet::Type type) { return false; }
bool invalidateOnNewPropertyState(TypeSet* property) { return false; }
bool invalidateOnNewObjectState(ObjectGroup* group) { return false; }
bool constraintHolds(JSContext* cx,
const HeapTypeSetKey& property, TemporaryTypeSet* expected)
{
return true;
}
bool shouldSweep() { return false; }
};
bool
HeapTypeSetKey::couldBeConstant(CompilerConstraintList* constraints)
{
// Only singleton object properties can be marked as constants.
if (!object()->isSingleton())
return false;
if (!maybeTypes() || !maybeTypes()->nonConstantProperty())
return true;
// It is possible for a property that was not marked as constant to
// 'become' one, if we throw away the type property during a GC and
// regenerate it with the constant flag set. ObjectGroup::sweep only removes
// type properties if they have no constraints attached to them, so add
// inert constraints to pin these properties in place.
LifoAlloc* alloc = constraints->alloc();
typedef CompilerConstraintInstance<ConstraintDataInert> T;
constraints->add(alloc->new_<T>(alloc, *this, ConstraintDataInert()));
return false;
}
bool
TemporaryTypeSet::filtersType(const TemporaryTypeSet* other, Type filteredType) const
{
if (other->unknown())
return unknown();
for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) {
Type type = PrimitiveType(TypeFlagPrimitive(flag));
if (type != filteredType && other->hasType(type) && !hasType(type))
return false;
}
if (other->unknownObject())
return unknownObject();
for (size_t i = 0; i < other->getObjectCount(); i++) {
ObjectKey* key = other->getObject(i);
if (key) {
Type type = ObjectType(key);
if (type != filteredType && !hasType(type))
return false;
}
}
return true;
}
TemporaryTypeSet::DoubleConversion
TemporaryTypeSet::convertDoubleElements(CompilerConstraintList* constraints)
{
if (unknownObject() || !getObjectCount())
return AmbiguousDoubleConversion;
bool alwaysConvert = true;
bool maybeConvert = false;
bool dontConvert = false;
for (unsigned i = 0; i < getObjectCount(); i++) {
ObjectKey* key = getObject(i);
if (!key)
continue;
if (key->unknownProperties()) {
alwaysConvert = false;
continue;
}
HeapTypeSetKey property = key->property(JSID_VOID);
property.freeze(constraints);
// We can't convert to double elements for objects which do not have
// double in their element types (as the conversion may render the type
// information incorrect), nor for non-array objects (as their elements
// may point to emptyObjectElements or emptyObjectElementsShared, which
// cannot be converted).
if (!property.maybeTypes() ||
!property.maybeTypes()->hasType(DoubleType()) ||
key->clasp() != &ArrayObject::class_)
{
dontConvert = true;
alwaysConvert = false;
continue;
}
// Only bother with converting known packed arrays whose possible
// element types are int or double. Other arrays require type tests
// when elements are accessed regardless of the conversion.
if (property.knownMIRType(constraints) == jit::MIRType_Double &&
!key->hasFlags(constraints, OBJECT_FLAG_NON_PACKED))
{
maybeConvert = true;
} else {
alwaysConvert = false;
}
}
MOZ_ASSERT_IF(alwaysConvert, maybeConvert);
if (maybeConvert && dontConvert)
return AmbiguousDoubleConversion;
if (alwaysConvert)
return AlwaysConvertToDoubles;
if (maybeConvert)
return MaybeConvertToDoubles;
return DontConvertToDoubles;
}
const Class*
TemporaryTypeSet::getKnownClass(CompilerConstraintList* constraints)
{
if (unknownObject())
return nullptr;
const Class* clasp = nullptr;
unsigned count = getObjectCount();
for (unsigned i = 0; i < count; i++) {
const Class* nclasp = getObjectClass(i);
if (!nclasp)
continue;
if (getObject(i)->unknownProperties())
return nullptr;
if (clasp && clasp != nclasp)
return nullptr;
clasp = nclasp;
}
if (clasp) {
for (unsigned i = 0; i < count; i++) {
ObjectKey* key = getObject(i);
if (key && !key->hasStableClassAndProto(constraints))
return nullptr;
}
}
return clasp;
}
void
TemporaryTypeSet::getTypedArraySharedness(CompilerConstraintList* constraints,
TypedArraySharedness* sharedness)
{
// In the future this will inspect the object set.
*sharedness = UnknownSharedness;
}
TemporaryTypeSet::ForAllResult
TemporaryTypeSet::forAllClasses(CompilerConstraintList* constraints,
bool (*func)(const Class* clasp))
{
if (unknownObject())
return ForAllResult::MIXED;
unsigned count = getObjectCount();
if (count == 0)
return ForAllResult::EMPTY;
bool true_results = false;
bool false_results = false;
for (unsigned i = 0; i < count; i++) {
const Class* clasp = getObjectClass(i);
if (!clasp)
continue;
if (!getObject(i)->hasStableClassAndProto(constraints))
return ForAllResult::MIXED;
if (func(clasp)) {
true_results = true;
if (false_results)
return ForAllResult::MIXED;
}
else {
false_results = true;
if (true_results)
return ForAllResult::MIXED;
}
}
MOZ_ASSERT(true_results != false_results);
return true_results ? ForAllResult::ALL_TRUE : ForAllResult::ALL_FALSE;
}
Scalar::Type
TemporaryTypeSet::getTypedArrayType(CompilerConstraintList* constraints,
TypedArraySharedness* sharedness)
{
const Class* clasp = getKnownClass(constraints);
if (clasp && IsTypedArrayClass(clasp)) {
if (sharedness)
getTypedArraySharedness(constraints, sharedness);
return (Scalar::Type) (clasp - &TypedArrayObject::classes[0]);
}
return Scalar::MaxTypedArrayViewType;
}
bool
TemporaryTypeSet::isDOMClass(CompilerConstraintList* constraints)
{
if (unknownObject())
return false;
unsigned count = getObjectCount();
for (unsigned i = 0; i < count; i++) {
const Class* clasp = getObjectClass(i);
if (!clasp)
continue;
if (!clasp->isDOMClass() || !getObject(i)->hasStableClassAndProto(constraints))
return false;
}
return count > 0;
}
bool
TemporaryTypeSet::maybeCallable(CompilerConstraintList* constraints)
{
if (!maybeObject())
return false;
if (unknownObject())
return true;
unsigned count = getObjectCount();
for (unsigned i = 0; i < count; i++) {
const Class* clasp = getObjectClass(i);
if (!clasp)
continue;
if (clasp->isProxy() || clasp->nonProxyCallable())
return true;
if (!getObject(i)->hasStableClassAndProto(constraints))
return true;
}
return false;
}
bool
TemporaryTypeSet::maybeEmulatesUndefined(CompilerConstraintList* constraints)
{
if (!maybeObject())
return false;
if (unknownObject())
return true;
unsigned count = getObjectCount();
for (unsigned i = 0; i < count; i++) {
// The object emulates undefined if clasp->emulatesUndefined() or if
// it's a WrapperObject, see EmulatesUndefined. Since all wrappers are
// proxies, we can just check for that.
const Class* clasp = getObjectClass(i);
if (!clasp)
continue;
if (clasp->emulatesUndefined() || clasp->isProxy())
return true;
if (!getObject(i)->hasStableClassAndProto(constraints))
return true;
}
return false;
}
bool
TemporaryTypeSet::getCommonPrototype(CompilerConstraintList* constraints, JSObject** proto)
{
if (unknownObject())
return false;
*proto = nullptr;
bool isFirst = true;
unsigned count = getObjectCount();
for (unsigned i = 0; i < count; i++) {
ObjectKey* key = getObject(i);
if (!key)
continue;
if (key->unknownProperties())
return false;
TaggedProto nproto = key->proto();
if (isFirst) {
if (nproto.isLazy())
return false;
*proto = nproto.toObjectOrNull();
isFirst = false;
} else {
if (nproto != TaggedProto(*proto))
return false;
}
}
// Guard against mutating __proto__.
for (unsigned i = 0; i < count; i++) {
if (ObjectKey* key = getObject(i))
JS_ALWAYS_TRUE(key->hasStableClassAndProto(constraints));
}
return true;
}
bool
TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList* constraints, jsid id)
{
if (unknownObject())
return true;
for (unsigned i = 0; i < getObjectCount(); i++) {
ObjectKey* key = getObject(i);
if (!key)
continue;
if (key->unknownProperties())
return true;
HeapTypeSetKey property = key->property(id);
if (property.needsBarrier(constraints))
return true;
}
return false;
}
bool
js::ClassCanHaveExtraProperties(const Class* clasp)
{
if (clasp == &UnboxedPlainObject::class_ || clasp == &UnboxedArrayObject::class_)
return false;
return clasp->resolve
|| clasp->ops.lookupProperty
|| clasp->ops.getProperty
|| IsAnyTypedArrayClass(clasp);
}
void
TypeZone::processPendingRecompiles(FreeOp* fop, RecompileInfoVector& recompiles)
{
MOZ_ASSERT(!recompiles.empty());
/*
* Steal the list of scripts to recompile, to make sure we don't try to
* recursively recompile them.
*/
RecompileInfoVector pending;
for (size_t i = 0; i < recompiles.length(); i++) {
AutoEnterOOMUnsafeRegion oomUnsafe;
if (!pending.append(recompiles[i]))
oomUnsafe.crash("processPendingRecompiles");
}
recompiles.clear();
jit::Invalidate(*this, fop, pending);
MOZ_ASSERT(recompiles.empty());
}
void
TypeZone::addPendingRecompile(JSContext* cx, const RecompileInfo& info)
{
CompilerOutput* co = info.compilerOutput(cx);
if (!co || !co->isValid() || co->pendingInvalidation())
return;
InferSpew(ISpewOps, "addPendingRecompile: %p:%s:%" PRIuSIZE,
co->script(), co->script()->filename(), co->script()->lineno());
co->setPendingInvalidation();
AutoEnterOOMUnsafeRegion oomUnsafe;
if (!cx->zone()->types.activeAnalysis->pendingRecompiles.append(info))
oomUnsafe.crash("Could not update pendingRecompiles");
}
void
TypeZone::addPendingRecompile(JSContext* cx, JSScript* script)
{
MOZ_ASSERT(script);
CancelOffThreadIonCompile(cx->compartment(), script);
// Let the script warm up again before attempting another compile.
if (jit::IsBaselineEnabled(cx))
script->resetWarmUpCounter();
if (script->hasIonScript())
addPendingRecompile(cx, script->ionScript()->recompileInfo());
// When one script is inlined into another the caller listens to state
// changes on the callee's script, so trigger these to force recompilation
// of any such callers.
if (script->functionNonDelazifying() && !script->functionNonDelazifying()->hasLazyGroup())
ObjectStateChange(cx, script->functionNonDelazifying()->group(), false);
}
void
js::PrintTypes(JSContext* cx, JSCompartment* comp, bool force)
{
#ifdef DEBUG
gc::AutoSuppressGC suppressGC(cx);
JSAutoRequest request(cx);
Zone* zone = comp->zone();
AutoEnterAnalysis enter(nullptr, zone);
if (!force && !InferSpewActive(ISpewResult))
return;
for (gc::ZoneCellIter i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next()) {
RootedScript script(cx, i.get<JSScript>());
if (script->types())
script->types()->printTypes(cx, script);
}
for (gc::ZoneCellIter i(zone, gc::AllocKind::OBJECT_GROUP); !i.done(); i.next()) {
ObjectGroup* group = i.get<ObjectGroup>();
group->print();
}
#endif
}
/////////////////////////////////////////////////////////////////////
// ObjectGroup
/////////////////////////////////////////////////////////////////////
static inline void
UpdatePropertyType(ExclusiveContext* cx, HeapTypeSet* types, NativeObject* obj, Shape* shape,
bool indexed)
{
MOZ_ASSERT(obj->isSingleton() && !obj->hasLazyGroup());
if (!shape->writable())
types->setNonWritableProperty(cx);
if (shape->hasGetterValue() || shape->hasSetterValue()) {
types->setNonDataProperty(cx);
types->TypeSet::addType(TypeSet::UnknownType(), &cx->typeLifoAlloc());
} else if (shape->hasDefaultGetter() && shape->hasSlot()) {
if (!indexed && types->canSetDefinite(shape->slot()))
types->setDefinite(shape->slot());
const Value& value = obj->getSlot(shape->slot());
/*
* Don't add initial undefined types for properties of global objects
* that are not collated into the JSID_VOID property (see propertySet
* comment).
*
* Also don't add untracked values (initial uninitialized lexical magic
* values and optimized out values) as appearing in CallObjects, module
* environments or the global lexical scope.
*/
MOZ_ASSERT_IF(TypeSet::IsUntrackedValue(value),
obj->is<LexicalScopeBase>() || IsExtensibleLexicalScope(obj));
if ((indexed || !value.isUndefined() || !CanHaveEmptyPropertyTypesForOwnProperty(obj)) &&
!TypeSet::IsUntrackedValue(value))
{
TypeSet::Type type = TypeSet::GetValueType(value);
types->TypeSet::addType(type, &cx->typeLifoAlloc());
types->postWriteBarrier(cx, type);
}
if (indexed || shape->hadOverwrite()) {
types->setNonConstantProperty(cx);
} else {
InferSpew(ISpewOps, "typeSet: %sT%p%s property %s %s - setConstant",
InferSpewColor(types), types, InferSpewColorReset(),
TypeSet::ObjectGroupString(obj->group()), TypeIdString(shape->propid()));
}
}
}
void
ObjectGroup::updateNewPropertyTypes(ExclusiveContext* cx, JSObject* objArg, jsid id, HeapTypeSet* types)
{
InferSpew(ISpewOps, "typeSet: %sT%p%s property %s %s",
InferSpewColor(types), types, InferSpewColorReset(),
TypeSet::ObjectGroupString(this), TypeIdString(id));
MOZ_ASSERT_IF(objArg, objArg->group() == this);
MOZ_ASSERT_IF(singleton(), objArg);
if (!singleton() || !objArg->isNative()) {
types->setNonConstantProperty(cx);
return;
}
NativeObject* obj = &objArg->as<NativeObject>();
/*
* Fill the property in with any type the object already has in an own
* property. We are only interested in plain native properties and
* dense elements which don't go through a barrier when read by the VM
* or jitcode.
*/
if (JSID_IS_VOID(id)) {
/* Go through all shapes on the object to get integer-valued properties. */
RootedShape shape(cx, obj->lastProperty());
while (!shape->isEmptyShape()) {
if (JSID_IS_VOID(IdToTypeId(shape->propid())))
UpdatePropertyType(cx, types, obj, shape, true);
shape = shape->previous();
}
/* Also get values of any dense elements in the object. */
for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
const Value& value = obj->getDenseElement(i);
if (!value.isMagic(JS_ELEMENTS_HOLE)) {
TypeSet::Type type = TypeSet::GetValueType(value);
types->TypeSet::addType(type, &cx->typeLifoAlloc());
types->postWriteBarrier(cx, type);
}
}
} else if (!JSID_IS_EMPTY(id)) {
RootedId rootedId(cx, id);
Shape* shape = obj->lookup(cx, rootedId);
if (shape)
UpdatePropertyType(cx, types, obj, shape, false);
}
if (obj->watched()) {
/*
* Mark the property as non-data, to inhibit optimizations on it
* and avoid bypassing the watchpoint handler.
*/
types->setNonDataProperty(cx);
}
}
void
ObjectGroup::addDefiniteProperties(ExclusiveContext* cx, Shape* shape)
{
if (unknownProperties())
return;
// Mark all properties of shape as definite properties of this group.
AutoEnterAnalysis enter(cx);
while (!shape->isEmptyShape()) {
jsid id = IdToTypeId(shape->propid());
if (!JSID_IS_VOID(id)) {
MOZ_ASSERT_IF(shape->slot() >= shape->numFixedSlots(),
shape->numFixedSlots() == NativeObject::MAX_FIXED_SLOTS);
TypeSet* types = getProperty(cx, nullptr, id);
if (types && types->canSetDefinite(shape->slot()))
types->setDefinite(shape->slot());
}
shape = shape->previous();
}
}
bool
ObjectGroup::matchDefiniteProperties(HandleObject obj)
{
unsigned count = getPropertyCount();
for (unsigned i = 0; i < count; i++) {
Property* prop = getProperty(i);
if (!prop)
continue;
if (prop->types.definiteProperty()) {
unsigned slot = prop->types.definiteSlot();
bool found = false;
Shape* shape = obj->as<NativeObject>().lastProperty();
while (!shape->isEmptyShape()) {
if (shape->slot() == slot && shape->propid() == prop->id) {
found = true;
break;
}
shape = shape->previous();
}
if (!found)
return false;
}
}
return true;
}
void
js::AddTypePropertyId(ExclusiveContext* cx, ObjectGroup* group, JSObject* obj, jsid id, TypeSet::Type type)
{
MOZ_ASSERT(id == IdToTypeId(id));
if (group->unknownProperties())
return;
AutoEnterAnalysis enter(cx);
HeapTypeSet* types = group->getProperty(cx, obj, id);
if (!types)
return;
// Clear any constant flag if it exists.
if (!types->empty() && !types->nonConstantProperty()) {
InferSpew(ISpewOps, "constantMutated: %sT%p%s %s",
InferSpewColor(types), types, InferSpewColorReset(), TypeSet::TypeString(type));
types->setNonConstantProperty(cx);
}
if (types->hasType(type))
return;
InferSpew(ISpewOps, "externalType: property %s %s: %s",
TypeSet::ObjectGroupString(group), TypeIdString(id), TypeSet::TypeString(type));
types->addType(cx, type);
// If this addType caused the type set to be marked as containing any
// object, make sure that is reflected in other type sets the addType is
// propagated to below.
if (type.isObjectUnchecked() && types->unknownObject())
type = TypeSet::AnyObjectType();
// Propagate new types from partially initialized groups to fully
// initialized groups for the acquired properties analysis. Note that we
// don't need to do this for other property changes, as these will also be
// reflected via shape changes on the object that will prevent the object
// from acquiring the fully initialized group.
if (group->newScript() && group->newScript()->initializedGroup())
AddTypePropertyId(cx, group->newScript()->initializedGroup(), nullptr, id, type);
// Maintain equivalent type information for unboxed object groups and their
// corresponding native group. Since type sets might contain the unboxed
// group but not the native group, this ensures optimizations based on the
// unboxed group are valid for the native group.
if (group->maybeUnboxedLayout() && group->maybeUnboxedLayout()->nativeGroup())
AddTypePropertyId(cx, group->maybeUnboxedLayout()->nativeGroup(), nullptr, id, type);
if (ObjectGroup* unboxedGroup = group->maybeOriginalUnboxedGroup())
AddTypePropertyId(cx, unboxedGroup, nullptr, id, type);
}
void
js::AddTypePropertyId(ExclusiveContext* cx, ObjectGroup* group, JSObject* obj, jsid id, const Value& value)
{
AddTypePropertyId(cx, group, obj, id, TypeSet::GetValueType(value));
}
void
ObjectGroup::markPropertyNonData(ExclusiveContext* cx, JSObject* obj, jsid id)
{
AutoEnterAnalysis enter(cx);
id = IdToTypeId(id);
HeapTypeSet* types = getProperty(cx, obj, id);
if (types)
types->setNonDataProperty(cx);
}
void
ObjectGroup::markPropertyNonWritable(ExclusiveContext* cx, JSObject* obj, jsid id)
{
AutoEnterAnalysis enter(cx);
id = IdToTypeId(id);
HeapTypeSet* types = getProperty(cx, obj, id);
if (types)
types->setNonWritableProperty(cx);
}
void
ObjectGroup::markStateChange(ExclusiveContext* cxArg)
{
if (unknownProperties())
return;
AutoEnterAnalysis enter(cxArg);
HeapTypeSet* types = maybeGetProperty(JSID_EMPTY);
if (types) {
if (JSContext* cx = cxArg->maybeJSContext()) {
TypeConstraint* constraint = types->constraintList;
while (constraint) {
constraint->newObjectState(cx, this);
constraint = constraint->next;
}
} else {
MOZ_ASSERT(!types->constraintList);
}
}
}
void
ObjectGroup::setFlags(ExclusiveContext* cx, ObjectGroupFlags flags)
{
if (hasAllFlags(flags))
return;
AutoEnterAnalysis enter(cx);
addFlags(flags);
InferSpew(ISpewOps, "%s: setFlags 0x%x", TypeSet::ObjectGroupString(this), flags);
ObjectStateChange(cx, this, false);
// Propagate flag changes from partially to fully initialized groups for the
// acquired properties analysis.
if (newScript() && newScript()->initializedGroup())
newScript()->initializedGroup()->setFlags(cx, flags);
// Propagate flag changes between unboxed and corresponding native groups.
if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup())
maybeUnboxedLayout()->nativeGroup()->setFlags(cx, flags);
if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup())
unboxedGroup->setFlags(cx, flags);
}
void
ObjectGroup::markUnknown(ExclusiveContext* cx)
{
AutoEnterAnalysis enter(cx);
MOZ_ASSERT(cx->zone()->types.activeAnalysis);
MOZ_ASSERT(!unknownProperties());
InferSpew(ISpewOps, "UnknownProperties: %s", TypeSet::ObjectGroupString(this));
clearNewScript(cx);
ObjectStateChange(cx, this, true);
/*
* Existing constraints may have already been added to this object, which we need
* to do the right thing for. We can't ensure that we will mark all unknown
* objects before they have been accessed, as the __proto__ of a known object
* could be dynamically set to an unknown object, and we can decide to ignore
* properties of an object during analysis (i.e. hashmaps). Adding unknown for
* any properties accessed already accounts for possible values read from them.
*/
unsigned count = getPropertyCount();
for (unsigned i = 0; i < count; i++) {
Property* prop = getProperty(i);
if (prop) {
prop->types.addType(cx, TypeSet::UnknownType());
prop->types.setNonDataProperty(cx);
}
}
if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup())
MarkObjectGroupUnknownProperties(cx, unboxedGroup);
if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup())
MarkObjectGroupUnknownProperties(cx, maybeUnboxedLayout()->nativeGroup());
if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup())
MarkObjectGroupUnknownProperties(cx, unboxedGroup);
}
TypeNewScript*
ObjectGroup::anyNewScript()
{
if (newScript())
return newScript();
if (maybeUnboxedLayout())
return unboxedLayout().newScript();
return nullptr;
}
void
ObjectGroup::detachNewScript(bool writeBarrier, ObjectGroup* replacement)
{
// Clear the TypeNewScript from this ObjectGroup and, if it has been
// analyzed, remove it from the newObjectGroups table so that it will not be
// produced by calling 'new' on the associated function anymore.
// The TypeNewScript is not actually destroyed.
TypeNewScript* newScript = anyNewScript();
MOZ_ASSERT(newScript);
if (newScript->analyzed()) {
ObjectGroupCompartment& objectGroups = newScript->function()->compartment()->objectGroups;
if (replacement) {
MOZ_ASSERT(replacement->newScript()->function() == newScript->function());
objectGroups.replaceDefaultNewGroup(nullptr, proto(), newScript->function(),
replacement);
} else {
objectGroups.removeDefaultNewGroup(nullptr, proto(), newScript->function());
}
} else {
MOZ_ASSERT(!replacement);
}
if (this->newScript())
setAddendum(Addendum_None, nullptr, writeBarrier);
else
unboxedLayout().setNewScript(nullptr, writeBarrier);
}
void
ObjectGroup::maybeClearNewScriptOnOOM()
{
MOZ_ASSERT(zone()->isGCSweepingOrCompacting());
if (!isMarked())
return;
TypeNewScript* newScript = anyNewScript();
if (!newScript)
return;
addFlags(OBJECT_FLAG_NEW_SCRIPT_CLEARED);
// This method is called during GC sweeping, so don't trigger pre barriers.
detachNewScript(/* writeBarrier = */ false, nullptr);
js_delete(newScript);
}
void
ObjectGroup::clearNewScript(ExclusiveContext* cx, ObjectGroup* replacement /* = nullptr*/)
{
TypeNewScript* newScript = anyNewScript();
if (!newScript)
return;
AutoEnterAnalysis enter(cx);
if (!replacement) {
// Invalidate any Ion code constructing objects of this type.
setFlags(cx, OBJECT_FLAG_NEW_SCRIPT_CLEARED);
// Mark the constructing function as having its 'new' script cleared, so we
// will not try to construct another one later.
if (!newScript->function()->setNewScriptCleared(cx))
cx->recoverFromOutOfMemory();