/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "vm/TypeInference-inl.h" #include "mozilla/DebugOnly.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" #include "mozilla/SizePrintfMacros.h" #include "mozilla/Sprintf.h" #include #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_BIGINT: return "BigInt"; case JSVAL_TYPE_MAGIC: return "lazyargs"; default: MOZ_CRASH("Bad type"); } } if (type.isUnknown()) return "unknown"; MOZ_ASSERT(type.isAnyObject()); return "object"; } /* static */ const char* TypeSet::TypeString(TypeSet::Type type) { if (type.isPrimitive() || type.isUnknown() || type.isAnyObject()) return NonObjectTypeString(type); static char bufs[4][40]; static unsigned which = 0; which = (which + 1) & 3; if (type.isSingleton()) { JSObject* singleton = type.singletonNoBarrier(); snprintf(bufs[which], 40, "<%s %#" PRIxPTR ">", singleton->getClass()->name, uintptr_t(singleton)); } else { snprintf(bufs[which], 40, "[%s * %#" PRIxPTR "]", type.groupNoBarrier()->clasp()->name, uintptr_t(type.groupNoBarrier())); } return bufs[which]; } /* static */ const char* TypeSet::ObjectGroupString(ObjectGroup* group) { return TypeString(TypeSet::ObjectType(group)); } #ifdef DEBUG bool js::InferSpewActive(SpewChannel channel) { static bool active[SPEW_COUNT]; static bool checked = false; if (!checked) { checked = true; PodArrayZero(active); const char* env = getenv("INFERFLAGS"); if (!env) return false; if (strstr(env, "ops")) active[ISpewOps] = true; if (strstr(env, "result")) active[ISpewResult] = true; if (strstr(env, "full")) { for (unsigned i = 0; i < SPEW_COUNT; i++) active[i] = true; } } return active[channel]; } static bool InferSpewColorable() { /* Only spew colors on xterm-color to not screw up emacs. */ static bool colorable = false; static bool checked = false; if (!checked) { checked = true; const char* env = getenv("TERM"); if (!env) return false; if (strcmp(env, "xterm-color") == 0 || strcmp(env, "xterm-256color") == 0) colorable = true; } return colorable; } const char* js::InferSpewColorReset() { if (!InferSpewColorable()) return ""; return "\x1b[0m"; } const char* js::InferSpewColor(TypeConstraint* constraint) { /* Type constraints are printed out using foreground colors. */ static const char * const colors[] = { "\x1b[31m", "\x1b[32m", "\x1b[33m", "\x1b[34m", "\x1b[35m", "\x1b[36m", "\x1b[37m" }; if (!InferSpewColorable()) return ""; return colors[DefaultHasher::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::hash(types) % 7]; } #ifdef DEBUG void js::InferSpewImpl(const char* fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "[infer] "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } #endif MOZ_NORETURN MOZ_COLD static void MOZ_FORMAT_PRINTF(2, 3) TypeFailure(JSContext* cx, const char* fmt, ...) { char msgbuf[1024]; /* Larger error messages will be truncated */ char errbuf[1024]; va_list ap; va_start(ap, fmt); VsprintfLiteral(errbuf, fmt, ap); va_end(ap); SprintfLiteral(msgbuf, "[infer failure] %s", errbuf); /* Dump type state, even if INFERFLAGS is unset. */ PrintTypes(cx, cx->compartment(), true); MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__); MOZ_CRASH(); } bool js::ObjectGroupHasProperty(JSContext* cx, ObjectGroup* group, jsid id, const Value& value) { /* * Check the correctness of the type information in the object's property * against an actual value. */ if (!group->unknownProperties() && !value.isUndefined()) { id = IdToTypeId(id); /* Watch for properties which inference does not monitor. */ if (id == id___proto__(cx) || id == id_constructor(cx) || id == id_caller(cx)) return true; TypeSet::Type type = TypeSet::GetValueType(value); AutoEnterAnalysis enter(cx); /* * We don't track types for properties inherited from prototypes which * haven't yet been accessed during analysis of the inheriting object. * Don't do the property instantiation now. */ TypeSet* types = group->maybeGetProperty(id); if (!types) return true; // Type set guards might miss when an object's group changes and its // properties become unknown. if (value.isObject()) { if (types->unknownObject()) return true; for (size_t i = 0; i < types->getObjectCount(); i++) { if (TypeSet::ObjectKey* key = types->getObject(i)) { if (key->unknownProperties()) return true; } } JSObject* obj = &value.toObject(); if (!obj->hasLazyGroup() && obj->group()->maybeOriginalUnboxedGroup()) return true; } if (!types->hasType(type)) { TypeFailure(cx, "Missing type in object %s %s: %s", TypeSet::ObjectGroupString(group), TypeIdString(id), TypeSet::TypeString(type)); } } return true; } #endif ///////////////////////////////////////////////////////////////////// // TypeSet ///////////////////////////////////////////////////////////////////// TemporaryTypeSet::TemporaryTypeSet(LifoAlloc* alloc, Type type) { if (type.isUnknown()) { flags |= TYPE_FLAG_BASE_MASK; } else if (type.isPrimitive()) { flags = PrimitiveTypeFlag(type.primitive()); if (flags == TYPE_FLAG_DOUBLE) flags |= TYPE_FLAG_INT32; } else if (type.isAnyObject()) { flags |= TYPE_FLAG_ANYOBJECT; } else if (type.isGroup() && type.group()->unknownProperties()) { flags |= TYPE_FLAG_ANYOBJECT; } else { setBaseObjectCount(1); objectSet = reinterpret_cast(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::BigInt: return baseFlags() & TYPE_FLAG_BIGINT; 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 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(TypeList* list) const; template bool TypeSet::enumerateTypes(jit::TempTypeList* list) const; inline bool TypeSet::addTypesToConstraint(JSContext* cx, TypeConstraint* constraint) { /* * Build all types in the set into a vector before triggering the * constraint, as doing so may modify this type set. */ TypeList types; if (!enumerateTypes(&types)) return false; for (unsigned i = 0; i < types.length(); i++) constraint->newType(cx, this, types[i]); return true; } #ifdef DEBUG static inline bool CompartmentsMatch(JSCompartment* a, JSCompartment* b) { return !a || !b || a == b; } #endif bool ConstraintTypeSet::addConstraint(JSContext* cx, TypeConstraint* constraint, bool callExisting) { if (!constraint) { /* OOM failure while constructing the constraint. */ return false; } MOZ_ASSERT(cx->zone()->types.activeAnalysis); MOZ_ASSERT(CompartmentsMatch(maybeCompartment(), constraint->maybeCompartment())); InferSpew(ISpewOps, "addConstraint: %sT%p%s %sC%p%s %s", InferSpewColor(this), this, InferSpewColorReset(), InferSpewColor(constraint), constraint, InferSpewColorReset(), constraint->kind()); MOZ_ASSERT(constraint->next == nullptr); constraint->next = constraintList; constraintList = constraint; if (callExisting) return addTypesToConstraint(cx, constraint); return true; } void TypeSet::clearObjects() { setBaseObjectCount(0); objectSet = nullptr; } JSCompartment* TypeSet::maybeCompartment() { if (unknownObject()) return nullptr; unsigned objectCount = getObjectCount(); for (unsigned i = 0; i < objectCount; i++) { ObjectKey* key = getObject(i); if (!key) continue; JSCompartment* comp = key->maybeCompartment(); if (comp) return comp; } return nullptr; } void TypeSet::addType(Type type, LifoAlloc* alloc) { MOZ_ASSERT(CompartmentsMatch(maybeCompartment(), type.maybeCompartment())); if (unknown()) return; if (type.isUnknown()) { flags |= TYPE_FLAG_BASE_MASK; clearObjects(); MOZ_ASSERT(unknown()); return; } if (type.isPrimitive()) { TypeFlags flag = PrimitiveTypeFlag(type.primitive()); if (flags & flag) return; /* If we add float to a type set it is also considered to contain int. */ if (flag == TYPE_FLAG_DOUBLE) flag |= TYPE_FLAG_INT32; flags |= flag; return; } if (flags & TYPE_FLAG_ANYOBJECT) return; if (type.isAnyObject()) goto unknownObject; { uint32_t objectCount = baseObjectCount(); ObjectKey* key = type.objectKey(); ObjectKey** pentry = TypeHashSet::Insert (*alloc, objectSet, objectCount, key); if (!pentry) goto unknownObject; if (*pentry) return; *pentry = key; setBaseObjectCount(objectCount); // Limit the number of objects we track. There is a different limit // depending on whether the set only contains DOM objects, which can // have many different classes and prototypes but are still optimizable // by IonMonkey. if (objectCount >= TYPE_FLAG_OBJECT_COUNT_LIMIT) { JS_STATIC_ASSERT(TYPE_FLAG_DOMOBJECT_COUNT_LIMIT >= TYPE_FLAG_OBJECT_COUNT_LIMIT); // Examining the entire type set is only required when we first hit // the normal object limit. if (objectCount == TYPE_FLAG_OBJECT_COUNT_LIMIT) { for (unsigned i = 0; i < objectCount; i++) { const Class* clasp = getObjectClass(i); if (clasp && !clasp->isDOMClass()) goto unknownObject; } } // Make sure the newly added object is also a DOM object. if (!key->clasp()->isDOMClass()) goto unknownObject; // Limit the number of DOM objects. if (objectCount == TYPE_FLAG_DOMOBJECT_COUNT_LIMIT) goto unknownObject; } } if (type.isGroup()) { ObjectGroup* ngroup = type.group(); MOZ_ASSERT(!ngroup->singleton()); if (ngroup->unknownProperties()) goto unknownObject; // If we add a partially initialized group to a type set, add the // corresponding fully initialized group, as an object's group may change // from the former to the latter via the acquired properties analysis. if (ngroup->newScript() && ngroup->newScript()->initializedGroup()) addType(ObjectType(ngroup->newScript()->initializedGroup()), alloc); } if (false) { unknownObject: flags |= TYPE_FLAG_ANYOBJECT; clearObjects(); } } // This class is used for post barriers on type set contents. The only times // when type sets contain nursery references is when a nursery object has its // group dynamically changed to a singleton. In such cases the type set will // need to be traced at the next minor GC. // // There is no barrier used for TemporaryTypeSets. These type sets are only // used during Ion compilation, and if some ConstraintTypeSet contains nursery // pointers then any number of TemporaryTypeSets might as well. Thus, if there // are any such ConstraintTypeSets in existence, all off thread Ion // compilations are canceled by the next minor GC. class TypeSetRef : public BufferableRef { Zone* zone; ConstraintTypeSet* types; public: TypeSetRef(Zone* zone, ConstraintTypeSet* types) : zone(zone), types(types) {} void trace(JSTracer* trc) override { types->trace(zone, trc); } }; void ConstraintTypeSet::postWriteBarrier(ExclusiveContext* cx, Type type) { if (type.isSingletonUnchecked() && IsInsideNursery(type.singletonNoBarrier())) { JSRuntime* rt = cx->asJSContext()->runtime(); rt->gc.storeBuffer.putGeneric(TypeSetRef(cx->zone(), this)); rt->gc.storeBuffer.setShouldCancelIonCompilations(); } } void ConstraintTypeSet::addType(ExclusiveContext* cxArg, Type type) { MOZ_ASSERT(cxArg->zone()->types.activeAnalysis); if (hasType(type)) return; TypeSet::addType(type, &cxArg->typeLifoAlloc()); if (type.isObjectUnchecked() && unknownObject()) type = AnyObjectType(); postWriteBarrier(cxArg, type); InferSpew(ISpewOps, "addType: %sT%p%s %s", InferSpewColor(this), this, InferSpewColorReset(), TypeString(type)); /* Propagate the type to all constraints. */ if (JSContext* cx = cxArg->maybeJSContext()) { TypeConstraint* constraint = constraintList; while (constraint) { constraint->newType(cx, this, type); constraint = constraint->next; } } else { MOZ_ASSERT(!constraintList); } } void TypeSet::print(FILE* fp) { bool fromDebugger = !fp; if (!fp) fp = stderr; if (flags & TYPE_FLAG_NON_DATA_PROPERTY) fprintf(fp, " [non-data]"); if (flags & TYPE_FLAG_NON_WRITABLE_PROPERTY) fprintf(fp, " [non-writable]"); if (definiteProperty()) fprintf(fp, " [definite:%d]", definiteSlot()); if (baseFlags() == 0 && !baseObjectCount()) { fprintf(fp, " missing"); return; } if (flags & TYPE_FLAG_UNKNOWN) fprintf(fp, " unknown"); if (flags & TYPE_FLAG_ANYOBJECT) fprintf(fp, " object"); if (flags & TYPE_FLAG_UNDEFINED) fprintf(fp, " void"); if (flags & TYPE_FLAG_NULL) fprintf(fp, " null"); if (flags & TYPE_FLAG_BOOLEAN) fprintf(fp, " bool"); if (flags & TYPE_FLAG_INT32) fprintf(fp, " int"); if (flags & TYPE_FLAG_DOUBLE) fprintf(fp, " float"); if (flags & TYPE_FLAG_STRING) fprintf(fp, " string"); if (flags & TYPE_FLAG_SYMBOL) fprintf(fp, " symbol"); if (flags & TYPE_FLAG_BIGINT) fprintf(fp, " BigInt"); if (flags & TYPE_FLAG_LAZYARGS) fprintf(fp, " lazyargs"); uint32_t objectCount = baseObjectCount(); if (objectCount) { fprintf(fp, " object[%u]", objectCount); unsigned count = getObjectCount(); for (unsigned i = 0; i < count; i++) { ObjectKey* key = getObject(i); if (key) fprintf(fp, " %s", TypeString(ObjectType(key))); } } if (fromDebugger) fprintf(fp, "\n"); } /* static */ void TypeSet::readBarrier(const TypeSet* types) { if (types->unknownObject()) return; for (unsigned i = 0; i < types->getObjectCount(); i++) { if (ObjectKey* key = types->getObject(i)) { if (key->isSingleton()) (void) key->singleton(); else (void) key->group(); } } } /* static */ bool TypeSet::IsTypeMarked(JSRuntime* rt, TypeSet::Type* v) { bool rv; if (v->isSingletonUnchecked()) { JSObject* obj = v->singletonNoBarrier(); rv = IsMarkedUnbarriered(rt, &obj); *v = TypeSet::ObjectType(obj); } else if (v->isGroupUnchecked()) { ObjectGroup* group = v->groupNoBarrier(); rv = IsMarkedUnbarriered(rt, &group); *v = TypeSet::ObjectType(group); } else { rv = true; } return rv; } /* static */ bool TypeSet::IsTypeAllocatedDuringIncremental(TypeSet::Type v) { bool rv; if (v.isSingletonUnchecked()) { JSObject* obj = v.singletonNoBarrier(); rv = obj->isTenured() && obj->asTenured().arena()->allocatedDuringIncremental; } else if (v.isGroupUnchecked()) { ObjectGroup* group = v.groupNoBarrier(); rv = group->arena()->allocatedDuringIncremental; } else { rv = false; } return rv; } static inline bool IsObjectKeyAboutToBeFinalized(TypeSet::ObjectKey** keyp) { TypeSet::ObjectKey* key = *keyp; bool isAboutToBeFinalized; if (key->isGroup()) { ObjectGroup* group = key->groupNoBarrier(); isAboutToBeFinalized = IsAboutToBeFinalizedUnbarriered(&group); if (!isAboutToBeFinalized) *keyp = TypeSet::ObjectKey::get(group); } else { MOZ_ASSERT(key->isSingleton()); JSObject* singleton = key->singletonNoBarrier(); isAboutToBeFinalized = IsAboutToBeFinalizedUnbarriered(&singleton); if (!isAboutToBeFinalized) *keyp = TypeSet::ObjectKey::get(singleton); } return isAboutToBeFinalized; } bool TypeSet::IsTypeAboutToBeFinalized(TypeSet::Type* v) { bool isAboutToBeFinalized; if (v->isObjectUnchecked()) { TypeSet::ObjectKey* key = v->objectKey(); isAboutToBeFinalized = IsObjectKeyAboutToBeFinalized(&key); if (!isAboutToBeFinalized) *v = TypeSet::ObjectType(key); } else { isAboutToBeFinalized = false; } return isAboutToBeFinalized; } bool TypeSet::cloneIntoUninitialized(LifoAlloc* alloc, TemporaryTypeSet* result) const { unsigned objectCount = baseObjectCount(); unsigned capacity = (objectCount >= 2) ? TypeHashSet::Capacity(objectCount) : 0; ObjectKey** newSet; if (capacity) { newSet = alloc->newArray(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->pod_malloc(); if (!res || !cloneIntoUninitialized(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_(); 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_(a->baseFlags() | b->baseFlags(), static_cast(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_(flags, static_cast(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_(a->baseFlags() & b->baseFlags(), static_cast(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 constraints; // Scripts whose stack type sets were frozen for the compilation. Vector 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_(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(count); if (!types) return false; for (size_t i = 0; i < count; i++) { if (!existing[i].cloneIntoUninitialized(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 class CompilerConstraintInstance : public CompilerConstraint { T data; public: CompilerConstraintInstance(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 class TypeCompilerConstraint : public TypeConstraint { // Compilation which this constraint may invalidate. RecompileInfo compilation; T data; public: TypeCompilerConstraint(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_ >(compilation, data); return true; } JSCompartment* maybeCompartment() { return data.maybeCompartment(); } }; template bool CompilerConstraintInstance::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_ >(recompileInfo, data), /* callExisting = */ false); } } /* anonymous namespace */ const Class* TypeSet::ObjectKey::clasp() { return isGroup() ? group()->clasp() : singleton()->getClass(); } TaggedProto TypeSet::ObjectKey::proto() { return isGroup() ? group()->proto() : singleton()->taggedProto(); } TypeNewScript* TypeSet::ObjectKey::newScript() { if (isGroup() && group()->newScript()) return group()->newScript(); return nullptr; } ObjectGroup* TypeSet::ObjectKey::maybeGroup() { if (isGroup()) return group(); if (!singleton()->hasLazyGroup()) return singleton()->group(); return nullptr; } bool TypeSet::ObjectKey::unknownProperties() { if (ObjectGroup* group = maybeGroup()) return group->unknownProperties(); return false; } HeapTypeSetKey TypeSet::ObjectKey::property(jsid id) { MOZ_ASSERT(!unknownProperties()); HeapTypeSetKey property; property.object_ = this; property.id_ = id; if (ObjectGroup* group = maybeGroup()) property.maybeTypes_ = group->maybeGetProperty(id); return property; } void TypeSet::ObjectKey::ensureTrackedProperty(JSContext* cx, jsid id) { // If we are accessing a lazily defined property which actually exists in // the VM and has not been instantiated yet, instantiate it now if we are // on the main thread and able to do so. if (!JSID_IS_VOID(id) && !JSID_IS_EMPTY(id)) { MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); if (isSingleton()) { JSObject* obj = singleton(); if (obj->isNative() && obj->as().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; RootedObject objRoot(cx, obj); if (!JSObject::getGroup(cx, objRoot)) { 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()) { RootedObject obj(cx, object()->singleton()); if (!JSObject::getGroup(cx, obj)) { 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_(script_); return true; } JSCompartment* maybeCompartment() { return script_->compartment(); } }; } /* anonymous namespace */ bool js::FinishCompilation(JSContext* cx, HandleScript script, CompilerConstraintList* constraints, RecompileInfo* precompileInfo, bool* isValidOut) { if (constraints->failed()) return false; CompilerOutput co(script); TypeZone& types = cx->zone()->types; if (!types.compilerOutputs) { types.compilerOutputs = cx->new_(); if (!types.compilerOutputs) return false; } #ifdef DEBUG for (size_t i = 0; i < types.compilerOutputs->length(); i++) { const CompilerOutput& co = (*types.compilerOutputs)[i]; MOZ_ASSERT_IF(co.isValid(), co.script() != script); } #endif uint32_t index = types.compilerOutputs->length(); if (!types.compilerOutputs->append(co)) { ReportOutOfMemory(cx); return false; } *precompileInfo = RecompileInfo(index, types.generation); bool succeeded = true; for (size_t i = 0; i < constraints->length(); i++) { CompilerConstraint* constraint = constraints->get(i); if (!constraint->generateTypeConstraint(cx, *precompileInfo)) succeeded = false; } for (size_t i = 0; i < constraints->numFrozenScripts(); i++) { const CompilerConstraintList::FrozenScript& entry = constraints->frozenScript(i); if (!entry.script->types()) { succeeded = false; break; } // It could happen that one of the compiled scripts was made a // debuggee mid-compilation (e.g., via setting a breakpoint). If so, // throw away the compilation. if (entry.script->isDebuggee()) { succeeded = false; break; } if (!CheckFrozenTypeSet(cx, entry.thisTypes, TypeScript::ThisTypes(entry.script))) succeeded = false; unsigned nargs = entry.script->functionNonDelazifying() ? entry.script->functionNonDelazifying()->nargs() : 0; for (size_t i = 0; i < nargs; i++) { if (!CheckFrozenTypeSet(cx, &entry.argTypes[i], TypeScript::ArgTypes(entry.script, i))) succeeded = false; } for (size_t i = 0; i < entry.script->nTypeSets(); i++) { if (!CheckFrozenTypeSet(cx, &entry.bytecodeTypes[i], &entry.script->types()->typeArray()[i])) succeeded = false; } // If necessary, add constraints to trigger invalidation on the script // after any future changes to the stack type sets. if (entry.script->hasFreezeConstraints()) continue; size_t count = TypeScript::NumTypeSets(entry.script); StackTypeSet* array = entry.script->types()->typeArray(); for (size_t i = 0; i < count; i++) { if (!array[i].addConstraint(cx, cx->typeLifoAlloc().new_(entry.script), false)) succeeded = false; } if (succeeded) entry.script->setHasFreezeConstraints(); } if (!succeeded || types.compilerOutputs->back().pendingInvalidation()) { types.compilerOutputs->back().invalidate(); script->resetWarmUpCounter(); *isValidOut = false; return true; } *isValidOut = true; return true; } static void CheckDefinitePropertiesTypeSet(JSContext* cx, TemporaryTypeSet* frozen, StackTypeSet* actual) { // The definite properties analysis happens on the main thread, so no new // types can have been added to actual. The analysis may have updated the // contents of |frozen| though with new speculative types, and these need // to be reflected in |actual| for AddClearDefiniteFunctionUsesInScript // to work. if (!frozen->isSubset(actual)) { TypeSet::TypeList list; frozen->enumerateTypes(&list); for (size_t i = 0; i < list.length(); i++) actual->addType(cx, list[i]); } } void js::FinishDefinitePropertiesAnalysis(JSContext* cx, CompilerConstraintList* constraints) { #ifdef DEBUG // Assert no new types have been added to the StackTypeSets. Do this before // calling CheckDefinitePropertiesTypeSet, as it may add new types to the // StackTypeSets and break these invariants if a script is inlined more // than once. See also CheckDefinitePropertiesTypeSet. for (size_t i = 0; i < constraints->numFrozenScripts(); i++) { const CompilerConstraintList::FrozenScript& entry = constraints->frozenScript(i); JSScript* script = entry.script; MOZ_ASSERT(script->types()); MOZ_ASSERT(TypeScript::ThisTypes(script)->isSubset(entry.thisTypes)); unsigned nargs = entry.script->functionNonDelazifying() ? entry.script->functionNonDelazifying()->nargs() : 0; for (size_t j = 0; j < nargs; j++) MOZ_ASSERT(TypeScript::ArgTypes(script, j)->isSubset(&entry.argTypes[j])); for (size_t j = 0; j < script->nTypeSets(); j++) MOZ_ASSERT(script->types()->typeArray()[j].isSubset(&entry.bytecodeTypes[j])); } #endif for (size_t i = 0; i < constraints->numFrozenScripts(); i++) { const CompilerConstraintList::FrozenScript& entry = constraints->frozenScript(i); JSScript* script = entry.script; if (!script->types()) MOZ_CRASH(); CheckDefinitePropertiesTypeSet(cx, entry.thisTypes, TypeScript::ThisTypes(script)); unsigned nargs = script->functionNonDelazifying() ? script->functionNonDelazifying()->nargs() : 0; for (size_t j = 0; j < nargs; j++) CheckDefinitePropertiesTypeSet(cx, &entry.argTypes[j], TypeScript::ArgTypes(script, j)); for (size_t j = 0; j < script->nTypeSets(); j++) CheckDefinitePropertiesTypeSet(cx, &entry.bytecodeTypes[j], &script->types()->typeArray()[j]); } } namespace { // Constraint which triggers recompilation of a script if any type is added to a type set. */ class ConstraintDataFreeze { public: ConstraintDataFreeze() {} const char* kind() { return "freeze"; } bool invalidateOnNewType(TypeSet::Type type) { return true; } bool invalidateOnNewPropertyState(TypeSet* property) { return true; } bool invalidateOnNewObjectState(ObjectGroup* group) { return false; } bool constraintHolds(JSContext* cx, const HeapTypeSetKey& property, TemporaryTypeSet* expected) { return expected ? property.maybeTypes()->isSubset(expected) : property.maybeTypes()->empty(); } bool shouldSweep() { return false; } JSCompartment* maybeCompartment() { return nullptr; } }; } /* anonymous namespace */ void HeapTypeSetKey::freeze(CompilerConstraintList* constraints) { LifoAlloc* alloc = constraints->alloc(); typedef CompilerConstraintInstance T; constraints->add(alloc->new_(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_BIGINT: return jit::MIRType::BigInt; 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 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()); if (!allowEmptyTypesForGlobal) { if (CanHaveEmptyPropertyTypesForOwnProperty(obj)) return true; } } freeze(constraints); return false; } bool HeapTypeSetKey::knownSubset(CompilerConstraintList* constraints, const HeapTypeSetKey& other) { if (!maybeTypes() || maybeTypes()->empty()) { freeze(constraints); return true; } if (!other.maybeTypes() || !maybeTypes()->isSubset(other.maybeTypes())) return false; freeze(constraints); return true; } JSObject* TemporaryTypeSet::maybeSingleton() { if (baseFlags() != 0 || baseObjectCount() != 1) return nullptr; return getSingleton(0); } JSObject* HeapTypeSetKey::singleton(CompilerConstraintList* constraints) { HeapTypeSet* types = maybeTypes(); if (!types || types->nonDataProperty() || types->baseFlags() != 0 || types->getObjectCount() != 1) return nullptr; JSObject* obj = types->getSingleton(0); if (obj) freeze(constraints); return obj; } bool HeapTypeSetKey::needsBarrier(CompilerConstraintList* constraints) { TypeSet* types = maybeTypes(); if (!types) return false; bool result = types->unknownObject() || types->getObjectCount() > 0 || types->hasAnyFlag(TYPE_FLAG_STRING | TYPE_FLAG_SYMBOL); if (!result) freeze(constraints); return result; } namespace { // Constraint which triggers recompilation if an object acquires particular flags. class ConstraintDataFreezeObjectFlags { public: // Flags we are watching for on this object. ObjectGroupFlags flags; explicit ConstraintDataFreezeObjectFlags(ObjectGroupFlags flags) : flags(flags) { MOZ_ASSERT(flags); } const char* kind() { return "freezeObjectFlags"; } bool invalidateOnNewType(TypeSet::Type type) { return false; } bool invalidateOnNewPropertyState(TypeSet* property) { return false; } bool invalidateOnNewObjectState(ObjectGroup* group) { return group->hasAnyFlags(flags); } bool constraintHolds(JSContext* cx, const HeapTypeSetKey& property, TemporaryTypeSet* expected) { return !invalidateOnNewObjectState(property.object()->maybeGroup()); } bool shouldSweep() { return false; } JSCompartment* maybeCompartment() { return nullptr; } }; } /* anonymous namespace */ bool TypeSet::ObjectKey::hasFlags(CompilerConstraintList* constraints, ObjectGroupFlags flags) { MOZ_ASSERT(flags); if (ObjectGroup* group = maybeGroup()) { if (group->hasAnyFlags(flags)) return true; } HeapTypeSetKey objectProperty = property(JSID_EMPTY); LifoAlloc* alloc = constraints->alloc(); typedef CompilerConstraintInstance T; constraints->add(alloc->new_(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 T; constraints->add(alloc->new_(alloc, objectProperty, ConstraintDataFreezeObjectFlags(OBJECT_FLAG_PRE_TENURE))); return gc::DefaultHeap; } namespace { // Constraint which triggers recompilation on any type change in an inlined // script. The freeze constraints added to stack type sets will only directly // invalidate the script containing those stack type sets. To invalidate code // for scripts into which the base script was inlined, ObjectStateChange is used. class ConstraintDataFreezeObjectForInlinedCall { public: ConstraintDataFreezeObjectForInlinedCall() {} const char* kind() { return "freezeObjectForInlinedCall"; } bool invalidateOnNewType(TypeSet::Type type) { return false; } bool invalidateOnNewPropertyState(TypeSet* property) { return false; } bool invalidateOnNewObjectState(ObjectGroup* group) { // We don't keep track of the exact dependencies the caller has on its // inlined scripts' type sets, so always invalidate the caller. return true; } bool constraintHolds(JSContext* cx, const HeapTypeSetKey& property, TemporaryTypeSet* expected) { return true; } bool shouldSweep() { return false; } JSCompartment* maybeCompartment() { return nullptr; } }; // Constraint which triggers recompilation when a typed array's data becomes // invalid. class ConstraintDataFreezeObjectForTypedArrayData { NativeObject* obj; uintptr_t viewData; uint32_t length; public: explicit ConstraintDataFreezeObjectForTypedArrayData(TypedArrayObject& tarray) : obj(&tarray), viewData(tarray.viewDataEither().unwrapValue()), length(tarray.length()) { MOZ_ASSERT(tarray.isSingleton()); } const char* kind() { return "freezeObjectForTypedArrayData"; } bool invalidateOnNewType(TypeSet::Type type) { return false; } bool invalidateOnNewPropertyState(TypeSet* property) { return false; } bool invalidateOnNewObjectState(ObjectGroup* group) { MOZ_ASSERT(obj->group() == group); TypedArrayObject& tarr = obj->as(); return tarr.viewDataEither().unwrapValue() != viewData || tarr.length() != length; } bool constraintHolds(JSContext* cx, const HeapTypeSetKey& property, TemporaryTypeSet* expected) { return !invalidateOnNewObjectState(property.object()->maybeGroup()); } bool shouldSweep() { // Note: |viewData| is only used for equality testing. return IsAboutToBeFinalizedUnbarriered(&obj); } JSCompartment* maybeCompartment() { return obj->compartment(); } }; // Constraint which triggers recompilation if an unboxed object in some group // is converted to a native object. class ConstraintDataFreezeObjectForUnboxedConvertedToNative { public: ConstraintDataFreezeObjectForUnboxedConvertedToNative() {} const char* kind() { return "freezeObjectForUnboxedConvertedToNative"; } bool invalidateOnNewType(TypeSet::Type type) { return false; } bool invalidateOnNewPropertyState(TypeSet* property) { return false; } bool invalidateOnNewObjectState(ObjectGroup* group) { return group->unboxedLayout().nativeGroup() != nullptr; } bool constraintHolds(JSContext* cx, const HeapTypeSetKey& property, TemporaryTypeSet* expected) { return !invalidateOnNewObjectState(property.object()->maybeGroup()); } bool shouldSweep() { return false; } JSCompartment* maybeCompartment() { return nullptr; } }; } /* anonymous namespace */ void TypeSet::ObjectKey::watchStateChangeForInlinedCall(CompilerConstraintList* constraints) { HeapTypeSetKey objectProperty = property(JSID_EMPTY); LifoAlloc* alloc = constraints->alloc(); typedef CompilerConstraintInstance T; constraints->add(alloc->new_(alloc, objectProperty, ConstraintDataFreezeObjectForInlinedCall())); } void TypeSet::ObjectKey::watchStateChangeForTypedArrayData(CompilerConstraintList* constraints) { TypedArrayObject& tarray = singleton()->as(); HeapTypeSetKey objectProperty = property(JSID_EMPTY); LifoAlloc* alloc = constraints->alloc(); typedef CompilerConstraintInstance T; constraints->add(alloc->new_(alloc, objectProperty, ConstraintDataFreezeObjectForTypedArrayData(tarray))); } void TypeSet::ObjectKey::watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList* constraints) { HeapTypeSetKey objectProperty = property(JSID_EMPTY); LifoAlloc* alloc = constraints->alloc(); typedef CompilerConstraintInstance T; constraints->add(alloc->new_(alloc, objectProperty, ConstraintDataFreezeObjectForUnboxedConvertedToNative())); } static void ObjectStateChange(ExclusiveContext* cxArg, ObjectGroup* group, bool markingUnknown) { if (group->unknownProperties()) return; /* All constraints listening to state changes are on the empty id. */ HeapTypeSet* types = group->maybeGetProperty(JSID_EMPTY); /* Mark as unknown after getting the types, to avoid assertion. */ if (markingUnknown) group->addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); if (types) { if (JSContext* cx = cxArg->maybeJSContext()) { TypeConstraint* constraint = types->constraintList; while (constraint) { constraint->newObjectState(cx, group); constraint = constraint->next; } } else { MOZ_ASSERT(!types->constraintList); } } } namespace { class ConstraintDataFreezePropertyState { public: enum Which { NON_DATA, NON_WRITABLE } which; explicit ConstraintDataFreezePropertyState(Which which) : which(which) {} const char* kind() { return (which == NON_DATA) ? "freezeNonDataProperty" : "freezeNonWritableProperty"; } bool invalidateOnNewType(TypeSet::Type type) { return false; } bool invalidateOnNewPropertyState(TypeSet* property) { return (which == NON_DATA) ? property->nonDataProperty() : property->nonWritableProperty(); } bool invalidateOnNewObjectState(ObjectGroup* group) { return false; } bool constraintHolds(JSContext* cx, const HeapTypeSetKey& property, TemporaryTypeSet* expected) { return !invalidateOnNewPropertyState(property.maybeTypes()); } bool shouldSweep() { return false; } JSCompartment* maybeCompartment() { return nullptr; } }; } /* anonymous namespace */ bool HeapTypeSetKey::nonData(CompilerConstraintList* constraints) { if (maybeTypes() && maybeTypes()->nonDataProperty()) return true; LifoAlloc* alloc = constraints->alloc(); typedef CompilerConstraintInstance T; constraints->add(alloc->new_(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 T; constraints->add(alloc->new_(alloc, *this, ConstraintDataFreezePropertyState(ConstraintDataFreezePropertyState::NON_WRITABLE))); return false; } namespace { class ConstraintDataConstantProperty { public: explicit ConstraintDataConstantProperty() {} const char* kind() { return "constantProperty"; } bool invalidateOnNewType(TypeSet::Type type) { return false; } bool invalidateOnNewPropertyState(TypeSet* property) { return property->nonConstantProperty(); } bool invalidateOnNewObjectState(ObjectGroup* group) { return false; } bool constraintHolds(JSContext* cx, const HeapTypeSetKey& property, TemporaryTypeSet* expected) { return !invalidateOnNewPropertyState(property.maybeTypes()); } bool shouldSweep() { return false; } JSCompartment* maybeCompartment() { return nullptr; } }; } /* anonymous namespace */ bool HeapTypeSetKey::constant(CompilerConstraintList* constraints, Value* valOut) { if (nonData(constraints)) return false; // Only singleton object properties can be marked as constants. JSObject* obj = object()->singleton(); if (!obj || !obj->isNative()) return false; if (maybeTypes() && maybeTypes()->nonConstantProperty()) return false; // Get the current value of the property. Shape* shape = obj->as().lookupPure(id()); if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot() || shape->hadOverwrite()) return false; Value val = obj->as().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 T; constraints->add(alloc->new_(alloc, *this, ConstraintDataConstantProperty())); return true; } // A constraint that never triggers recompilation. class ConstraintDataInert { public: explicit ConstraintDataInert() {} const char* kind() { return "inert"; } bool invalidateOnNewType(TypeSet::Type type) { return false; } bool invalidateOnNewPropertyState(TypeSet* property) { return false; } bool invalidateOnNewObjectState(ObjectGroup* group) { return false; } bool constraintHolds(JSContext* cx, const HeapTypeSetKey& property, TemporaryTypeSet* expected) { return true; } bool shouldSweep() { return false; } JSCompartment* maybeCompartment() { return nullptr; } }; bool HeapTypeSetKey::couldBeConstant(CompilerConstraintList* constraints) { // Only singleton object properties can be marked as constants. if (!object()->isSingleton()) return false; if (!maybeTypes() || !maybeTypes()->nonConstantProperty()) return true; // It is possible for a property that was not marked as constant to // 'become' one, if we throw away the type property during a GC and // regenerate it with the constant flag set. ObjectGroup::sweep only removes // type properties if they have no constraints attached to them, so add // inert constraints to pin these properties in place. LifoAlloc* alloc = constraints->alloc(); typedef CompilerConstraintInstance T; constraints->add(alloc->new_(alloc, *this, ConstraintDataInert())); return false; } bool TemporaryTypeSet::filtersType(const TemporaryTypeSet* other, Type filteredType) const { if (other->unknown()) return unknown(); for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) { Type type = PrimitiveType(TypeFlagPrimitive(flag)); if (type != filteredType && other->hasType(type) && !hasType(type)) return false; } if (other->unknownObject()) return unknownObject(); for (size_t i = 0; i < other->getObjectCount(); i++) { ObjectKey* key = other->getObject(i); if (key) { Type type = ObjectType(key); if (type != filteredType && !hasType(type)) return false; } } return true; } TemporaryTypeSet::DoubleConversion TemporaryTypeSet::convertDoubleElements(CompilerConstraintList* constraints) { if (unknownObject() || !getObjectCount()) return AmbiguousDoubleConversion; bool alwaysConvert = true; bool maybeConvert = false; bool dontConvert = false; for (unsigned i = 0; i < getObjectCount(); i++) { ObjectKey* key = getObject(i); if (!key) continue; if (key->unknownProperties()) { alwaysConvert = false; continue; } HeapTypeSetKey property = key->property(JSID_VOID); property.freeze(constraints); // We can't convert to double elements for objects which do not have // double in their element types (as the conversion may render the type // information incorrect), nor for non-array objects (as their elements // may point to emptyObjectElements or emptyObjectElementsShared, which // cannot be converted). if (!property.maybeTypes() || !property.maybeTypes()->hasType(DoubleType()) || key->clasp() != &ArrayObject::class_) { dontConvert = true; alwaysConvert = false; continue; } // Only bother with converting known packed arrays whose possible // element types are int or double. Other arrays require type tests // when elements are accessed regardless of the conversion. if (property.knownMIRType(constraints) == jit::MIRType::Double && !key->hasFlags(constraints, OBJECT_FLAG_NON_PACKED)) { maybeConvert = true; } else { alwaysConvert = false; } } MOZ_ASSERT_IF(alwaysConvert, maybeConvert); if (maybeConvert && dontConvert) return AmbiguousDoubleConversion; if (alwaysConvert) return AlwaysConvertToDoubles; if (maybeConvert) return MaybeConvertToDoubles; return DontConvertToDoubles; } const Class* TemporaryTypeSet::getKnownClass(CompilerConstraintList* constraints) { if (unknownObject()) return nullptr; const Class* clasp = nullptr; unsigned count = getObjectCount(); for (unsigned i = 0; i < count; i++) { const Class* nclasp = getObjectClass(i); if (!nclasp) continue; if (getObject(i)->unknownProperties()) return nullptr; if (clasp && clasp != nclasp) return nullptr; clasp = nclasp; } if (clasp) { for (unsigned i = 0; i < count; i++) { ObjectKey* key = getObject(i); if (key && !key->hasStableClassAndProto(constraints)) return nullptr; } } return clasp; } void TemporaryTypeSet::getTypedArraySharedness(CompilerConstraintList* constraints, TypedArraySharedness* sharedness) { // In the future this will inspect the object set. *sharedness = UnknownSharedness; } TemporaryTypeSet::ForAllResult TemporaryTypeSet::forAllClasses(CompilerConstraintList* constraints, bool (*func)(const Class* clasp)) { if (unknownObject()) return ForAllResult::MIXED; unsigned count = getObjectCount(); if (count == 0) return ForAllResult::EMPTY; bool true_results = false; bool false_results = false; for (unsigned i = 0; i < count; i++) { const Class* clasp = getObjectClass(i); if (!clasp) continue; if (!getObject(i)->hasStableClassAndProto(constraints)) return ForAllResult::MIXED; if (func(clasp)) { true_results = true; if (false_results) return ForAllResult::MIXED; } else { false_results = true; if (true_results) return ForAllResult::MIXED; } } MOZ_ASSERT(true_results != false_results); return true_results ? ForAllResult::ALL_TRUE : ForAllResult::ALL_FALSE; } Scalar::Type TemporaryTypeSet::getTypedArrayType(CompilerConstraintList* constraints, TypedArraySharedness* sharedness) { const Class* clasp = getKnownClass(constraints); if (clasp && IsTypedArrayClass(clasp)) { if (sharedness) getTypedArraySharedness(constraints, sharedness); return (Scalar::Type) (clasp - &TypedArrayObject::classes[0]); } return Scalar::MaxTypedArrayViewType; } bool TemporaryTypeSet::isDOMClass(CompilerConstraintList* constraints) { if (unknownObject()) return false; unsigned count = getObjectCount(); for (unsigned i = 0; i < count; i++) { const Class* clasp = getObjectClass(i); if (!clasp) continue; if (!clasp->isDOMClass() || !getObject(i)->hasStableClassAndProto(constraints)) return false; } return count > 0; } bool TemporaryTypeSet::maybeCallable(CompilerConstraintList* constraints) { if (!maybeObject()) return false; if (unknownObject()) return true; unsigned count = getObjectCount(); for (unsigned i = 0; i < count; i++) { const Class* clasp = getObjectClass(i); if (!clasp) continue; if (clasp->isProxy() || clasp->nonProxyCallable()) return true; if (!getObject(i)->hasStableClassAndProto(constraints)) return true; } return false; } bool TemporaryTypeSet::maybeEmulatesUndefined(CompilerConstraintList* constraints) { if (!maybeObject()) return false; if (unknownObject()) return true; unsigned count = getObjectCount(); for (unsigned i = 0; i < count; i++) { // The object emulates undefined if clasp->emulatesUndefined() or if // it's a WrapperObject, see EmulatesUndefined. Since all wrappers are // proxies, we can just check for that. const Class* clasp = getObjectClass(i); if (!clasp) continue; if (clasp->emulatesUndefined() || clasp->isProxy()) return true; if (!getObject(i)->hasStableClassAndProto(constraints)) return true; } return false; } bool TemporaryTypeSet::getCommonPrototype(CompilerConstraintList* constraints, JSObject** proto) { if (unknownObject()) return false; *proto = nullptr; bool isFirst = true; unsigned count = getObjectCount(); for (unsigned i = 0; i < count; i++) { ObjectKey* key = getObject(i); if (!key) continue; if (key->unknownProperties()) return false; TaggedProto nproto = key->proto(); if (isFirst) { if (nproto.isDynamic()) return false; *proto = nproto.toObjectOrNull(); isFirst = false; } else { if (nproto != TaggedProto(*proto)) return false; } } // Guard against mutating __proto__. for (unsigned i = 0; i < count; i++) { if (ObjectKey* key = getObject(i)) JS_ALWAYS_TRUE(key->hasStableClassAndProto(constraints)); } return true; } bool TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList* constraints, jsid id) { if (unknownObject()) return true; for (unsigned i = 0; i < getObjectCount(); i++) { ObjectKey* key = getObject(i); if (!key) continue; if (key->unknownProperties()) return true; HeapTypeSetKey property = key->property(id); if (property.needsBarrier(constraints)) return true; } return false; } bool js::ClassCanHaveExtraProperties(const Class* clasp) { if (clasp == &UnboxedPlainObject::class_ || clasp == &UnboxedArrayObject::class_) return false; return clasp->getResolve() || clasp->getOpsLookupProperty() || clasp->getOpsGetProperty() || IsTypedArrayClass(clasp); } void TypeZone::processPendingRecompiles(FreeOp* fop, RecompileInfoVector& recompiles) { MOZ_ASSERT(!recompiles.empty()); /* * Steal the list of scripts to recompile, to make sure we don't try to * recursively recompile them. */ RecompileInfoVector pending; for (size_t i = 0; i < recompiles.length(); i++) { AutoEnterOOMUnsafeRegion oomUnsafe; if (!pending.append(recompiles[i])) oomUnsafe.crash("processPendingRecompiles"); } recompiles.clear(); jit::Invalidate(*this, fop, pending); MOZ_ASSERT(recompiles.empty()); } void TypeZone::addPendingRecompile(JSContext* cx, const RecompileInfo& info) { CompilerOutput* co = info.compilerOutput(cx); if (!co || !co->isValid() || co->pendingInvalidation()) return; InferSpew(ISpewOps, "addPendingRecompile: %p:%s:%" PRIuSIZE, co->script(), co->script()->filename(), co->script()->lineno()); co->setPendingInvalidation(); AutoEnterOOMUnsafeRegion oomUnsafe; if (!cx->zone()->types.activeAnalysis->pendingRecompiles.append(info)) oomUnsafe.crash("Could not update pendingRecompiles"); } void TypeZone::addPendingRecompile(JSContext* cx, JSScript* script) { MOZ_ASSERT(script); CancelOffThreadIonCompile(script); // Let the script warm up again before attempting another compile. if (jit::IsBaselineEnabled(cx)) script->resetWarmUpCounter(); if (script->hasIonScript()) addPendingRecompile(cx, script->ionScript()->recompileInfo()); // When one script is inlined into another the caller listens to state // changes on the callee's script, so trigger these to force recompilation // of any such callers. if (script->functionNonDelazifying() && !script->functionNonDelazifying()->hasLazyGroup()) ObjectStateChange(cx, script->functionNonDelazifying()->group(), false); } void js::PrintTypes(JSContext* cx, JSCompartment* comp, bool force) { #ifdef DEBUG gc::AutoSuppressGC suppressGC(cx); JSAutoRequest request(cx); Zone* zone = comp->zone(); AutoEnterAnalysis enter(nullptr, zone); if (!force && !InferSpewActive(ISpewResult)) return; RootedScript script(cx); for (auto iter = zone->cellIter(); !iter.done(); iter.next()) { script = iter; if (script->types()) script->types()->printTypes(cx, script); } for (auto group = zone->cellIter(); !group.done(); group.next()) group->print(); #endif } ///////////////////////////////////////////////////////////////////// // ObjectGroup ///////////////////////////////////////////////////////////////////// static inline void UpdatePropertyType(ExclusiveContext* cx, HeapTypeSet* types, NativeObject* obj, Shape* shape, bool indexed) { MOZ_ASSERT(obj->isSingleton() && !obj->hasLazyGroup()); if (!shape->writable()) types->setNonWritableProperty(cx); if (shape->hasGetterValue() || shape->hasSetterValue()) { types->setNonDataProperty(cx); types->TypeSet::addType(TypeSet::UnknownType(), &cx->typeLifoAlloc()); } else if (shape->hasDefaultGetter() && shape->hasSlot()) { if (!indexed && types->canSetDefinite(shape->slot())) types->setDefinite(shape->slot()); const Value& value = obj->getSlot(shape->slot()); /* * Don't add initial undefined types for properties of global objects * that are not collated into the JSID_VOID property (see propertySet * comment). * * Also don't add untracked values (initial uninitialized lexical magic * values and optimized out values) as appearing in CallObjects, module * environments or the global lexical scope. */ MOZ_ASSERT_IF(TypeSet::IsUntrackedValue(value), obj->is() || obj->is() || IsExtensibleLexicalEnvironment(obj)); if ((indexed || !value.isUndefined() || !CanHaveEmptyPropertyTypesForOwnProperty(obj)) && !TypeSet::IsUntrackedValue(value)) { TypeSet::Type type = TypeSet::GetValueType(value); types->TypeSet::addType(type, &cx->typeLifoAlloc()); types->postWriteBarrier(cx, type); } if (indexed || shape->hadOverwrite()) { types->setNonConstantProperty(cx); } else { InferSpew(ISpewOps, "typeSet: %sT%p%s property %s %s - setConstant", InferSpewColor(types), types, InferSpewColorReset(), TypeSet::ObjectGroupString(obj->group()), TypeIdString(shape->propid())); } } } void ObjectGroup::updateNewPropertyTypes(ExclusiveContext* cx, JSObject* objArg, jsid id, HeapTypeSet* types) { InferSpew(ISpewOps, "typeSet: %sT%p%s property %s %s", InferSpewColor(types), types, InferSpewColorReset(), TypeSet::ObjectGroupString(this), TypeIdString(id)); MOZ_ASSERT_IF(objArg, objArg->group() == this); MOZ_ASSERT_IF(singleton(), objArg); if (!singleton() || !objArg->isNative()) { types->setNonConstantProperty(cx); return; } NativeObject* obj = &objArg->as(); /* * 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); } } void ObjectGroup::addDefiniteProperties(ExclusiveContext* cx, Shape* shape) { if (unknownProperties()) return; // Mark all properties of shape as definite properties of this group. AutoEnterAnalysis enter(cx); while (!shape->isEmptyShape()) { jsid id = IdToTypeId(shape->propid()); if (!JSID_IS_VOID(id)) { MOZ_ASSERT_IF(shape->slot() >= shape->numFixedSlots(), shape->numFixedSlots() == NativeObject::MAX_FIXED_SLOTS); TypeSet* types = getProperty(cx, nullptr, id); if (!types) { MOZ_ASSERT(unknownProperties()); return; } if (types->canSetDefinite(shape->slot())) types->setDefinite(shape->slot()); } shape = shape->previous(); } } bool ObjectGroup::matchDefiniteProperties(HandleObject obj) { unsigned count = getPropertyCount(); for (unsigned i = 0; i < count; i++) { Property* prop = getProperty(i); if (!prop) continue; if (prop->types.definiteProperty()) { unsigned slot = prop->types.definiteSlot(); bool found = false; Shape* shape = obj->as().lastProperty(); while (!shape->isEmptyShape()) { if (shape->slot() == slot && shape->propid() == prop->id) { found = true; break; } shape = shape->previous(); } if (!found) return false; } } return true; } void js::AddTypePropertyId(ExclusiveContext* cx, ObjectGroup* group, JSObject* obj, jsid id, TypeSet::Type type) { MOZ_ASSERT(id == IdToTypeId(id)); if (group->unknownProperties()) return; AutoEnterAnalysis enter(cx); HeapTypeSet* types = group->getProperty(cx, obj, id); if (!types) return; // Clear any constant flag if it exists. if (!types->empty() && !types->nonConstantProperty()) { InferSpew(ISpewOps, "constantMutated: %sT%p%s %s", InferSpewColor(types), types, InferSpewColorReset(), TypeSet::TypeString(type)); types->setNonConstantProperty(cx); } if (types->hasType(type)) return; InferSpew(ISpewOps, "externalType: property %s %s: %s", TypeSet::ObjectGroupString(group), TypeIdString(id), TypeSet::TypeString(type)); types->addType(cx, type); // If this addType caused the type set to be marked as containing any // object, make sure that is reflected in other type sets the addType is // propagated to below. if (type.isObjectUnchecked() && types->unknownObject()) type = TypeSet::AnyObjectType(); // Propagate new types from partially initialized groups to fully // initialized groups for the acquired properties analysis. Note that we // don't need to do this for other property changes, as these will also be // reflected via shape changes on the object that will prevent the object // from acquiring the fully initialized group. if (group->newScript() && group->newScript()->initializedGroup()) AddTypePropertyId(cx, group->newScript()->initializedGroup(), nullptr, id, type); // Maintain equivalent type information for unboxed object groups and their // corresponding native group. Since type sets might contain the unboxed // group but not the native group, this ensures optimizations based on the // unboxed group are valid for the native group. if (group->maybeUnboxedLayout() && group->maybeUnboxedLayout()->nativeGroup()) AddTypePropertyId(cx, group->maybeUnboxedLayout()->nativeGroup(), nullptr, id, type); if (ObjectGroup* unboxedGroup = group->maybeOriginalUnboxedGroup()) AddTypePropertyId(cx, unboxedGroup, nullptr, id, type); } void js::AddTypePropertyId(ExclusiveContext* cx, ObjectGroup* group, JSObject* obj, jsid id, const Value& value) { AddTypePropertyId(cx, group, obj, id, TypeSet::GetValueType(value)); } void ObjectGroup::markPropertyNonData(ExclusiveContext* cx, JSObject* obj, jsid id) { AutoEnterAnalysis enter(cx); id = IdToTypeId(id); HeapTypeSet* types = getProperty(cx, obj, id); if (types) types->setNonDataProperty(cx); } void ObjectGroup::markPropertyNonWritable(ExclusiveContext* cx, JSObject* obj, jsid id) { AutoEnterAnalysis enter(cx); id = IdToTypeId(id); HeapTypeSet* types = getProperty(cx, obj, id); if (types) types->setNonWritableProperty(cx); } void ObjectGroup::markStateChange(ExclusiveContext* cxArg) { if (unknownProperties()) return; AutoEnterAnalysis enter(cxArg); HeapTypeSet* types = maybeGetProperty(JSID_EMPTY); if (types) { if (JSContext* cx = cxArg->maybeJSContext()) { TypeConstraint* constraint = types->constraintList; while (constraint) { constraint->newObjectState(cx, this); constraint = constraint->next; } } else { MOZ_ASSERT(!types->constraintList); } } } void ObjectGroup::setFlags(ExclusiveContext* cx, ObjectGroupFlags flags) { if (hasAllFlags(flags)) return; AutoEnterAnalysis enter(cx); addFlags(flags); InferSpew(ISpewOps, "%s: setFlags 0x%x", TypeSet::ObjectGroupString(this), flags); ObjectStateChange(cx, this, false); // Propagate flag changes from partially to fully initialized groups for the // acquired properties analysis. if (newScript() && newScript()->initializedGroup()) newScript()->initializedGroup()->setFlags(cx, flags); // Propagate flag changes between unboxed and corresponding native groups. if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup()) maybeUnboxedLayout()->nativeGroup()->setFlags(cx, flags); if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup()) unboxedGroup->setFlags(cx, flags); } void ObjectGroup::markUnknown(ExclusiveContext* cx) { AutoEnterAnalysis enter(cx); MOZ_ASSERT(cx->zone()->types.activeAnalysis); MOZ_ASSERT(!unknownProperties()); InferSpew(ISpewOps, "UnknownProperties: %s", TypeSet::ObjectGroupString(this)); clearNewScript(cx); ObjectStateChange(cx, this, true); /* * Existing constraints may have already been added to this object, which we need * to do the right thing for. We can't ensure that we will mark all unknown * objects before they have been accessed, as the __proto__ of a known object * could be dynamically set to an unknown object, and we can decide to ignore * properties of an object during analysis (i.e. hashmaps). Adding unknown for * any properties accessed already accounts for possible values read from them. */ unsigned count = getPropertyCount(); for (unsigned i = 0; i < count; i++) { Property* prop = getProperty(i); if (prop) { prop->types.addType(cx, TypeSet::UnknownType()); prop->types.setNonDataProperty(cx); } } if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup()) MarkObjectGroupUnknownProperties(cx, unboxedGroup); if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup()) MarkObjectGroupUnknownProperties(cx, maybeUnboxedLayout()->nativeGroup()); if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup()) MarkObjectGroupUnknownProperties(cx, unboxedGroup); } TypeNewScript* ObjectGroup::anyNewScript() { if (newScript()) return newScript(); if (maybeUnboxedLayout()) return unboxedLayout().newScript(); return nullptr; } void ObjectGroup::detachNewScript(bool writeBarrier, ObjectGroup* replacement) { // Clear the TypeNewScript from this ObjectGroup and, if it has been // analyzed, remove it from the newObjectGroups table so that it will not be // produced by calling 'new' on the associated function anymore. // The TypeNewScript is not actually destroyed. TypeNewScript* newScript = anyNewScript(); MOZ_ASSERT(newScript); if (newScript->analyzed()) { ObjectGroupCompartment& objectGroups = newScript->function()->compartment()->objectGroups; TaggedProto proto = this->proto(); if (proto.isObject() && IsForwarded(proto.toObject())) proto = TaggedProto(Forwarded(proto.toObject())); JSObject* associated = MaybeForwarded(newScript->function()); if (replacement) { MOZ_ASSERT(replacement->newScript()->function() == newScript->function()); objectGroups.replaceDefaultNewGroup(nullptr, proto, associated, replacement); } else { objectGroups.removeDefaultNewGroup(nullptr, proto, associated); } } else { MOZ_ASSERT(!replacement); } if (this->newScript()) setAddendum(Addendum_None, nullptr, writeBarrier); else unboxedLayout().setNewScript(nullptr, writeBarrier); } void ObjectGroup::maybeClearNewScriptOnOOM() { MOZ_ASSERT(zone()->isGCSweepingOrCompacting()); if (!isMarked()) return; TypeNewScript* newScript = anyNewScript(); if (!newScript) return; addFlags(OBJECT_FLAG_NEW_SCRIPT_CLEARED); // This method is called during GC sweeping, so don't trigger pre barriers. detachNewScript(/* writeBarrier = */ false, nullptr); js_delete(newScript); } void ObjectGroup::clearNewScript(ExclusiveContext* cx, ObjectGroup* replacement /* = nullptr*/) { TypeNewScript* newScript = anyNewScript(); if (!newScript) return; AutoEnterAnalysis enter(cx); if (!replacement) { // Invalidate any Ion code constructing objects of this type. setFlags(cx, OBJECT_FLAG_NEW_SCRIPT_CLEARED); // Mark the constructing function as having its 'new' script cleared, so we // will not try to construct another one later. RootedFunction fun(cx, newScript->function()); if (!JSObject::setNewScriptCleared(cx, fun)) cx->recoverFromOutOfMemory(); } detachNewScript(/* writeBarrier = */ true, replacement); if (cx->isJSContext()) { bool found = newScript->rollbackPartiallyInitializedObjects(cx->asJSContext(), this); // If we managed to rollback any partially initialized objects, then // any definite properties we added due to analysis of the new script // are now invalid, so remove them. If there weren't any partially // initialized objects then we don't need to change type information, // as no more objects of this type will be created and the 'new' script // analysis was still valid when older objects were created. if (found) { for (unsigned i = 0; i < getPropertyCount(); i++) { Property* prop = getProperty(i); if (!prop) continue; if (prop->types.definiteProperty()) prop->types.setNonDataProperty(cx); } } } else { // Threads with an ExclusiveContext are not allowed to run scripts. MOZ_ASSERT(!cx->perThreadData->runtimeIfOnOwnerThread() || !cx->perThreadData->runtimeIfOnOwnerThread()->activation()); } js_delete(newScript); markStateChange(cx); } void ObjectGroup::print() { TaggedProto tagged(proto()); fprintf(stderr, "%s : %s", TypeSet::ObjectGroupString(this), tagged.isObject() ? TypeSet::TypeString(TypeSet::ObjectType(tagged.toObject())) : tagged.isDynamic() ? "(dynamic)" : "(null)"); if (unknownProperties()) { fprintf(stderr, " unknown"); } else { if (!hasAnyFlags(OBJECT_FLAG_SPARSE_INDEXES)) fprintf(stderr, " dense"); if (!hasAnyFlags(OBJECT_FLAG_NON_PACKED)) fprintf(stderr, " packed"); if (!hasAnyFlags(OBJECT_FLAG_LENGTH_OVERFLOW)) fprintf(stderr, " noLengthOverflow"); if (hasAnyFlags(OBJECT_FLAG_ITERATED)) fprintf(stderr, " iterated"); if (maybeInterpretedFunction()) fprintf(stderr, " ifun"); } unsigned count = getPropertyCount(); if (count == 0) { fprintf(stderr, " {}\n"); return; } fprintf(stderr, " {"); if (newScript()) { if (newScript()->analyzed()) { fprintf(stderr, "\n newScript %d properties", (int) newScript()->templateObject()->slotSpan()); if (newScript()->initializedGroup()) { fprintf(stderr, " initializedGroup %#" PRIxPTR " with %d properties", uintptr_t(newScript()->initializedGroup()), int(newScript()->initializedShape()->slotSpan())); } } else { fprintf(stderr, "\n newScript unanalyzed"); } } for (unsigned i = 0; i < count; i++) { Property* prop = getProperty(i); if (prop) { fprintf(stderr, "\n %s:", TypeIdString(prop->id)); prop->types.print(); } } fprintf(stderr, "\n}\n"); } ///////////////////////////////////////////////////////////////////// // Type Analysis ///////////////////////////////////////////////////////////////////// /* * Persistent constraint clearing out newScript and definite properties from * an object should a property on another object get a getter or setter. */ class TypeConstraintClearDefiniteGetterSetter : public TypeConstraint { public: ObjectGroup* group; explicit TypeConstraintClearDefiniteGetterSetter(ObjectGroup* group) : group(group) {} const char* kind() { return "clearDefiniteGetterSetter"; } void newPropertyState(JSContext* cx, TypeSet* source) { /* * Clear out the newScript shape and definite property information from * an object if the source type set could be a setter or could be * non-writable. */ if (source->nonDataProperty() || source->nonWritableProperty()) group->clearNewScript(cx); } void newType(JSContext* cx, TypeSet* source, TypeSet::Type type) {} bool sweep(TypeZone& zone, TypeConstraint** res) { if (IsAboutToBeFinalizedUnbarriered(&group)) return false; *res = zone.typeLifoAlloc.new_(group); return true; } JSCompartment* maybeCompartment() { return group->compartment(); } }; bool js::AddClearDefiniteGetterSetterForPrototypeChain(JSContext* cx, DPAConstraintInfo& constraintInfo, ObjectGroup* group, HandleId id, bool* added) { /* * 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. */ *added = false; RootedObject proto(cx, group->proto().toObjectOrNull()); while (proto) { ObjectGroup* protoGroup = JSObject::getGroup(cx, proto); if (!protoGroup) { return false; } if (protoGroup->unknownProperties()) return true; HeapTypeSet* protoTypes = protoGroup->getProperty(cx, proto, id); if (!protoTypes) return false; if (protoTypes->nonDataProperty() || protoTypes->nonWritableProperty()) return true; if (!constraintInfo.addProtoConstraint(proto, id)) return false; proto = proto->staticPrototype(); } *added = true; 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_(group); return true; } JSCompartment* maybeCompartment() { return group->compartment(); } }; bool js::AddClearDefiniteFunctionUsesInScript(JSContext* cx, ObjectGroup* group, JSScript* script, JSScript* calleeScript) { // Look for any uses of the specified calleeScript in type sets for // |script|, and add constraints to ensure that if the type sets' contents // change then the definite properties are cleared from the type. // This ensures that the inlining performed when the definite properties // analysis was done is stable. We only need to look at type sets which // contain a single object, as IonBuilder does not inline polymorphic sites // during the definite properties analysis. TypeSet::ObjectKey* calleeKey = TypeSet::ObjectType(calleeScript->functionNonDelazifying()).objectKey(); unsigned count = TypeScript::NumTypeSets(script); StackTypeSet* typeArray = script->types()->typeArray(); for (unsigned i = 0; i < count; i++) { StackTypeSet* types = &typeArray[i]; if (!types->unknownObject() && types->getObjectCount() == 1) { if (calleeKey != types->getObject(0)) { // Also check if the object is the Function.call or // Function.apply native. IonBuilder uses the presence of these // functions during inlining. JSObject* singleton = types->getSingleton(0); if (!singleton || !singleton->is()) continue; JSFunction* fun = &singleton->as(); 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_(group))) return false; } } return true; } ///////////////////////////////////////////////////////////////////// // Interface functions ///////////////////////////////////////////////////////////////////// void js::TypeMonitorCallSlow(JSContext* cx, JSObject* callee, const CallArgs& args, bool constructing) { unsigned nargs = callee->as().nargs(); JSScript* script = callee->as().nonLazyScript(); if (!constructing) TypeScript::SetThis(cx, script, args.thisv()); /* * Add constraints going up to the minimum of the actual and formal count. * If there are more actuals than formals the later values can only be * accessed through the arguments object, which is monitored. */ unsigned arg = 0; for (; arg < args.length() && arg < nargs; arg++) TypeScript::SetArgument(cx, script, arg, args[arg]); /* Watch for fewer actuals than formals to the call. */ for (; arg < nargs; arg++) TypeScript::SetArgument(cx, script, arg, UndefinedValue()); } void js::FillBytecodeTypeMap(JSScript* script, uint32_t* bytecodeMap) { uint32_t added = 0; for (jsbytecode* pc = script->code(); pc < script->codeEnd(); pc += GetBytecodeLength(pc)) { JSOp op = JSOp(*pc); if (CodeSpec[op].format & JOF_TYPESET) { bytecodeMap[added++] = script->pcToOffset(pc); if (added == script->nTypeSets()) break; } } MOZ_ASSERT(added == script->nTypeSets()); } void js::TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc, TypeSet::Type type) { assertSameCompartment(cx, script, type); AutoEnterAnalysis enter(cx); StackTypeSet* types = TypeScript::BytecodeTypes(script, pc); if (types->hasType(type)) return; InferSpew(ISpewOps, "bytecodeType: %p %05" PRIuSIZE ": %s", script, script->pcToOffset(pc), TypeSet::TypeString(type)); types->addType(cx, type); } void js::TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc, const js::Value& rval) { /* Allow the non-TYPESET scenario to simplify stubs used in compound opcodes. */ if (!(CodeSpec[*pc].format & JOF_TYPESET)) return; if (!script->hasBaselineScript()) return; TypeMonitorResult(cx, script, pc, TypeSet::GetValueType(rval)); } ///////////////////////////////////////////////////////////////////// // TypeScript ///////////////////////////////////////////////////////////////////// bool JSScript::makeTypes(JSContext* cx) { MOZ_ASSERT(!types_); AutoEnterAnalysis enter(cx); unsigned count = TypeScript::NumTypeSets(this); TypeScript* typeScript = (TypeScript*) zone()->pod_calloc(TypeScript::SizeIncludingTypeArray(count)); if (!typeScript) { ReportOutOfMemory(cx); return false; } types_ = typeScript; setTypesGeneration(cx->zone()->types.generation); #ifdef DEBUG StackTypeSet* typeArray = typeScript->typeArray(); for (unsigned i = 0; i < nTypeSets(); i++) { InferSpew(ISpewOps, "typeSet: %sT%p%s bytecode%u %p", InferSpewColor(&typeArray[i]), &typeArray[i], InferSpewColorReset(), i, this); } TypeSet* thisTypes = TypeScript::ThisTypes(this); InferSpew(ISpewOps, "typeSet: %sT%p%s this %p", InferSpewColor(thisTypes), thisTypes, InferSpewColorReset(), this); unsigned nargs = functionNonDelazifying() ? functionNonDelazifying()->nargs() : 0; for (unsigned i = 0; i < nargs; i++) { TypeSet* types = TypeScript::ArgTypes(this, i); InferSpew(ISpewOps, "typeSet: %sT%p%s arg%u %p", InferSpewColor(types), types, InferSpewColorReset(), i, this); } #endif return true; } /* static */ bool JSFunction::setTypeForScriptedFunction(ExclusiveContext* cx, HandleFunction fun, bool singleton /* = false */) { if (singleton) { if (!setSingleton(cx, fun)) return false; } else { RootedObject funProto(cx, fun->staticPrototype()); Rooted taggedProto(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()); MOZ_ASSERT(obj->getClass() == objectProto->getClass()); MOZ_ASSERT(!obj->getClass()->hasFinalize()); } *ptr = nullptr; } } } void PreliminaryObjectArrayWithTemplate::trace(JSTracer* trc) { TraceNullableEdge(trc, &shape_, "PreliminaryObjectArrayWithTemplate_shape"); } /* static */ void PreliminaryObjectArrayWithTemplate::writeBarrierPre(PreliminaryObjectArrayWithTemplate* objects) { Shape* shape = objects->shape(); if (!shape) return; JS::Zone* zone = shape->zoneFromAnyThread(); if (zone->needsIncrementalBarrier()) objects->trace(zone->barrierTracer()); } // Return whether shape consists entirely of plain data properties. static bool OnlyHasDataProperties(Shape* shape) { MOZ_ASSERT(!shape->inDictionary()); while (!shape->isEmptyShape()) { if (!shape->isDataDescriptor() || !shape->configurable() || !shape->enumerable() || !shape->writable() || !shape->hasSlot()) { return false; } shape = shape->previous(); } return true; } // Find the most recent common ancestor of two shapes, or an empty shape if // the two shapes have no common ancestor. static Shape* CommonPrefix(Shape* first, Shape* second) { MOZ_ASSERT(OnlyHasDataProperties(first)); MOZ_ASSERT(OnlyHasDataProperties(second)); while (first->slotSpan() > second->slotSpan()) first = first->previous(); while (second->slotSpan() > first->slotSpan()) second = second->previous(); while (first != second && !first->isEmptyShape()) { first = first->previous(); second = second->previous(); } return first; } void PreliminaryObjectArrayWithTemplate::maybeAnalyze(ExclusiveContext* cx, ObjectGroup* group, bool force) { // Don't perform the analyses until sufficient preliminary objects have // been allocated. if (!force && !full()) return; AutoEnterAnalysis enter(cx); ScopedJSDeletePtr 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(); if (obj->inDictionaryMode() || !OnlyHasDataProperties(obj->lastProperty())) return; if (CommonPrefix(obj->lastProperty(), shape()) != shape()) return; } } TryConvertToUnboxedLayout(cx, enter, shape(), group, preliminaryObjects); if (group->maybeUnboxedLayout()) return; if (shape()) { // We weren't able to use an unboxed layout, but since the preliminary // objects still reflect the template object's properties, and all // objects in the future will be created with those properties, the // properties can be marked as definite for objects in the group. group->addDefiniteProperties(cx, shape()); } } ///////////////////////////////////////////////////////////////////// // TypeNewScript ///////////////////////////////////////////////////////////////////// // Make a TypeNewScript for |group|, and set it up to hold the preliminary // objects created with the group. /* static */ bool TypeNewScript::make(JSContext* cx, ObjectGroup* group, JSFunction* fun) { MOZ_ASSERT(cx->zone()->types.activeAnalysis); MOZ_ASSERT(!group->newScript()); MOZ_ASSERT(!group->maybeUnboxedLayout()); // rollbackPartiallyInitializedObjects expects function_ to be // canonicalized. MOZ_ASSERT(fun->maybeCanonicalFunction() == fun); if (group->unknownProperties()) return true; ScopedJSDeletePtr newScript(cx->new_()); if (!newScript) return false; newScript->function_ = fun; newScript->preliminaryObjects = group->zone()->new_(); 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 nativeNewScript(cx->new_()); 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(initializerLength); if (!nativeNewScript->initializerList) { ReportOutOfMemory(cx); return nullptr; } PodCopy(nativeNewScript->initializerList, newScript->initializerList, initializerLength); return nativeNewScript.forget(); } size_t TypeNewScript::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { size_t n = mallocSizeOf(this); n += mallocSizeOf(preliminaryObjects); n += mallocSizeOf(initializerList); return n; } void TypeNewScript::registerNewObject(PlainObject* res) { MOZ_ASSERT(!analyzed()); // New script objects must have the maximum number of fixed slots, so that // we can adjust their shape later to match the number of fixed slots used // by the template object we eventually create. MOZ_ASSERT(res->numFixedSlots() == NativeObject::MAX_FIXED_SLOTS); preliminaryObjects->registerNewObject(res); } static bool ChangeObjectFixedSlotCount(JSContext* cx, PlainObject* obj, gc::AllocKind allocKind) { MOZ_ASSERT(OnlyHasDataProperties(obj->lastProperty())); Shape* newShape = ReshapeForAllocKind(cx, obj->lastProperty(), obj->taggedProto(), allocKind); if (!newShape) return false; obj->setLastPropertyShrinkFixedSlots(newShape); return true; } namespace { struct DestroyTypeNewScript { JSContext* cx; ObjectGroup* group; DestroyTypeNewScript(JSContext* cx, ObjectGroup* group) : cx(cx), group(group) {} ~DestroyTypeNewScript() { if (group) group->clearNewScript(cx); } }; } // namespace bool DPAConstraintInfo::finishConstraints(JSContext* cx, ObjectGroup* group) { for (const ProtoConstraint& constraint : protoConstraints_) { ObjectGroup* protoGroup = constraint.proto->group(); // Note: we rely on the group's type information being unchanged since // AddClearDefiniteGetterSetterForPrototypeChain. bool unknownProperties = protoGroup->unknownProperties(); MOZ_RELEASE_ASSERT(!unknownProperties); HeapTypeSet* protoTypes = protoGroup->getProperty(cx, constraint.proto, constraint.id); MOZ_RELEASE_ASSERT(protoTypes); MOZ_ASSERT(!protoTypes->nonDataProperty()); MOZ_ASSERT(!protoTypes->nonWritableProperty()); if (!protoTypes->addConstraint( cx, cx->typeLifoAlloc().new_( group))) { ReportOutOfMemory(cx); return false; } } for (const InliningConstraint& constraint : inliningConstraints_) { if (!AddClearDefiniteFunctionUsesInScript(cx, group, constraint.caller, constraint.callee)) { ReportOutOfMemory(cx); return false; } } return true; } 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(); // 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(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(); 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(cx, groupRoot, kind, TenuredObject); if (!templateObject_) return false; Vector initializerVector(cx); DPAConstraintInfo constraintInfo(cx); RootedPlainObject templateRoot(cx, templateObject()); RootedFunction fun(cx, function()); if (!jit::AnalyzeNewScriptDefiniteProperties(cx, constraintInfo, fun, 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(initializerVector.length()); if (!initializerList) { ReportOutOfMemory(cx); return false; } PodCopy(initializerList, initializerVector.begin(), initializerVector.length()); } // Try to use an unboxed representation for the group. if (!TryConvertToUnboxedLayout(cx, enter, templateObject()->lastProperty(), group, preliminaryObjects)) return false; js_delete(preliminaryObjects); preliminaryObjects = nullptr; if (group->maybeUnboxedLayout()) { // An unboxed layout was constructed for the group, and this has already // been hooked into it. MOZ_ASSERT(group->unboxedLayout().newScript() == this); destroyNewScript.group = nullptr; // Clear out the template object, which is not used for TypeNewScripts // with an unboxed layout. Currently it is a mutant object with a // non-native group and native shape, so make it safe for GC by changing // its group to the default for its prototype. AutoEnterOOMUnsafeRegion oomUnsafe; ObjectGroup* plainGroup = ObjectGroup::defaultNewGroup(cx, &PlainObject::class_, group->proto()); if (!plainGroup) oomUnsafe.crash("TypeNewScript::maybeAnalyze"); templateObject_->setGroup(plainGroup); templateObject_ = nullptr; return true; } if (prefixShape->slotSpan() == templateObject()->slotSpan()) { // The definite properties analysis found exactly the properties that // are held in common by the preliminary objects. No further analysis // is needed. if (!constraintInfo.finishConstraints(cx, group)) { return false; } if (!group->newScript()) { return true; } 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 protoRoot(cx, group->proto()); ObjectGroup* initialGroup = ObjectGroupCompartment::makeGroup(cx, group->clasp(), protoRoot, initialFlags); if (!initialGroup) return false; // Add the constraints. Use the initialGroup as group referenced by the // constraints because that's the group that will have the TypeNewScript // associated with it. See the detachNewScript and setNewScript calls below. if (!constraintInfo.finishConstraints(cx, initialGroup)) { return false; } if (!group->newScript()) { return true; } 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 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()) { continue; } MOZ_ASSERT(iter.calleeTemplate()->maybeCanonicalFunction()); if (iter.calleeTemplate()->maybeCanonicalFunction() != 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()) { AutoEnterOOMUnsafeRegion oomUnsafe; if (!UnboxedPlainObject::convertToNative(cx, &thisv.toObject())) oomUnsafe.crash("rollbackPartiallyInitializedObjects"); } // Found a matching frame. RootedPlainObject obj(cx, &thisv.toObject().as()); // Whether all identified 'new' properties have been initialized. bool finished = false; // If not finished, number of properties that have been added. uint32_t numProperties = 0; // Whether the current SETPROP is within an inner frame which has // finished entirely. bool pastProperty = false; // Index in pcOffsets of the outermost frame. int callDepth = pcOffsets.length() - 1; // Index in pcOffsets of the frame currently being checked for a SETPROP. int setpropDepth = callDepth; for (Initializer* init = initializerList;; init++) { if (init->kind == Initializer::SETPROP) { if (!pastProperty && pcOffsets[setpropDepth] < init->offset) { // Have not yet reached this setprop. break; } // This setprop has executed, reset state for the next one. numProperties++; pastProperty = false; setpropDepth = callDepth; } else if (init->kind == Initializer::SETPROP_FRAME) { if (!pastProperty) { if (pcOffsets[setpropDepth] < init->offset) { // Have not yet reached this inner call. break; } else if (pcOffsets[setpropDepth] > init->offset) { // Have advanced past this inner call. pastProperty = true; } else if (setpropDepth == 0) { // Have reached this call but not yet in it. break; } else { // Somewhere inside this inner call. setpropDepth--; } } } else { MOZ_ASSERT(init->kind == Initializer::DONE); finished = true; break; } } if (!finished) { (void) NativeObject::rollbackProperties(cx, obj, numProperties); found = true; } } return found; } void TypeNewScript::trace(JSTracer* trc) { TraceEdge(trc, &function_, "TypeNewScript_function"); TraceNullableEdge(trc, &templateObject_, "TypeNewScript_templateObject"); TraceNullableEdge(trc, &initializedShape_, "TypeNewScript_initializedShape"); TraceNullableEdge(trc, &initializedGroup_, "TypeNewScript_initializedGroup"); } /* static */ void TypeNewScript::writeBarrierPre(TypeNewScript* newScript) { if (newScript->function()->runtimeFromAnyThread()->isHeapCollecting()) return; JS::Zone* zone = newScript->function()->zoneFromAnyThread(); if (zone->needsIncrementalBarrier()) newScript->trace(zone->barrierTracer()); } void TypeNewScript::sweep() { if (preliminaryObjects) preliminaryObjects->sweep(); } ///////////////////////////////////////////////////////////////////// // Tracing ///////////////////////////////////////////////////////////////////// static inline void TraceObjectKey(JSTracer* trc, TypeSet::ObjectKey** keyp) { TypeSet::ObjectKey* key = *keyp; if (key->isGroup()) { ObjectGroup* group = key->groupNoBarrier(); TraceManuallyBarrieredEdge(trc, &group, "objectKey_group"); *keyp = TypeSet::ObjectKey::get(group); } else { JSObject* singleton = key->singletonNoBarrier(); TraceManuallyBarrieredEdge(trc, &singleton, "objectKey_singleton"); *keyp = TypeSet::ObjectKey::get(singleton); } } void ConstraintTypeSet::trace(Zone* zone, JSTracer* trc) { // ConstraintTypeSets only hold strong references during minor collections. MOZ_ASSERT(zone->runtimeFromMainThread()->isHeapMinorCollecting()); unsigned objectCount = baseObjectCount(); if (objectCount >= 2) { unsigned oldCapacity = TypeHashSet::Capacity(objectCount); ObjectKey** oldArray = objectSet; clearObjects(); objectCount = 0; for (unsigned i = 0; i < oldCapacity; i++) { ObjectKey* key = oldArray[i]; if (!key) continue; TraceObjectKey(trc, &key); AutoEnterOOMUnsafeRegion oomUnsafe; ObjectKey** pentry = TypeHashSet::Insert (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(key); } } static inline void AssertGCStateForSweep(Zone* zone) { MOZ_ASSERT(zone->isGCSweepingOrCompacting()); // IsAboutToBeFinalized doesn't work right on tenured objects when called // during a minor collection. MOZ_ASSERT(!zone->runtimeFromMainThread()->isHeapMinorCollecting()); } void ConstraintTypeSet::sweep(Zone* zone, AutoClearTypeInferenceStateOnOOM& oom) { AssertGCStateForSweep(zone); /* * Purge references to objects that are no longer live. Type sets hold * only weak references. For type sets containing more than one object, * live entries in the object hash need to be copied to the zone's * new arena. */ unsigned objectCount = baseObjectCount(); if (objectCount >= 2) { unsigned oldCapacity = TypeHashSet::Capacity(objectCount); ObjectKey** oldArray = objectSet; clearObjects(); objectCount = 0; for (unsigned i = 0; i < oldCapacity; i++) { ObjectKey* key = oldArray[i]; if (!key) continue; if (!IsObjectKeyAboutToBeFinalized(&key)) { ObjectKey** pentry = TypeHashSet::Insert (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(key); } else { // As above, mark type sets containing objects with unknown // properties as unknown. if (key->isGroup() && key->groupNoBarrier()->unknownPropertiesDontCheckGeneration()) flags |= TYPE_FLAG_ANYOBJECT; objectSet = nullptr; setBaseObjectCount(0); } } /* * Type constraints only hold weak references. Copy constraints referring * to data that is still live into the zone's new arena. */ TypeConstraint* constraint = constraintList; constraintList = nullptr; while (constraint) { MOZ_ASSERT(zone->types.sweepTypeLifoAlloc.contains(constraint)); TypeConstraint* copy; if (constraint->sweep(zone->types, ©)) { if (copy) { MOZ_ASSERT(zone->types.typeLifoAlloc.contains(copy)); copy->next = constraintList; constraintList = copy; } else { oom.setOOM(); } } constraint = constraint->next; } } inline void ObjectGroup::clearProperties() { setBasePropertyCount(0); propertySet = nullptr; } static void EnsureHasAutoClearTypeInferenceStateOnOOM(AutoClearTypeInferenceStateOnOOM*& oom, Zone* zone, Maybe& fallback) { if (!oom) { if (zone->types.activeAnalysis) { oom = &zone->types.activeAnalysis->oom; } else { fallback.emplace(zone); oom = &fallback.ref(); } } } /* * Before sweeping the arenas themselves, scan all groups in a compartment to * fixup weak references: property type sets referencing dead JS and type * objects, and singleton JS objects whose type is not referenced elsewhere. * This is done either incrementally as part of the sweep, or on demand as type * objects are accessed before their contents have been swept. */ void ObjectGroup::sweep(AutoClearTypeInferenceStateOnOOM* oom) { MOZ_ASSERT(generation() != zoneFromAnyThread()->types.generation); setGeneration(zone()->types.generation); AssertGCStateForSweep(zone()); Maybe fallbackOOM; EnsureHasAutoClearTypeInferenceStateOnOOM(oom, zone(), fallbackOOM); if (maybeUnboxedLayout()) { // Remove unboxed layouts that are about to be finalized from the // compartment wide list while we are still on the main thread. ObjectGroup* group = this; if (IsAboutToBeFinalizedUnbarriered(&group)) unboxedLayout().detachFromCompartment(); if (unboxedLayout().newScript()) unboxedLayout().newScript()->sweep(); // Discard constructor code to avoid holding onto ExecutablePools. if (zone()->isGCCompacting()) unboxedLayout().setConstructorCode(nullptr); } if (maybePreliminaryObjects()) maybePreliminaryObjects()->sweep(); if (newScript()) newScript()->sweep(); LifoAlloc& typeLifoAlloc = zone()->types.typeLifoAlloc; /* * Properties were allocated from the old arena, and need to be copied over * to the new one. */ unsigned propertyCount = basePropertyCount(); if (propertyCount >= 2) { unsigned oldCapacity = TypeHashSet::Capacity(propertyCount); Property** oldArray = propertySet; clearProperties(); propertyCount = 0; for (unsigned i = 0; i < oldCapacity; i++) { Property* prop = oldArray[i]; if (prop) { if (singleton() && !prop->types.constraintList && !zone()->isPreservingCode()) { /* * Don't copy over properties of singleton objects when their * presence will not be required by jitcode or type constraints * (i.e. for the definite properties analysis). The contents of * these type sets will be regenerated as necessary. */ continue; } Property* newProp = typeLifoAlloc.new_(*prop); if (newProp) { Property** pentry = TypeHashSet::Insert (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_(*prop); if (newProp) { propertySet = (Property**) newProp; newProp->types.sweep(zone(), *oom); } else { oom->setOOM(); addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); clearProperties(); return; } } } } /* static */ void JSScript::maybeSweepTypes(AutoClearTypeInferenceStateOnOOM* oom) { if (!types_ || typesGeneration() == zone()->types.generation) return; setTypesGeneration(zone()->types.generation); AssertGCStateForSweep(zone()); Maybe fallbackOOM; EnsureHasAutoClearTypeInferenceStateOnOOM(oom, zone(), fallbackOOM); TypeZone& types = zone()->types; // Destroy all type information attached to the script if desired. We can // only do this if nothing has been compiled for the script, which will be // the case unless the script has been compiled since we started sweeping. if (types.sweepReleaseTypes && !hasBaselineScript() && !hasIonScript()) { types_->destroy(); types_ = nullptr; // Freeze constraints on stack type sets need to be regenerated the // next time the script is analyzed. hasFreezeConstraints_ = false; return; } unsigned num = TypeScript::NumTypeSets(this); StackTypeSet* typeArray = types_->typeArray(); // Remove constraints and references to dead objects from stack type sets. for (unsigned i = 0; i < num; i++) typeArray[i].sweep(zone(), *oom); if (oom->hadOOM()) { // It's possible we OOM'd while copying freeze constraints, so they // need to be regenerated. hasFreezeConstraints_ = false; } // Update the recompile indexes in any IonScripts still on the script. if (hasIonScript()) ionScript()->recompileInfoRef().shouldSweep(types); } void TypeScript::destroy() { js_free(this); } void Zone::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t* typePool, size_t* baselineStubsOptimized, size_t* uniqueIdMap, size_t* shapeTables) { *typePool += types.typeLifoAlloc.sizeOfExcludingThis(mallocSizeOf); if (jitZone()) { *baselineStubsOptimized += jitZone()->optimizedStubSpace()->sizeOfExcludingThis(mallocSizeOf); } *uniqueIdMap += uniqueIds_.sizeOfExcludingThis(mallocSizeOf); *shapeTables += baseShapes.sizeOfExcludingThis(mallocSizeOf) + initialShapes.sizeOfExcludingThis(mallocSizeOf); } TypeZone::TypeZone(Zone* zone) : zone_(zone), typeLifoAlloc(TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), generation(0), compilerOutputs(nullptr), sweepTypeLifoAlloc(TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), sweepCompilerOutputs(nullptr), sweepReleaseTypes(false), activeAnalysis(nullptr) { } TypeZone::~TypeZone() { js_delete(compilerOutputs); js_delete(sweepCompilerOutputs); } void TypeZone::beginSweep(FreeOp* fop, bool releaseTypes, AutoClearTypeInferenceStateOnOOM& oom) { MOZ_ASSERT(zone()->isGCSweepingOrCompacting()); MOZ_ASSERT(!sweepCompilerOutputs); MOZ_ASSERT(!sweepReleaseTypes); sweepReleaseTypes = releaseTypes; // Clear the analysis pool, but don't release its data yet. While sweeping // types any live data will be allocated into the pool. sweepTypeLifoAlloc.steal(&typeLifoAlloc); // Sweep any invalid or dead compiler outputs, and keep track of the new // index for remaining live outputs. if (compilerOutputs) { CompilerOutputVector* newCompilerOutputs = nullptr; for (size_t i = 0; i < compilerOutputs->length(); i++) { CompilerOutput& output = (*compilerOutputs)[i]; if (output.isValid()) { JSScript* script = output.script(); if (IsAboutToBeFinalizedUnbarriered(&script)) { if (script->hasIonScript()) script->ionScript()->recompileInfoRef() = RecompileInfo(); output.invalidate(); } else { CompilerOutput newOutput(script); if (!newCompilerOutputs) newCompilerOutputs = js_new(); if (newCompilerOutputs && newCompilerOutputs->append(newOutput)) { output.setSweepIndex(newCompilerOutputs->length() - 1); } else { oom.setOOM(); script->ionScript()->recompileInfoRef() = RecompileInfo(); output.invalidate(); } } } } sweepCompilerOutputs = compilerOutputs; compilerOutputs = newCompilerOutputs; } // All existing RecompileInfos are stale and will be updated to the new // compiler outputs list later during the sweep. Don't worry about overflow // here, since stale indexes will persist only until the sweep finishes. generation++; } void TypeZone::endSweep(JSRuntime* rt) { js_delete(sweepCompilerOutputs); sweepCompilerOutputs = nullptr; sweepReleaseTypes = false; rt->gc.freeAllLifoBlocksAfterSweeping(&sweepTypeLifoAlloc); } void TypeZone::clearAllNewScriptsOnOOM() { for (auto iter = zone()->cellIter(); !iter.done(); iter.next()) { ObjectGroup* group = iter; if (!IsAboutToBeFinalizedUnbarriered(&group)) group->maybeClearNewScriptOnOOM(); } } AutoClearTypeInferenceStateOnOOM::~AutoClearTypeInferenceStateOnOOM() { if (oom) { JSRuntime* rt = zone->runtimeFromMainThread(); js::CancelOffThreadIonCompile(rt); zone->setPreservingCode(false); zone->discardJitCode(rt->defaultFreeOp(), /* discardBaselineCode = */ false); zone->types.clearAllNewScriptsOnOOM(); } } #ifdef DEBUG void TypeScript::printTypes(JSContext* cx, HandleScript script) const { MOZ_ASSERT(script->types() == this); if (!script->hasBaselineScript()) return; AutoEnterAnalysis enter(nullptr, script->zone()); if (script->functionNonDelazifying()) fprintf(stderr, "Function"); else if (script->isForEval()) fprintf(stderr, "Eval"); else fprintf(stderr, "Main"); fprintf(stderr, " %#" PRIxPTR " %s:%" PRIuSIZE " ", uintptr_t(script.get()), script->filename(), script->lineno()); if (script->functionNonDelazifying()) { if (JSAtom* name = script->functionNonDelazifying()->explicitName()) 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::size(mozilla::MallocSizeOf mallocSizeOf) const { Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind()); size += get().sizeOfExcludingThis(mallocSizeOf); return size; }