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