diff options
Diffstat (limited to 'js/src/vm/TypeInference.cpp')
-rw-r--r-- | js/src/vm/TypeInference.cpp | 4600 |
1 files changed, 4600 insertions, 0 deletions
diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp new file mode 100644 index 000000000..5b55ba947 --- /dev/null +++ b/js/src/vm/TypeInference.cpp @@ -0,0 +1,4600 @@ +/* -*- 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 "mozilla/Sprintf.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"; +} + +/* 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()) { + JSObject* singleton = type.singletonNoBarrier(); + snprintf(bufs[which], 40, "<%s %#" PRIxPTR ">", + singleton->getClass()->name, uintptr_t(singleton)); + } else { + snprintf(bufs[which], 40, "[%s * %#" PRIxPTR "]", type.groupNoBarrier()->clasp()->name, uintptr_t(type.groupNoBarrier())); + } + + return bufs[which]; +} + +/* static */ const char* +TypeSet::ObjectGroupString(ObjectGroup* group) +{ + return TypeString(TypeSet::ObjectType(group)); +} + +#ifdef DEBUG + +bool +js::InferSpewActive(SpewChannel channel) +{ + static bool active[SPEW_COUNT]; + static bool checked = false; + if (!checked) { + checked = true; + PodArrayZero(active); + const char* env = 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 = 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]; +} + +#ifdef DEBUG +void +js::InferSpewImpl(const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "[infer] "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} +#endif + +MOZ_NORETURN MOZ_COLD static void +MOZ_FORMAT_PRINTF(2, 3) +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); + VsprintfLiteral(errbuf, fmt, ap); + va_end(ap); + + SprintfLiteral(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(); +} + +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 + + +///////////////////////////////////////////////////////////////////// +// 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; +} + +#ifdef DEBUG +static inline bool +CompartmentsMatch(JSCompartment* a, JSCompartment* b) +{ + return !a || !b || a == b; +} +#endif + +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); + MOZ_ASSERT(CompartmentsMatch(maybeCompartment(), constraint->maybeCompartment())); + + 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; +} + +JSCompartment* +TypeSet::maybeCompartment() +{ + if (unknownObject()) + return nullptr; + + unsigned objectCount = getObjectCount(); + for (unsigned i = 0; i < objectCount; i++) { + ObjectKey* key = getObject(i); + if (!key) + continue; + + JSCompartment* comp = key->maybeCompartment(); + if (comp) + return comp; + } + + return nullptr; +} + +void +TypeSet::addType(Type type, LifoAlloc* alloc) +{ + MOZ_ASSERT(CompartmentsMatch(maybeCompartment(), type.maybeCompartment())); + + 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) +{ + bool fromDebugger = !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))); + } + } + + if (fromDebugger) + fprintf(fp, "\n"); +} + +/* 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(JSRuntime* rt, TypeSet::Type* v) +{ + bool rv; + if (v->isSingletonUnchecked()) { + JSObject* obj = v->singletonNoBarrier(); + rv = IsMarkedUnbarriered(rt, &obj); + *v = TypeSet::ObjectType(obj); + } else if (v->isGroupUnchecked()) { + ObjectGroup* group = v->groupNoBarrier(); + rv = IsMarkedUnbarriered(rt, &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().arena()->allocatedDuringIncremental; + } else if (v.isGroupUnchecked()) { + ObjectGroup* group = v.groupNoBarrier(); + rv = group->arena()->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; + } + + JSCompartment* maybeCompartment() { + return data.maybeCompartment(); + } +}; + +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()->taggedProto(); +} + +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; + } + + JSCompartment* maybeCompartment() { + return script_->compartment(); + } +}; + +} /* 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; + + 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) + entry.script->setHasFreezeConstraints(); + } + + 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; } + + JSCompartment* maybeCompartment() { return nullptr; } +}; + +} /* 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; } + + JSCompartment* maybeCompartment() { return nullptr; } +}; + +} /* 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; } + + JSCompartment* maybeCompartment() { return nullptr; } +}; + +// 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); + } + + JSCompartment* maybeCompartment() { + return obj->compartment(); + } +}; + +// 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; } + + JSCompartment* maybeCompartment() { return nullptr; } +}; + +} /* 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; } + + JSCompartment* maybeCompartment() { return nullptr; } +}; + +} /* 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; } + + JSCompartment* maybeCompartment() { return nullptr; } +}; + +} /* 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; } + + JSCompartment* maybeCompartment() { return nullptr; } +}; + +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.isDynamic()) + 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->getResolve() + || clasp->getOpsLookupProperty() + || clasp->getOpsGetProperty() + || IsTypedArrayClass(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(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; + + RootedScript script(cx); + for (auto iter = zone->cellIter<JSScript>(); !iter.done(); iter.next()) { + script = iter; + if (script->types()) + script->types()->printTypes(cx, script); + } + + for (auto group = zone->cellIter<ObjectGroup>(); !group.done(); group.next()) + 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<CallObject>() || + obj->is<ModuleEnvironmentObject>() || + IsExtensibleLexicalEnvironment(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) { + MOZ_ASSERT(unknownProperties()); + return; + } + if (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; + TaggedProto proto = this->proto(); + if (proto.isObject() && IsForwarded(proto.toObject())) + proto = TaggedProto(Forwarded(proto.toObject())); + JSObject* associated = MaybeForwarded(newScript->function()); + if (replacement) { + MOZ_ASSERT(replacement->newScript()->function() == newScript->function()); + objectGroups.replaceDefaultNewGroup(nullptr, proto, associated, replacement); + } else { + objectGroups.removeDefaultNewGroup(nullptr, proto, associated); + } + } 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(); + } + + detachNewScript(/* writeBarrier = */ true, replacement); + + if (cx->isJSContext()) { + bool found = newScript->rollbackPartiallyInitializedObjects(cx->asJSContext(), this); + + // If we managed to rollback any partially initialized objects, then + // any definite properties we added due to analysis of the new script + // are now invalid, so remove them. If there weren't any partially + // initialized objects then we don't need to change type information, + // as no more objects of this type will be created and the 'new' script + // analysis was still valid when older objects were created. + if (found) { + for (unsigned i = 0; i < getPropertyCount(); i++) { + Property* prop = getProperty(i); + if (!prop) + continue; + if (prop->types.definiteProperty()) + prop->types.setNonDataProperty(cx); + } + } + } else { + // Threads with an ExclusiveContext are not allowed to run scripts. + MOZ_ASSERT(!cx->perThreadData->runtimeIfOnOwnerThread() || + !cx->perThreadData->runtimeIfOnOwnerThread()->activation()); + } + + js_delete(newScript); + markStateChange(cx); +} + +void +ObjectGroup::print() +{ + TaggedProto tagged(proto()); + fprintf(stderr, "%s : %s", + TypeSet::ObjectGroupString(this), + tagged.isObject() + ? TypeSet::TypeString(TypeSet::ObjectType(tagged.toObject())) + : tagged.isDynamic() + ? "(dynamic)" + : "(null)"); + + if (unknownProperties()) { + fprintf(stderr, " unknown"); + } else { + if (!hasAnyFlags(OBJECT_FLAG_SPARSE_INDEXES)) + fprintf(stderr, " dense"); + if (!hasAnyFlags(OBJECT_FLAG_NON_PACKED)) + fprintf(stderr, " packed"); + if (!hasAnyFlags(OBJECT_FLAG_LENGTH_OVERFLOW)) + fprintf(stderr, " noLengthOverflow"); + if (hasAnyFlags(OBJECT_FLAG_ITERATED)) + fprintf(stderr, " iterated"); + if (maybeInterpretedFunction()) + fprintf(stderr, " ifun"); + } + + unsigned count = getPropertyCount(); + + if (count == 0) { + fprintf(stderr, " {}\n"); + return; + } + + fprintf(stderr, " {"); + + if (newScript()) { + if (newScript()->analyzed()) { + fprintf(stderr, "\n newScript %d properties", + (int) newScript()->templateObject()->slotSpan()); + if (newScript()->initializedGroup()) { + fprintf(stderr, " initializedGroup %#" PRIxPTR " with %d properties", + uintptr_t(newScript()->initializedGroup()), int(newScript()->initializedShape()->slotSpan())); + } + } else { + fprintf(stderr, "\n newScript unanalyzed"); + } + } + + for (unsigned i = 0; i < count; i++) { + Property* prop = getProperty(i); + if (prop) { + fprintf(stderr, "\n %s:", TypeIdString(prop->id)); + prop->types.print(); + } + } + + fprintf(stderr, "\n}\n"); +} + +///////////////////////////////////////////////////////////////////// +// Type Analysis +///////////////////////////////////////////////////////////////////// + +/* + * Persistent constraint clearing out newScript and definite properties from + * an object should a property on another object get a getter or setter. + */ +class TypeConstraintClearDefiniteGetterSetter : public TypeConstraint +{ + public: + ObjectGroup* group; + + explicit TypeConstraintClearDefiniteGetterSetter(ObjectGroup* group) + : group(group) + {} + + const char* kind() { return "clearDefiniteGetterSetter"; } + + void newPropertyState(JSContext* cx, TypeSet* source) { + /* + * Clear out the newScript shape and definite property information from + * an object if the source type set could be a setter or could be + * non-writable. + */ + if (source->nonDataProperty() || source->nonWritableProperty()) + group->clearNewScript(cx); + } + + void newType(JSContext* cx, TypeSet* source, TypeSet::Type type) {} + + bool sweep(TypeZone& zone, TypeConstraint** res) { + if (IsAboutToBeFinalizedUnbarriered(&group)) + return false; + *res = zone.typeLifoAlloc.new_<TypeConstraintClearDefiniteGetterSetter>(group); + return true; + } + + JSCompartment* maybeCompartment() { + return group->compartment(); + } +}; + +bool +js::AddClearDefiniteGetterSetterForPrototypeChain(JSContext* cx, ObjectGroup* group, HandleId id) +{ + /* + * Ensure that if the properties named here could have a getter, setter or + * a permanent property in any transitive prototype, the definite + * properties get cleared from the group. + */ + RootedObject proto(cx, group->proto().toObjectOrNull()); + while (proto) { + ObjectGroup* protoGroup = proto->getGroup(cx); + if (!protoGroup) { + cx->recoverFromOutOfMemory(); + return false; + } + if (protoGroup->unknownProperties()) + return false; + HeapTypeSet* protoTypes = protoGroup->getProperty(cx, proto, id); + if (!protoTypes || protoTypes->nonDataProperty() || protoTypes->nonWritableProperty()) + return false; + if (!protoTypes->addConstraint(cx, cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteGetterSetter>(group))) + return false; + proto = proto->staticPrototype(); + } + return true; +} + +/* + * Constraint which clears definite properties on a group should a type set + * contain any types other than a single object. + */ +class TypeConstraintClearDefiniteSingle : public TypeConstraint +{ + public: + ObjectGroup* group; + + explicit TypeConstraintClearDefiniteSingle(ObjectGroup* group) + : group(group) + {} + + const char* kind() { return "clearDefiniteSingle"; } + + void newType(JSContext* cx, TypeSet* source, TypeSet::Type type) { + if (source->baseFlags() || source->getObjectCount() > 1) + group->clearNewScript(cx); + } + + bool sweep(TypeZone& zone, TypeConstraint** res) { + if (IsAboutToBeFinalizedUnbarriered(&group)) + return false; + *res = zone.typeLifoAlloc.new_<TypeConstraintClearDefiniteSingle>(group); + return true; + } + + JSCompartment* maybeCompartment() { + return group->compartment(); + } +}; + +bool +js::AddClearDefiniteFunctionUsesInScript(JSContext* cx, ObjectGroup* group, + JSScript* script, JSScript* calleeScript) +{ + // Look for any uses of the specified calleeScript in type sets for + // |script|, and add constraints to ensure that if the type sets' contents + // change then the definite properties are cleared from the type. + // This ensures that the inlining performed when the definite properties + // analysis was done is stable. We only need to look at type sets which + // contain a single object, as IonBuilder does not inline polymorphic sites + // during the definite properties analysis. + + TypeSet::ObjectKey* calleeKey = + TypeSet::ObjectType(calleeScript->functionNonDelazifying()).objectKey(); + + unsigned count = TypeScript::NumTypeSets(script); + StackTypeSet* typeArray = script->types()->typeArray(); + + for (unsigned i = 0; i < count; i++) { + StackTypeSet* types = &typeArray[i]; + if (!types->unknownObject() && types->getObjectCount() == 1) { + if (calleeKey != types->getObject(0)) { + // Also check if the object is the Function.call or + // Function.apply native. IonBuilder uses the presence of these + // functions during inlining. + JSObject* singleton = types->getSingleton(0); + if (!singleton || !singleton->is<JSFunction>()) + continue; + JSFunction* fun = &singleton->as<JSFunction>(); + if (!fun->isNative()) + continue; + if (fun->native() != fun_call && fun->native() != fun_apply) + continue; + } + // This is a type set that might have been used when inlining + // |calleeScript| into |script|. + if (!types->addConstraint(cx, cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteSingle>(group))) + return false; + } + } + + return true; +} + +///////////////////////////////////////////////////////////////////// +// Interface functions +///////////////////////////////////////////////////////////////////// + +void +js::TypeMonitorCallSlow(JSContext* cx, JSObject* callee, const CallArgs& args, bool constructing) +{ + unsigned nargs = callee->as<JSFunction>().nargs(); + JSScript* script = callee->as<JSFunction>().nonLazyScript(); + + if (!constructing) + TypeScript::SetThis(cx, script, args.thisv()); + + /* + * Add constraints going up to the minimum of the actual and formal count. + * If there are more actuals than formals the later values can only be + * accessed through the arguments object, which is monitored. + */ + unsigned arg = 0; + for (; arg < args.length() && arg < nargs; arg++) + TypeScript::SetArgument(cx, script, arg, args[arg]); + + /* Watch for fewer actuals than formals to the call. */ + for (; arg < nargs; arg++) + TypeScript::SetArgument(cx, script, arg, UndefinedValue()); +} + +void +js::FillBytecodeTypeMap(JSScript* script, uint32_t* bytecodeMap) +{ + uint32_t added = 0; + for (jsbytecode* pc = script->code(); pc < script->codeEnd(); pc += GetBytecodeLength(pc)) { + JSOp op = JSOp(*pc); + if (CodeSpec[op].format & JOF_TYPESET) { + bytecodeMap[added++] = script->pcToOffset(pc); + if (added == script->nTypeSets()) + break; + } + } + MOZ_ASSERT(added == script->nTypeSets()); +} + +void +js::TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc, TypeSet::Type type) +{ + assertSameCompartment(cx, script, type); + + AutoEnterAnalysis enter(cx); + + StackTypeSet* types = TypeScript::BytecodeTypes(script, pc); + if (types->hasType(type)) + return; + + InferSpew(ISpewOps, "bytecodeType: %p %05" PRIuSIZE ": %s", + script, script->pcToOffset(pc), TypeSet::TypeString(type)); + types->addType(cx, type); +} + +void +js::TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc, const js::Value& rval) +{ + /* Allow the non-TYPESET scenario to simplify stubs used in compound opcodes. */ + if (!(CodeSpec[*pc].format & JOF_TYPESET)) + return; + + if (!script->hasBaselineScript()) + return; + + TypeMonitorResult(cx, script, pc, TypeSet::GetValueType(rval)); +} + +///////////////////////////////////////////////////////////////////// +// TypeScript +///////////////////////////////////////////////////////////////////// + +bool +JSScript::makeTypes(JSContext* cx) +{ + MOZ_ASSERT(!types_); + + AutoEnterAnalysis enter(cx); + + unsigned count = TypeScript::NumTypeSets(this); + + TypeScript* typeScript = (TypeScript*) + zone()->pod_calloc<uint8_t>(TypeScript::SizeIncludingTypeArray(count)); + if (!typeScript) { + ReportOutOfMemory(cx); + return false; + } + + types_ = typeScript; + setTypesGeneration(cx->zone()->types.generation); + +#ifdef DEBUG + StackTypeSet* typeArray = typeScript->typeArray(); + for (unsigned i = 0; i < nTypeSets(); i++) { + InferSpew(ISpewOps, "typeSet: %sT%p%s bytecode%u %p", + InferSpewColor(&typeArray[i]), &typeArray[i], InferSpewColorReset(), + i, this); + } + TypeSet* thisTypes = TypeScript::ThisTypes(this); + InferSpew(ISpewOps, "typeSet: %sT%p%s this %p", + InferSpewColor(thisTypes), thisTypes, InferSpewColorReset(), + this); + unsigned nargs = functionNonDelazifying() ? functionNonDelazifying()->nargs() : 0; + for (unsigned i = 0; i < nargs; i++) { + TypeSet* types = TypeScript::ArgTypes(this, i); + InferSpew(ISpewOps, "typeSet: %sT%p%s arg%u %p", + InferSpewColor(types), types, InferSpewColorReset(), + i, this); + } +#endif + + return true; +} + +/* static */ bool +JSFunction::setTypeForScriptedFunction(ExclusiveContext* cx, HandleFunction fun, + bool singleton /* = false */) +{ + if (singleton) { + if (!setSingleton(cx, fun)) + return false; + } else { + RootedObject funProto(cx, fun->staticPrototype()); + Rooted<TaggedProto> taggedProto(cx, TaggedProto(funProto)); + ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, &JSFunction::class_, + taggedProto); + if (!group) + return false; + + fun->setGroup(group); + group->setInterpretedFunction(fun); + } + + return true; +} + +///////////////////////////////////////////////////////////////////// +// PreliminaryObjectArray +///////////////////////////////////////////////////////////////////// + +void +PreliminaryObjectArray::registerNewObject(JSObject* res) +{ + // The preliminary object pointers are weak, and won't be swept properly + // during nursery collections, so the preliminary objects need to be + // initially tenured. + MOZ_ASSERT(!IsInsideNursery(res)); + + for (size_t i = 0; i < COUNT; i++) { + if (!objects[i]) { + objects[i] = res; + return; + } + } + + MOZ_CRASH("There should be room for registering the new object"); +} + +void +PreliminaryObjectArray::unregisterObject(JSObject* obj) +{ + for (size_t i = 0; i < COUNT; i++) { + if (objects[i] == obj) { + objects[i] = nullptr; + return; + } + } + + MOZ_CRASH("The object should be in the array"); +} + +bool +PreliminaryObjectArray::full() const +{ + for (size_t i = 0; i < COUNT; i++) { + if (!objects[i]) + return false; + } + return true; +} + +bool +PreliminaryObjectArray::empty() const +{ + for (size_t i = 0; i < COUNT; i++) { + if (objects[i]) + return false; + } + return true; +} + +void +PreliminaryObjectArray::sweep() +{ + // All objects in the array are weak, so clear any that are about to be + // destroyed. + for (size_t i = 0; i < COUNT; i++) { + JSObject** ptr = &objects[i]; + if (*ptr && IsAboutToBeFinalizedUnbarriered(ptr)) { + // Before we clear this reference, change the object's group to the + // Object.prototype group. This is done to ensure JSObject::finalize + // sees a NativeObject Class even if we change the current group's + // Class to one of the unboxed object classes in the meantime. If + // the compartment's global is dead, we don't do anything as the + // group's Class is not going to change in that case. + JSObject* obj = *ptr; + GlobalObject* global = obj->compartment()->unsafeUnbarrieredMaybeGlobal(); + if (global && !obj->isSingleton()) { + JSObject* objectProto = GetBuiltinPrototypePure(global, JSProto_Object); + obj->setGroup(objectProto->groupRaw()); + MOZ_ASSERT(obj->is<NativeObject>()); + MOZ_ASSERT(obj->getClass() == objectProto->getClass()); + MOZ_ASSERT(!obj->getClass()->hasFinalize()); + } + + *ptr = nullptr; + } + } +} + +void +PreliminaryObjectArrayWithTemplate::trace(JSTracer* trc) +{ + TraceNullableEdge(trc, &shape_, "PreliminaryObjectArrayWithTemplate_shape"); +} + +/* static */ void +PreliminaryObjectArrayWithTemplate::writeBarrierPre(PreliminaryObjectArrayWithTemplate* objects) +{ + Shape* shape = objects->shape(); + + if (!shape) + return; + + JS::Zone* zone = shape->zoneFromAnyThread(); + if (zone->needsIncrementalBarrier()) + objects->trace(zone->barrierTracer()); +} + +// Return whether shape consists entirely of plain data properties. +static bool +OnlyHasDataProperties(Shape* shape) +{ + MOZ_ASSERT(!shape->inDictionary()); + + while (!shape->isEmptyShape()) { + if (!shape->isDataDescriptor() || + !shape->configurable() || + !shape->enumerable() || + !shape->writable() || + !shape->hasSlot()) + { + return false; + } + shape = shape->previous(); + } + + return true; +} + +// Find the most recent common ancestor of two shapes, or an empty shape if +// the two shapes have no common ancestor. +static Shape* +CommonPrefix(Shape* first, Shape* second) +{ + MOZ_ASSERT(OnlyHasDataProperties(first)); + MOZ_ASSERT(OnlyHasDataProperties(second)); + + while (first->slotSpan() > second->slotSpan()) + first = first->previous(); + while (second->slotSpan() > first->slotSpan()) + second = second->previous(); + + while (first != second && !first->isEmptyShape()) { + first = first->previous(); + second = second->previous(); + } + + return first; +} + +void +PreliminaryObjectArrayWithTemplate::maybeAnalyze(ExclusiveContext* cx, ObjectGroup* group, bool force) +{ + // Don't perform the analyses until sufficient preliminary objects have + // been allocated. + if (!force && !full()) + return; + + AutoEnterAnalysis enter(cx); + + ScopedJSDeletePtr<PreliminaryObjectArrayWithTemplate> preliminaryObjects(this); + group->detachPreliminaryObjects(); + + if (shape()) { + MOZ_ASSERT(shape()->slotSpan() != 0); + MOZ_ASSERT(OnlyHasDataProperties(shape())); + + // Make sure all the preliminary objects reflect the properties originally + // in the template object. + for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) { + JSObject* objBase = preliminaryObjects->get(i); + if (!objBase) + continue; + PlainObject* obj = &objBase->as<PlainObject>(); + + if (obj->inDictionaryMode() || !OnlyHasDataProperties(obj->lastProperty())) + return; + + if (CommonPrefix(obj->lastProperty(), shape()) != shape()) + return; + } + } + + TryConvertToUnboxedLayout(cx, enter, shape(), group, preliminaryObjects); + if (group->maybeUnboxedLayout()) + return; + + if (shape()) { + // We weren't able to use an unboxed layout, but since the preliminary + // objects still reflect the template object's properties, and all + // objects in the future will be created with those properties, the + // properties can be marked as definite for objects in the group. + group->addDefiniteProperties(cx, shape()); + } +} + +///////////////////////////////////////////////////////////////////// +// TypeNewScript +///////////////////////////////////////////////////////////////////// + +// Make a TypeNewScript for |group|, and set it up to hold the preliminary +// objects created with the group. +/* static */ bool +TypeNewScript::make(JSContext* cx, ObjectGroup* group, JSFunction* fun) +{ + MOZ_ASSERT(cx->zone()->types.activeAnalysis); + MOZ_ASSERT(!group->newScript()); + MOZ_ASSERT(!group->maybeUnboxedLayout()); + + if (group->unknownProperties()) + return true; + + ScopedJSDeletePtr<TypeNewScript> newScript(cx->new_<TypeNewScript>()); + if (!newScript) + return false; + + newScript->function_ = fun; + + newScript->preliminaryObjects = group->zone()->new_<PreliminaryObjectArray>(); + if (!newScript->preliminaryObjects) + return true; + + group->setNewScript(newScript.forget()); + + gc::TraceTypeNewScript(group); + return true; +} + +// Make a TypeNewScript with the same initializer list as |newScript| but with +// a new template object. +/* static */ TypeNewScript* +TypeNewScript::makeNativeVersion(JSContext* cx, TypeNewScript* newScript, + PlainObject* templateObject) +{ + MOZ_ASSERT(cx->zone()->types.activeAnalysis); + + ScopedJSDeletePtr<TypeNewScript> nativeNewScript(cx->new_<TypeNewScript>()); + if (!nativeNewScript) + return nullptr; + + nativeNewScript->function_ = newScript->function(); + nativeNewScript->templateObject_ = templateObject; + + Initializer* cursor = newScript->initializerList; + while (cursor->kind != Initializer::DONE) { cursor++; } + size_t initializerLength = cursor - newScript->initializerList + 1; + + nativeNewScript->initializerList = cx->zone()->pod_calloc<Initializer>(initializerLength); + if (!nativeNewScript->initializerList) { + ReportOutOfMemory(cx); + return nullptr; + } + PodCopy(nativeNewScript->initializerList, newScript->initializerList, initializerLength); + + return nativeNewScript.forget(); +} + +size_t +TypeNewScript::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const +{ + size_t n = mallocSizeOf(this); + n += mallocSizeOf(preliminaryObjects); + n += mallocSizeOf(initializerList); + return n; +} + +void +TypeNewScript::registerNewObject(PlainObject* res) +{ + MOZ_ASSERT(!analyzed()); + + // New script objects must have the maximum number of fixed slots, so that + // we can adjust their shape later to match the number of fixed slots used + // by the template object we eventually create. + MOZ_ASSERT(res->numFixedSlots() == NativeObject::MAX_FIXED_SLOTS); + + preliminaryObjects->registerNewObject(res); +} + +static bool +ChangeObjectFixedSlotCount(JSContext* cx, PlainObject* obj, gc::AllocKind allocKind) +{ + MOZ_ASSERT(OnlyHasDataProperties(obj->lastProperty())); + + Shape* newShape = ReshapeForAllocKind(cx, obj->lastProperty(), obj->taggedProto(), allocKind); + if (!newShape) + return false; + + obj->setLastPropertyShrinkFixedSlots(newShape); + return true; +} + +namespace { + +struct DestroyTypeNewScript +{ + JSContext* cx; + ObjectGroup* group; + + DestroyTypeNewScript(JSContext* cx, ObjectGroup* group) + : cx(cx), group(group) + {} + + ~DestroyTypeNewScript() { + if (group) + group->clearNewScript(cx); + } +}; + +} // namespace + +bool +TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, bool force) +{ + // Perform the new script properties analysis if necessary, returning + // whether the new group table was updated and group needs to be refreshed. + MOZ_ASSERT(this == group->newScript()); + + // Make sure there aren't dead references in preliminaryObjects. This can + // clear out the new script information on OOM. + group->maybeSweep(nullptr); + if (!group->newScript()) + return true; + + if (regenerate) + *regenerate = false; + + if (analyzed()) { + // The analyses have already been performed. + return true; + } + + // Don't perform the analyses until sufficient preliminary objects have + // been allocated. + if (!force && !preliminaryObjects->full()) + return true; + + AutoEnterAnalysis enter(cx); + + // Any failures after this point will clear out this TypeNewScript. + DestroyTypeNewScript destroyNewScript(cx, group); + + // Compute the greatest common shape prefix and the largest slot span of + // the preliminary objects. + Shape* prefixShape = nullptr; + size_t maxSlotSpan = 0; + for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) { + JSObject* objBase = preliminaryObjects->get(i); + if (!objBase) + continue; + PlainObject* obj = &objBase->as<PlainObject>(); + + // For now, we require all preliminary objects to have only simple + // lineages of plain data properties. + Shape* shape = obj->lastProperty(); + if (shape->inDictionary() || + !OnlyHasDataProperties(shape) || + shape->getObjectFlags() != 0) + { + return true; + } + + maxSlotSpan = Max<size_t>(maxSlotSpan, obj->slotSpan()); + + if (prefixShape) { + MOZ_ASSERT(shape->numFixedSlots() == prefixShape->numFixedSlots()); + prefixShape = CommonPrefix(prefixShape, shape); + } else { + prefixShape = shape; + } + if (prefixShape->isEmptyShape()) { + // The preliminary objects don't have any common properties. + return true; + } + } + if (!prefixShape) + return true; + + gc::AllocKind kind = gc::GetGCObjectKind(maxSlotSpan); + + if (kind != gc::GetGCObjectKind(NativeObject::MAX_FIXED_SLOTS)) { + // The template object will have a different allocation kind from the + // preliminary objects that have already been constructed. Optimizing + // definite property accesses requires both that the property is + // definitely in a particular slot and that the object has a specific + // number of fixed slots. So, adjust the shape and slot layout of all + // the preliminary objects so that their structure matches that of the + // template object. Also recompute the prefix shape, as it reflects the + // old number of fixed slots. + Shape* newPrefixShape = nullptr; + for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) { + JSObject* objBase = preliminaryObjects->get(i); + if (!objBase) + continue; + PlainObject* obj = &objBase->as<PlainObject>(); + if (!ChangeObjectFixedSlotCount(cx, obj, kind)) + return false; + if (newPrefixShape) { + MOZ_ASSERT(CommonPrefix(obj->lastProperty(), newPrefixShape) == newPrefixShape); + } else { + newPrefixShape = obj->lastProperty(); + while (newPrefixShape->slotSpan() > prefixShape->slotSpan()) + newPrefixShape = newPrefixShape->previous(); + } + } + prefixShape = newPrefixShape; + } + + RootedObjectGroup groupRoot(cx, group); + templateObject_ = NewObjectWithGroup<PlainObject>(cx, groupRoot, kind, TenuredObject); + if (!templateObject_) + return false; + + Vector<Initializer> initializerVector(cx); + + RootedPlainObject templateRoot(cx, templateObject()); + if (!jit::AnalyzeNewScriptDefiniteProperties(cx, function(), group, templateRoot, &initializerVector)) + return false; + + if (!group->newScript()) + return true; + + MOZ_ASSERT(OnlyHasDataProperties(templateObject()->lastProperty())); + + if (templateObject()->slotSpan() != 0) { + // Make sure that all definite properties found are reflected in the + // prefix shape. Otherwise, the constructor behaved differently before + // we baseline compiled it and started observing types. Compare + // property names rather than looking at the shapes directly, as the + // allocation kind and other non-property parts of the template and + // existing objects may differ. + if (templateObject()->slotSpan() > prefixShape->slotSpan()) + return true; + { + Shape* shape = prefixShape; + while (shape->slotSpan() != templateObject()->slotSpan()) + shape = shape->previous(); + Shape* templateShape = templateObject()->lastProperty(); + while (!shape->isEmptyShape()) { + if (shape->slot() != templateShape->slot()) + return true; + if (shape->propid() != templateShape->propid()) + return true; + shape = shape->previous(); + templateShape = templateShape->previous(); + } + if (!templateShape->isEmptyShape()) + return true; + } + + Initializer done(Initializer::DONE, 0); + + if (!initializerVector.append(done)) + return false; + + initializerList = group->zone()->pod_calloc<Initializer>(initializerVector.length()); + if (!initializerList) { + ReportOutOfMemory(cx); + return false; + } + PodCopy(initializerList, initializerVector.begin(), initializerVector.length()); + } + + // Try to use an unboxed representation for the group. + if (!TryConvertToUnboxedLayout(cx, enter, templateObject()->lastProperty(), group, preliminaryObjects)) + return false; + + js_delete(preliminaryObjects); + preliminaryObjects = nullptr; + + if (group->maybeUnboxedLayout()) { + // An unboxed layout was constructed for the group, and this has already + // been hooked into it. + MOZ_ASSERT(group->unboxedLayout().newScript() == this); + destroyNewScript.group = nullptr; + + // Clear out the template object, which is not used for TypeNewScripts + // with an unboxed layout. Currently it is a mutant object with a + // non-native group and native shape, so make it safe for GC by changing + // its group to the default for its prototype. + AutoEnterOOMUnsafeRegion oomUnsafe; + ObjectGroup* plainGroup = ObjectGroup::defaultNewGroup(cx, &PlainObject::class_, + group->proto()); + if (!plainGroup) + oomUnsafe.crash("TypeNewScript::maybeAnalyze"); + templateObject_->setGroup(plainGroup); + templateObject_ = nullptr; + + return true; + } + + if (prefixShape->slotSpan() == templateObject()->slotSpan()) { + // The definite properties analysis found exactly the properties that + // are held in common by the preliminary objects. No further analysis + // is needed. + group->addDefiniteProperties(cx, templateObject()->lastProperty()); + + destroyNewScript.group = nullptr; + return true; + } + + // There are more properties consistently added to objects of this group + // than were discovered by the definite properties analysis. Use the + // existing group to represent fully initialized objects with all + // definite properties in the prefix shape, and make a new group to + // represent partially initialized objects. + MOZ_ASSERT(prefixShape->slotSpan() > templateObject()->slotSpan()); + + ObjectGroupFlags initialFlags = group->flags() & OBJECT_FLAG_DYNAMIC_MASK; + + Rooted<TaggedProto> protoRoot(cx, group->proto()); + ObjectGroup* initialGroup = ObjectGroupCompartment::makeGroup(cx, group->clasp(), protoRoot, + initialFlags); + if (!initialGroup) + return false; + + initialGroup->addDefiniteProperties(cx, templateObject()->lastProperty()); + group->addDefiniteProperties(cx, prefixShape); + + cx->compartment()->objectGroups.replaceDefaultNewGroup(nullptr, group->proto(), function(), + initialGroup); + + templateObject()->setGroup(initialGroup); + + // Transfer this TypeNewScript from the fully initialized group to the + // partially initialized group. + group->setNewScript(nullptr); + initialGroup->setNewScript(this); + + initializedShape_ = prefixShape; + initializedGroup_ = group; + + destroyNewScript.group = nullptr; + + if (regenerate) + *regenerate = true; + return true; +} + +bool +TypeNewScript::rollbackPartiallyInitializedObjects(JSContext* cx, ObjectGroup* group) +{ + // If we cleared this new script while in the middle of initializing an + // object, it will still have the new script's shape and reflect the no + // longer correct state of the object once its initialization is completed. + // We can't detect the possibility of this statically while remaining + // robust, but the new script keeps track of where each property is + // initialized so we can walk the stack and fix up any such objects. + // Return whether any objects were modified. + + if (!initializerList) + return false; + + bool found = false; + + RootedFunction function(cx, this->function()); + Vector<uint32_t, 32> pcOffsets(cx); + for (ScriptFrameIter iter(cx); !iter.done(); ++iter) { + { + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!pcOffsets.append(iter.script()->pcToOffset(iter.pc()))) + oomUnsafe.crash("rollbackPartiallyInitializedObjects"); + } + + if (!iter.isConstructing() || !iter.matchCallee(cx, function)) + continue; + + // Derived class constructors initialize their this-binding later and + // we shouldn't run the definite properties analysis on them. + MOZ_ASSERT(!iter.script()->isDerivedClassConstructor()); + + Value thisv = iter.thisArgument(cx); + if (!thisv.isObject() || + thisv.toObject().hasLazyGroup() || + thisv.toObject().group() != group) + { + continue; + } + + if (thisv.toObject().is<UnboxedPlainObject>()) { + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!UnboxedPlainObject::convertToNative(cx, &thisv.toObject())) + oomUnsafe.crash("rollbackPartiallyInitializedObjects"); + } + + // Found a matching frame. + RootedPlainObject obj(cx, &thisv.toObject().as<PlainObject>()); + + // Whether all identified 'new' properties have been initialized. + bool finished = false; + + // If not finished, number of properties that have been added. + uint32_t numProperties = 0; + + // Whether the current SETPROP is within an inner frame which has + // finished entirely. + bool pastProperty = false; + + // Index in pcOffsets of the outermost frame. + int callDepth = pcOffsets.length() - 1; + + // Index in pcOffsets of the frame currently being checked for a SETPROP. + int setpropDepth = callDepth; + + for (Initializer* init = initializerList;; init++) { + if (init->kind == Initializer::SETPROP) { + if (!pastProperty && pcOffsets[setpropDepth] < init->offset) { + // Have not yet reached this setprop. + break; + } + // This setprop has executed, reset state for the next one. + numProperties++; + pastProperty = false; + setpropDepth = callDepth; + } else if (init->kind == Initializer::SETPROP_FRAME) { + if (!pastProperty) { + if (pcOffsets[setpropDepth] < init->offset) { + // Have not yet reached this inner call. + break; + } else if (pcOffsets[setpropDepth] > init->offset) { + // Have advanced past this inner call. + pastProperty = true; + } else if (setpropDepth == 0) { + // Have reached this call but not yet in it. + break; + } else { + // Somewhere inside this inner call. + setpropDepth--; + } + } + } else { + MOZ_ASSERT(init->kind == Initializer::DONE); + finished = true; + break; + } + } + + if (!finished) { + (void) NativeObject::rollbackProperties(cx, obj, numProperties); + found = true; + } + } + + return found; +} + +void +TypeNewScript::trace(JSTracer* trc) +{ + TraceEdge(trc, &function_, "TypeNewScript_function"); + TraceNullableEdge(trc, &templateObject_, "TypeNewScript_templateObject"); + TraceNullableEdge(trc, &initializedShape_, "TypeNewScript_initializedShape"); + TraceNullableEdge(trc, &initializedGroup_, "TypeNewScript_initializedGroup"); +} + +/* static */ void +TypeNewScript::writeBarrierPre(TypeNewScript* newScript) +{ + if (newScript->function()->runtimeFromAnyThread()->isHeapCollecting()) + return; + + JS::Zone* zone = newScript->function()->zoneFromAnyThread(); + if (zone->needsIncrementalBarrier()) + newScript->trace(zone->barrierTracer()); +} + +void +TypeNewScript::sweep() +{ + if (preliminaryObjects) + preliminaryObjects->sweep(); +} + +///////////////////////////////////////////////////////////////////// +// Tracing +///////////////////////////////////////////////////////////////////// + +static inline void +TraceObjectKey(JSTracer* trc, TypeSet::ObjectKey** keyp) +{ + TypeSet::ObjectKey* key = *keyp; + if (key->isGroup()) { + ObjectGroup* group = key->groupNoBarrier(); + TraceManuallyBarrieredEdge(trc, &group, "objectKey_group"); + *keyp = TypeSet::ObjectKey::get(group); + } else { + JSObject* singleton = key->singletonNoBarrier(); + TraceManuallyBarrieredEdge(trc, &singleton, "objectKey_singleton"); + *keyp = TypeSet::ObjectKey::get(singleton); + } +} + +void +ConstraintTypeSet::trace(Zone* zone, JSTracer* trc) +{ + // ConstraintTypeSets only hold strong references during minor collections. + MOZ_ASSERT(zone->runtimeFromMainThread()->isHeapMinorCollecting()); + + unsigned objectCount = baseObjectCount(); + if (objectCount >= 2) { + unsigned oldCapacity = TypeHashSet::Capacity(objectCount); + ObjectKey** oldArray = objectSet; + + clearObjects(); + objectCount = 0; + for (unsigned i = 0; i < oldCapacity; i++) { + ObjectKey* key = oldArray[i]; + if (!key) + continue; + TraceObjectKey(trc, &key); + + AutoEnterOOMUnsafeRegion oomUnsafe; + ObjectKey** pentry = + TypeHashSet::Insert<ObjectKey*, ObjectKey, ObjectKey> + (zone->types.typeLifoAlloc, objectSet, objectCount, key); + if (!pentry) + oomUnsafe.crash("ConstraintTypeSet::trace"); + + *pentry = key; + } + setBaseObjectCount(objectCount); + } else if (objectCount == 1) { + ObjectKey* key = (ObjectKey*) objectSet; + TraceObjectKey(trc, &key); + objectSet = reinterpret_cast<ObjectKey**>(key); + } +} + +static inline void +AssertGCStateForSweep(Zone* zone) +{ + MOZ_ASSERT(zone->isGCSweepingOrCompacting()); + + // IsAboutToBeFinalized doesn't work right on tenured objects when called + // during a minor collection. + MOZ_ASSERT(!zone->runtimeFromMainThread()->isHeapMinorCollecting()); +} + +void +ConstraintTypeSet::sweep(Zone* zone, AutoClearTypeInferenceStateOnOOM& oom) +{ + AssertGCStateForSweep(zone); + + /* + * Purge references to objects that are no longer live. Type sets hold + * only weak references. For type sets containing more than one object, + * live entries in the object hash need to be copied to the zone's + * new arena. + */ + unsigned objectCount = baseObjectCount(); + if (objectCount >= 2) { + unsigned oldCapacity = TypeHashSet::Capacity(objectCount); + ObjectKey** oldArray = objectSet; + + clearObjects(); + objectCount = 0; + for (unsigned i = 0; i < oldCapacity; i++) { + ObjectKey* key = oldArray[i]; + if (!key) + continue; + if (!IsObjectKeyAboutToBeFinalized(&key)) { + ObjectKey** pentry = + TypeHashSet::Insert<ObjectKey*, ObjectKey, ObjectKey> + (zone->types.typeLifoAlloc, objectSet, objectCount, key); + if (pentry) { + *pentry = key; + } else { + oom.setOOM(); + flags |= TYPE_FLAG_ANYOBJECT; + clearObjects(); + objectCount = 0; + break; + } + } else if (key->isGroup() && + key->groupNoBarrier()->unknownPropertiesDontCheckGeneration()) { + // Object sets containing objects with unknown properties might + // not be complete. Mark the type set as unknown, which it will + // be treated as during Ion compilation. + // + // Note that we don't have to do this when the type set might + // be missing the native group corresponding to an unboxed + // object group. In this case, the native group points to the + // unboxed object group via its addendum, so as long as objects + // with either group exist, neither group will be finalized. + flags |= TYPE_FLAG_ANYOBJECT; + clearObjects(); + objectCount = 0; + break; + } + } + setBaseObjectCount(objectCount); + } else if (objectCount == 1) { + ObjectKey* key = (ObjectKey*) objectSet; + if (!IsObjectKeyAboutToBeFinalized(&key)) { + objectSet = reinterpret_cast<ObjectKey**>(key); + } else { + // As above, mark type sets containing objects with unknown + // properties as unknown. + if (key->isGroup() && key->groupNoBarrier()->unknownPropertiesDontCheckGeneration()) + flags |= TYPE_FLAG_ANYOBJECT; + objectSet = nullptr; + setBaseObjectCount(0); + } + } + + /* + * Type constraints only hold weak references. Copy constraints referring + * to data that is still live into the zone's new arena. + */ + TypeConstraint* constraint = constraintList; + constraintList = nullptr; + while (constraint) { + MOZ_ASSERT(zone->types.sweepTypeLifoAlloc.contains(constraint)); + TypeConstraint* copy; + if (constraint->sweep(zone->types, ©)) { + if (copy) { + MOZ_ASSERT(zone->types.typeLifoAlloc.contains(copy)); + copy->next = constraintList; + constraintList = copy; + } else { + oom.setOOM(); + } + } + constraint = constraint->next; + } +} + +inline void +ObjectGroup::clearProperties() +{ + setBasePropertyCount(0); + propertySet = nullptr; +} + +static void +EnsureHasAutoClearTypeInferenceStateOnOOM(AutoClearTypeInferenceStateOnOOM*& oom, Zone* zone, + Maybe<AutoClearTypeInferenceStateOnOOM>& fallback) +{ + if (!oom) { + if (zone->types.activeAnalysis) { + oom = &zone->types.activeAnalysis->oom; + } else { + fallback.emplace(zone); + oom = &fallback.ref(); + } + } +} + +/* + * Before sweeping the arenas themselves, scan all groups in a compartment to + * fixup weak references: property type sets referencing dead JS and type + * objects, and singleton JS objects whose type is not referenced elsewhere. + * This is done either incrementally as part of the sweep, or on demand as type + * objects are accessed before their contents have been swept. + */ +void +ObjectGroup::sweep(AutoClearTypeInferenceStateOnOOM* oom) +{ + MOZ_ASSERT(generation() != zoneFromAnyThread()->types.generation); + + setGeneration(zone()->types.generation); + + AssertGCStateForSweep(zone()); + + Maybe<AutoClearTypeInferenceStateOnOOM> fallbackOOM; + EnsureHasAutoClearTypeInferenceStateOnOOM(oom, zone(), fallbackOOM); + + if (maybeUnboxedLayout()) { + // Remove unboxed layouts that are about to be finalized from the + // compartment wide list while we are still on the main thread. + ObjectGroup* group = this; + if (IsAboutToBeFinalizedUnbarriered(&group)) + unboxedLayout().detachFromCompartment(); + + if (unboxedLayout().newScript()) + unboxedLayout().newScript()->sweep(); + + // Discard constructor code to avoid holding onto ExecutablePools. + if (zone()->isGCCompacting()) + unboxedLayout().setConstructorCode(nullptr); + } + + if (maybePreliminaryObjects()) + maybePreliminaryObjects()->sweep(); + + if (newScript()) + newScript()->sweep(); + + LifoAlloc& typeLifoAlloc = zone()->types.typeLifoAlloc; + + /* + * Properties were allocated from the old arena, and need to be copied over + * to the new one. + */ + unsigned propertyCount = basePropertyCount(); + if (propertyCount >= 2) { + unsigned oldCapacity = TypeHashSet::Capacity(propertyCount); + Property** oldArray = propertySet; + + clearProperties(); + propertyCount = 0; + for (unsigned i = 0; i < oldCapacity; i++) { + Property* prop = oldArray[i]; + if (prop) { + if (singleton() && !prop->types.constraintList && !zone()->isPreservingCode()) { + /* + * Don't copy over properties of singleton objects when their + * presence will not be required by jitcode or type constraints + * (i.e. for the definite properties analysis). The contents of + * these type sets will be regenerated as necessary. + */ + continue; + } + + Property* newProp = typeLifoAlloc.new_<Property>(*prop); + if (newProp) { + Property** pentry = TypeHashSet::Insert<jsid, Property, Property> + (typeLifoAlloc, propertySet, propertyCount, prop->id); + if (pentry) { + *pentry = newProp; + newProp->types.sweep(zone(), *oom); + continue; + } + } + + oom->setOOM(); + addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); + clearProperties(); + return; + } + } + setBasePropertyCount(propertyCount); + } else if (propertyCount == 1) { + Property* prop = (Property*) propertySet; + if (singleton() && !prop->types.constraintList && !zone()->isPreservingCode()) { + // Skip, as above. + clearProperties(); + } else { + Property* newProp = typeLifoAlloc.new_<Property>(*prop); + if (newProp) { + propertySet = (Property**) newProp; + newProp->types.sweep(zone(), *oom); + } else { + oom->setOOM(); + addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); + clearProperties(); + return; + } + } + } +} + +/* static */ void +JSScript::maybeSweepTypes(AutoClearTypeInferenceStateOnOOM* oom) +{ + if (!types_ || typesGeneration() == zone()->types.generation) + return; + + setTypesGeneration(zone()->types.generation); + + AssertGCStateForSweep(zone()); + + Maybe<AutoClearTypeInferenceStateOnOOM> fallbackOOM; + EnsureHasAutoClearTypeInferenceStateOnOOM(oom, zone(), fallbackOOM); + + TypeZone& types = zone()->types; + + // Destroy all type information attached to the script if desired. We can + // only do this if nothing has been compiled for the script, which will be + // the case unless the script has been compiled since we started sweeping. + if (types.sweepReleaseTypes && + !hasBaselineScript() && + !hasIonScript()) + { + types_->destroy(); + types_ = nullptr; + + // Freeze constraints on stack type sets need to be regenerated the + // next time the script is analyzed. + hasFreezeConstraints_ = false; + + return; + } + + unsigned num = TypeScript::NumTypeSets(this); + StackTypeSet* typeArray = types_->typeArray(); + + // Remove constraints and references to dead objects from stack type sets. + for (unsigned i = 0; i < num; i++) + typeArray[i].sweep(zone(), *oom); + + if (oom->hadOOM()) { + // It's possible we OOM'd while copying freeze constraints, so they + // need to be regenerated. + hasFreezeConstraints_ = false; + } + + // Update the recompile indexes in any IonScripts still on the script. + if (hasIonScript()) + ionScript()->recompileInfoRef().shouldSweep(types); +} + +void +TypeScript::destroy() +{ + js_free(this); +} + +void +Zone::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, + size_t* typePool, + size_t* baselineStubsOptimized, + size_t* uniqueIdMap, + size_t* shapeTables) +{ + *typePool += types.typeLifoAlloc.sizeOfExcludingThis(mallocSizeOf); + if (jitZone()) { + *baselineStubsOptimized += + jitZone()->optimizedStubSpace()->sizeOfExcludingThis(mallocSizeOf); + } + *uniqueIdMap += uniqueIds_.sizeOfExcludingThis(mallocSizeOf); + *shapeTables += baseShapes.sizeOfExcludingThis(mallocSizeOf) + + initialShapes.sizeOfExcludingThis(mallocSizeOf); +} + +TypeZone::TypeZone(Zone* zone) + : zone_(zone), + typeLifoAlloc(TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), + generation(0), + compilerOutputs(nullptr), + sweepTypeLifoAlloc(TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), + sweepCompilerOutputs(nullptr), + sweepReleaseTypes(false), + activeAnalysis(nullptr) +{ +} + +TypeZone::~TypeZone() +{ + js_delete(compilerOutputs); + js_delete(sweepCompilerOutputs); +} + +void +TypeZone::beginSweep(FreeOp* fop, bool releaseTypes, AutoClearTypeInferenceStateOnOOM& oom) +{ + MOZ_ASSERT(zone()->isGCSweepingOrCompacting()); + MOZ_ASSERT(!sweepCompilerOutputs); + MOZ_ASSERT(!sweepReleaseTypes); + + sweepReleaseTypes = releaseTypes; + + // Clear the analysis pool, but don't release its data yet. While sweeping + // types any live data will be allocated into the pool. + sweepTypeLifoAlloc.steal(&typeLifoAlloc); + + // Sweep any invalid or dead compiler outputs, and keep track of the new + // index for remaining live outputs. + if (compilerOutputs) { + CompilerOutputVector* newCompilerOutputs = nullptr; + for (size_t i = 0; i < compilerOutputs->length(); i++) { + CompilerOutput& output = (*compilerOutputs)[i]; + if (output.isValid()) { + JSScript* script = output.script(); + if (IsAboutToBeFinalizedUnbarriered(&script)) { + if (script->hasIonScript()) + script->ionScript()->recompileInfoRef() = RecompileInfo(); + output.invalidate(); + } else { + CompilerOutput newOutput(script); + + if (!newCompilerOutputs) + newCompilerOutputs = js_new<CompilerOutputVector>(); + if (newCompilerOutputs && newCompilerOutputs->append(newOutput)) { + output.setSweepIndex(newCompilerOutputs->length() - 1); + } else { + oom.setOOM(); + script->ionScript()->recompileInfoRef() = RecompileInfo(); + output.invalidate(); + } + } + } + } + sweepCompilerOutputs = compilerOutputs; + compilerOutputs = newCompilerOutputs; + } + + // All existing RecompileInfos are stale and will be updated to the new + // compiler outputs list later during the sweep. Don't worry about overflow + // here, since stale indexes will persist only until the sweep finishes. + generation++; +} + +void +TypeZone::endSweep(JSRuntime* rt) +{ + js_delete(sweepCompilerOutputs); + sweepCompilerOutputs = nullptr; + + sweepReleaseTypes = false; + + rt->gc.freeAllLifoBlocksAfterSweeping(&sweepTypeLifoAlloc); +} + +void +TypeZone::clearAllNewScriptsOnOOM() +{ + for (auto iter = zone()->cellIter<ObjectGroup>(); !iter.done(); iter.next()) { + ObjectGroup* group = iter; + if (!IsAboutToBeFinalizedUnbarriered(&group)) + group->maybeClearNewScriptOnOOM(); + } +} + +AutoClearTypeInferenceStateOnOOM::~AutoClearTypeInferenceStateOnOOM() +{ + if (oom) { + JSRuntime* rt = zone->runtimeFromMainThread(); + js::CancelOffThreadIonCompile(rt); + zone->setPreservingCode(false); + zone->discardJitCode(rt->defaultFreeOp(), /* discardBaselineCode = */ false); + zone->types.clearAllNewScriptsOnOOM(); + } +} + +#ifdef DEBUG +void +TypeScript::printTypes(JSContext* cx, HandleScript script) const +{ + MOZ_ASSERT(script->types() == this); + + if (!script->hasBaselineScript()) + return; + + AutoEnterAnalysis enter(nullptr, script->zone()); + + if (script->functionNonDelazifying()) + fprintf(stderr, "Function"); + else if (script->isForEval()) + fprintf(stderr, "Eval"); + else + fprintf(stderr, "Main"); + fprintf(stderr, " %#" PRIxPTR " %s:%" PRIuSIZE " ", + uintptr_t(script.get()), script->filename(), script->lineno()); + + if (script->functionNonDelazifying()) { + if (JSAtom* name = script->functionNonDelazifying()->name()) + name->dumpCharsNoNewline(); + } + + fprintf(stderr, "\n this:"); + TypeScript::ThisTypes(script)->print(); + + for (unsigned i = 0; + script->functionNonDelazifying() && i < script->functionNonDelazifying()->nargs(); + i++) + { + fprintf(stderr, "\n arg%u:", i); + TypeScript::ArgTypes(script, i)->print(); + } + fprintf(stderr, "\n"); + + for (jsbytecode* pc = script->code(); pc < script->codeEnd(); pc += GetBytecodeLength(pc)) { + { + fprintf(stderr, "%p:", script.get()); + Sprinter sprinter(cx); + if (!sprinter.init()) + return; + Disassemble1(cx, script, pc, script->pcToOffset(pc), true, &sprinter); + fprintf(stderr, "%s", sprinter.string()); + } + + if (CodeSpec[*pc].format & JOF_TYPESET) { + StackTypeSet* types = TypeScript::BytecodeTypes(script, pc); + fprintf(stderr, " typeset %u:", unsigned(types - typeArray())); + types->print(); + fprintf(stderr, "\n"); + } + } + + fprintf(stderr, "\n"); +} +#endif /* DEBUG */ + +JS::ubi::Node::Size +JS::ubi::Concrete<js::ObjectGroup>::size(mozilla::MallocSizeOf mallocSizeOf) const +{ + Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind()); + size += get().sizeOfExcludingThis(mallocSizeOf); + return size; +} |