michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jsinferinlines.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/PodOperations.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "jscntxt.h" michael@0: #include "jsgc.h" michael@0: #include "jshashutil.h" michael@0: #include "jsobj.h" michael@0: #include "jsprf.h" michael@0: #include "jsscript.h" michael@0: #include "jsstr.h" michael@0: #include "jsworkers.h" michael@0: #include "prmjtime.h" michael@0: michael@0: #include "gc/Marking.h" michael@0: #ifdef JS_ION michael@0: #include "jit/BaselineJIT.h" michael@0: #include "jit/Ion.h" michael@0: #include "jit/IonAnalysis.h" michael@0: #include "jit/JitCompartment.h" michael@0: #endif michael@0: #include "js/MemoryMetrics.h" michael@0: #include "vm/Opcodes.h" michael@0: #include "vm/Shape.h" michael@0: michael@0: #include "jsatominlines.h" michael@0: #include "jsgcinlines.h" michael@0: #include "jsobjinlines.h" michael@0: #include "jsscriptinlines.h" michael@0: michael@0: #include "jit/ExecutionMode-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: using namespace js::types; michael@0: using namespace js::analyze; michael@0: michael@0: using mozilla::DebugOnly; michael@0: using mozilla::Maybe; michael@0: using mozilla::PodArrayZero; michael@0: using mozilla::PodCopy; michael@0: using mozilla::PodZero; michael@0: michael@0: static inline jsid michael@0: id_prototype(JSContext *cx) { michael@0: return NameToId(cx->names().prototype); michael@0: } michael@0: michael@0: static inline jsid michael@0: id___proto__(JSContext *cx) { michael@0: return NameToId(cx->names().proto); michael@0: } michael@0: michael@0: static inline jsid michael@0: id_constructor(JSContext *cx) { michael@0: return NameToId(cx->names().constructor); michael@0: } michael@0: michael@0: static inline jsid michael@0: id_caller(JSContext *cx) { michael@0: return NameToId(cx->names().caller); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: const char * michael@0: types::TypeIdStringImpl(jsid id) michael@0: { michael@0: if (JSID_IS_VOID(id)) michael@0: return "(index)"; michael@0: if (JSID_IS_EMPTY(id)) michael@0: return "(new)"; michael@0: static char bufs[4][100]; michael@0: static unsigned which = 0; michael@0: which = (which + 1) & 3; michael@0: PutEscapedString(bufs[which], 100, JSID_TO_FLAT_STRING(id), 0); michael@0: return bufs[which]; michael@0: } michael@0: #endif michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // Logging michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: #ifdef DEBUG michael@0: michael@0: static bool InferSpewActive(SpewChannel channel) michael@0: { michael@0: static bool active[SPEW_COUNT]; michael@0: static bool checked = false; michael@0: if (!checked) { michael@0: checked = true; michael@0: PodArrayZero(active); michael@0: const char *env = getenv("INFERFLAGS"); michael@0: if (!env) michael@0: return false; michael@0: if (strstr(env, "ops")) michael@0: active[ISpewOps] = true; michael@0: if (strstr(env, "result")) michael@0: active[ISpewResult] = true; michael@0: if (strstr(env, "full")) { michael@0: for (unsigned i = 0; i < SPEW_COUNT; i++) michael@0: active[i] = true; michael@0: } michael@0: } michael@0: return active[channel]; michael@0: } michael@0: michael@0: static bool InferSpewColorable() michael@0: { michael@0: /* Only spew colors on xterm-color to not screw up emacs. */ michael@0: static bool colorable = false; michael@0: static bool checked = false; michael@0: if (!checked) { michael@0: checked = true; michael@0: const char *env = getenv("TERM"); michael@0: if (!env) michael@0: return false; michael@0: if (strcmp(env, "xterm-color") == 0 || strcmp(env, "xterm-256color") == 0) michael@0: colorable = true; michael@0: } michael@0: return colorable; michael@0: } michael@0: michael@0: const char * michael@0: types::InferSpewColorReset() michael@0: { michael@0: if (!InferSpewColorable()) michael@0: return ""; michael@0: return "\x1b[0m"; michael@0: } michael@0: michael@0: const char * michael@0: types::InferSpewColor(TypeConstraint *constraint) michael@0: { michael@0: /* Type constraints are printed out using foreground colors. */ michael@0: static const char * const colors[] = { "\x1b[31m", "\x1b[32m", "\x1b[33m", michael@0: "\x1b[34m", "\x1b[35m", "\x1b[36m", michael@0: "\x1b[37m" }; michael@0: if (!InferSpewColorable()) michael@0: return ""; michael@0: return colors[DefaultHasher::hash(constraint) % 7]; michael@0: } michael@0: michael@0: const char * michael@0: types::InferSpewColor(TypeSet *types) michael@0: { michael@0: /* Type sets are printed out using bold colors. */ michael@0: static const char * const colors[] = { "\x1b[1;31m", "\x1b[1;32m", "\x1b[1;33m", michael@0: "\x1b[1;34m", "\x1b[1;35m", "\x1b[1;36m", michael@0: "\x1b[1;37m" }; michael@0: if (!InferSpewColorable()) michael@0: return ""; michael@0: return colors[DefaultHasher::hash(types) % 7]; michael@0: } michael@0: michael@0: const char * michael@0: types::TypeString(Type type) michael@0: { michael@0: if (type.isPrimitive()) { michael@0: switch (type.primitive()) { michael@0: case JSVAL_TYPE_UNDEFINED: michael@0: return "void"; michael@0: case JSVAL_TYPE_NULL: michael@0: return "null"; michael@0: case JSVAL_TYPE_BOOLEAN: michael@0: return "bool"; michael@0: case JSVAL_TYPE_INT32: michael@0: return "int"; michael@0: case JSVAL_TYPE_DOUBLE: michael@0: return "float"; michael@0: case JSVAL_TYPE_STRING: michael@0: return "string"; michael@0: case JSVAL_TYPE_MAGIC: michael@0: return "lazyargs"; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("Bad type"); michael@0: } michael@0: } michael@0: if (type.isUnknown()) michael@0: return "unknown"; michael@0: if (type.isAnyObject()) michael@0: return " object"; michael@0: michael@0: static char bufs[4][40]; michael@0: static unsigned which = 0; michael@0: which = (which + 1) & 3; michael@0: michael@0: if (type.isSingleObject()) michael@0: JS_snprintf(bufs[which], 40, "<0x%p>", (void *) type.singleObject()); michael@0: else michael@0: JS_snprintf(bufs[which], 40, "[0x%p]", (void *) type.typeObject()); michael@0: michael@0: return bufs[which]; michael@0: } michael@0: michael@0: const char * michael@0: types::TypeObjectString(TypeObject *type) michael@0: { michael@0: return TypeString(Type::ObjectType(type)); michael@0: } michael@0: michael@0: unsigned JSScript::id() { michael@0: if (!id_) { michael@0: id_ = ++compartment()->types.scriptCount; michael@0: InferSpew(ISpewOps, "script #%u: %p %s:%d", michael@0: id_, this, filename() ? filename() : "", lineno()); michael@0: } michael@0: return id_; michael@0: } michael@0: michael@0: void michael@0: types::InferSpew(SpewChannel channel, const char *fmt, ...) michael@0: { michael@0: if (!InferSpewActive(channel)) michael@0: return; michael@0: michael@0: va_list ap; michael@0: va_start(ap, fmt); michael@0: fprintf(stderr, "[infer] "); michael@0: vfprintf(stderr, fmt, ap); michael@0: fprintf(stderr, "\n"); michael@0: va_end(ap); michael@0: } michael@0: michael@0: bool michael@0: types::TypeHasProperty(JSContext *cx, TypeObject *obj, jsid id, const Value &value) michael@0: { michael@0: /* michael@0: * Check the correctness of the type information in the object's property michael@0: * against an actual value. michael@0: */ michael@0: if (!obj->unknownProperties() && !value.isUndefined()) { michael@0: id = IdToTypeId(id); michael@0: michael@0: /* Watch for properties which inference does not monitor. */ michael@0: if (id == id___proto__(cx) || id == id_constructor(cx) || id == id_caller(cx)) michael@0: return true; michael@0: michael@0: Type type = GetValueType(value); michael@0: michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: /* michael@0: * We don't track types for properties inherited from prototypes which michael@0: * haven't yet been accessed during analysis of the inheriting object. michael@0: * Don't do the property instantiation now. michael@0: */ michael@0: TypeSet *types = obj->maybeGetProperty(id); michael@0: if (!types) michael@0: return true; michael@0: michael@0: if (!types->hasType(type)) { michael@0: TypeFailure(cx, "Missing type in object %s %s: %s", michael@0: TypeObjectString(obj), TypeIdString(id), TypeString(type)); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: #endif michael@0: michael@0: void michael@0: types::TypeFailure(JSContext *cx, const char *fmt, ...) michael@0: { michael@0: char msgbuf[1024]; /* Larger error messages will be truncated */ michael@0: char errbuf[1024]; michael@0: michael@0: va_list ap; michael@0: va_start(ap, fmt); michael@0: JS_vsnprintf(errbuf, sizeof(errbuf), fmt, ap); michael@0: va_end(ap); michael@0: michael@0: JS_snprintf(msgbuf, sizeof(msgbuf), "[infer failure] %s", errbuf); michael@0: michael@0: /* Dump type state, even if INFERFLAGS is unset. */ michael@0: cx->compartment()->types.print(cx, true); michael@0: michael@0: MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__); michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // TypeSet michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: TemporaryTypeSet::TemporaryTypeSet(Type type) michael@0: { michael@0: if (type.isUnknown()) { michael@0: flags |= TYPE_FLAG_BASE_MASK; michael@0: } else if (type.isPrimitive()) { michael@0: flags = PrimitiveTypeFlag(type.primitive()); michael@0: if (flags == TYPE_FLAG_DOUBLE) michael@0: flags |= TYPE_FLAG_INT32; michael@0: } else if (type.isAnyObject()) { michael@0: flags |= TYPE_FLAG_ANYOBJECT; michael@0: } else if (type.isTypeObject() && type.typeObject()->unknownProperties()) { michael@0: flags |= TYPE_FLAG_ANYOBJECT; michael@0: } else { michael@0: setBaseObjectCount(1); michael@0: objectSet = reinterpret_cast(type.objectKey()); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: TypeSet::mightBeMIRType(jit::MIRType type) michael@0: { michael@0: if (unknown()) michael@0: return true; michael@0: michael@0: if (type == jit::MIRType_Object) michael@0: return unknownObject() || baseObjectCount() != 0; michael@0: michael@0: switch (type) { michael@0: case jit::MIRType_Undefined: michael@0: return baseFlags() & TYPE_FLAG_UNDEFINED; michael@0: case jit::MIRType_Null: michael@0: return baseFlags() & TYPE_FLAG_NULL; michael@0: case jit::MIRType_Boolean: michael@0: return baseFlags() & TYPE_FLAG_BOOLEAN; michael@0: case jit::MIRType_Int32: michael@0: return baseFlags() & TYPE_FLAG_INT32; michael@0: case jit::MIRType_Float32: // Fall through, there's no JSVAL for Float32. michael@0: case jit::MIRType_Double: michael@0: return baseFlags() & TYPE_FLAG_DOUBLE; michael@0: case jit::MIRType_String: michael@0: return baseFlags() & TYPE_FLAG_STRING; michael@0: case jit::MIRType_MagicOptimizedArguments: michael@0: return baseFlags() & TYPE_FLAG_LAZYARGS; michael@0: case jit::MIRType_MagicHole: michael@0: case jit::MIRType_MagicIsConstructing: michael@0: // These magic constants do not escape to script and are not observed michael@0: // in the type sets. michael@0: // michael@0: // The reason we can return false here is subtle: if Ion is asking the michael@0: // type set if it has seen such a magic constant, then the MIR in michael@0: // question is the most generic type, MIRType_Value. A magic constant michael@0: // could only be emitted by a MIR of MIRType_Value if that MIR is a michael@0: // phi, and we check that different magic constants do not flow to the michael@0: // same join point in GuessPhiType. michael@0: return false; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("Bad MIR type"); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: TypeSet::isSubset(TypeSet *other) michael@0: { michael@0: if ((baseFlags() & other->baseFlags()) != baseFlags()) michael@0: return false; michael@0: michael@0: if (unknownObject()) { michael@0: JS_ASSERT(other->unknownObject()); michael@0: } else { michael@0: for (unsigned i = 0; i < getObjectCount(); i++) { michael@0: TypeObjectKey *obj = getObject(i); michael@0: if (!obj) michael@0: continue; michael@0: if (!other->hasType(Type::ObjectType(obj))) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: TypeSet::enumerateTypes(TypeList *list) michael@0: { michael@0: /* If any type is possible, there's no need to worry about specifics. */ michael@0: if (flags & TYPE_FLAG_UNKNOWN) michael@0: return list->append(Type::UnknownType()); michael@0: michael@0: /* Enqueue type set members stored as bits. */ michael@0: for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) { michael@0: if (flags & flag) { michael@0: Type type = Type::PrimitiveType(TypeFlagPrimitive(flag)); michael@0: if (!list->append(type)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* If any object is possible, skip specifics. */ michael@0: if (flags & TYPE_FLAG_ANYOBJECT) michael@0: return list->append(Type::AnyObjectType()); michael@0: michael@0: /* Enqueue specific object types. */ michael@0: unsigned count = getObjectCount(); michael@0: for (unsigned i = 0; i < count; i++) { michael@0: TypeObjectKey *object = getObject(i); michael@0: if (object) { michael@0: if (!list->append(Type::ObjectType(object))) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: inline bool michael@0: TypeSet::addTypesToConstraint(JSContext *cx, TypeConstraint *constraint) michael@0: { michael@0: /* michael@0: * Build all types in the set into a vector before triggering the michael@0: * constraint, as doing so may modify this type set. michael@0: */ michael@0: TypeList types; michael@0: if (!enumerateTypes(&types)) michael@0: return false; michael@0: michael@0: for (unsigned i = 0; i < types.length(); i++) michael@0: constraint->newType(cx, this, types[i]); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ConstraintTypeSet::addConstraint(JSContext *cx, TypeConstraint *constraint, bool callExisting) michael@0: { michael@0: if (!constraint) { michael@0: /* OOM failure while constructing the constraint. */ michael@0: return false; michael@0: } michael@0: michael@0: JS_ASSERT(cx->compartment()->activeAnalysis); michael@0: michael@0: InferSpew(ISpewOps, "addConstraint: %sT%p%s %sC%p%s %s", michael@0: InferSpewColor(this), this, InferSpewColorReset(), michael@0: InferSpewColor(constraint), constraint, InferSpewColorReset(), michael@0: constraint->kind()); michael@0: michael@0: JS_ASSERT(constraint->next == nullptr); michael@0: constraint->next = constraintList; michael@0: constraintList = constraint; michael@0: michael@0: if (callExisting) michael@0: return addTypesToConstraint(cx, constraint); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: TypeSet::clearObjects() michael@0: { michael@0: setBaseObjectCount(0); michael@0: objectSet = nullptr; michael@0: } michael@0: michael@0: void michael@0: TypeSet::addType(Type type, LifoAlloc *alloc) michael@0: { michael@0: if (unknown()) michael@0: return; michael@0: michael@0: if (type.isUnknown()) { michael@0: flags |= TYPE_FLAG_BASE_MASK; michael@0: clearObjects(); michael@0: JS_ASSERT(unknown()); michael@0: return; michael@0: } michael@0: michael@0: if (type.isPrimitive()) { michael@0: TypeFlags flag = PrimitiveTypeFlag(type.primitive()); michael@0: if (flags & flag) michael@0: return; michael@0: michael@0: /* If we add float to a type set it is also considered to contain int. */ michael@0: if (flag == TYPE_FLAG_DOUBLE) michael@0: flag |= TYPE_FLAG_INT32; michael@0: michael@0: flags |= flag; michael@0: return; michael@0: } michael@0: michael@0: if (flags & TYPE_FLAG_ANYOBJECT) michael@0: return; michael@0: if (type.isAnyObject()) michael@0: goto unknownObject; michael@0: michael@0: { michael@0: uint32_t objectCount = baseObjectCount(); michael@0: TypeObjectKey *object = type.objectKey(); michael@0: TypeObjectKey **pentry = HashSetInsert michael@0: (*alloc, objectSet, objectCount, object); michael@0: if (!pentry) michael@0: goto unknownObject; michael@0: if (*pentry) michael@0: return; michael@0: *pentry = object; michael@0: michael@0: setBaseObjectCount(objectCount); michael@0: michael@0: if (objectCount == TYPE_FLAG_OBJECT_COUNT_LIMIT) michael@0: goto unknownObject; michael@0: } michael@0: michael@0: if (type.isTypeObject()) { michael@0: TypeObject *nobject = type.typeObject(); michael@0: JS_ASSERT(!nobject->singleton()); michael@0: if (nobject->unknownProperties()) michael@0: goto unknownObject; michael@0: } michael@0: michael@0: if (false) { michael@0: unknownObject: michael@0: flags |= TYPE_FLAG_ANYOBJECT; michael@0: clearObjects(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ConstraintTypeSet::addType(ExclusiveContext *cxArg, Type type) michael@0: { michael@0: JS_ASSERT(cxArg->compartment()->activeAnalysis); michael@0: michael@0: if (hasType(type)) michael@0: return; michael@0: michael@0: TypeSet::addType(type, &cxArg->typeLifoAlloc()); michael@0: michael@0: if (type.isObjectUnchecked() && unknownObject()) michael@0: type = Type::AnyObjectType(); michael@0: michael@0: InferSpew(ISpewOps, "addType: %sT%p%s %s", michael@0: InferSpewColor(this), this, InferSpewColorReset(), michael@0: TypeString(type)); michael@0: michael@0: /* Propagate the type to all constraints. */ michael@0: if (JSContext *cx = cxArg->maybeJSContext()) { michael@0: TypeConstraint *constraint = constraintList; michael@0: while (constraint) { michael@0: constraint->newType(cx, this, type); michael@0: constraint = constraint->next; michael@0: } michael@0: } else { michael@0: JS_ASSERT(!constraintList); michael@0: } michael@0: } michael@0: michael@0: void michael@0: TypeSet::print() michael@0: { michael@0: if (flags & TYPE_FLAG_NON_DATA_PROPERTY) michael@0: fprintf(stderr, " [non-data]"); michael@0: michael@0: if (flags & TYPE_FLAG_NON_WRITABLE_PROPERTY) michael@0: fprintf(stderr, " [non-writable]"); michael@0: michael@0: if (definiteProperty()) michael@0: fprintf(stderr, " [definite:%d]", definiteSlot()); michael@0: michael@0: if (baseFlags() == 0 && !baseObjectCount()) { michael@0: fprintf(stderr, " missing"); michael@0: return; michael@0: } michael@0: michael@0: if (flags & TYPE_FLAG_UNKNOWN) michael@0: fprintf(stderr, " unknown"); michael@0: if (flags & TYPE_FLAG_ANYOBJECT) michael@0: fprintf(stderr, " object"); michael@0: michael@0: if (flags & TYPE_FLAG_UNDEFINED) michael@0: fprintf(stderr, " void"); michael@0: if (flags & TYPE_FLAG_NULL) michael@0: fprintf(stderr, " null"); michael@0: if (flags & TYPE_FLAG_BOOLEAN) michael@0: fprintf(stderr, " bool"); michael@0: if (flags & TYPE_FLAG_INT32) michael@0: fprintf(stderr, " int"); michael@0: if (flags & TYPE_FLAG_DOUBLE) michael@0: fprintf(stderr, " float"); michael@0: if (flags & TYPE_FLAG_STRING) michael@0: fprintf(stderr, " string"); michael@0: if (flags & TYPE_FLAG_LAZYARGS) michael@0: fprintf(stderr, " lazyargs"); michael@0: michael@0: uint32_t objectCount = baseObjectCount(); michael@0: if (objectCount) { michael@0: fprintf(stderr, " object[%u]", objectCount); michael@0: michael@0: unsigned count = getObjectCount(); michael@0: for (unsigned i = 0; i < count; i++) { michael@0: TypeObjectKey *object = getObject(i); michael@0: if (object) michael@0: fprintf(stderr, " %s", TypeString(Type::ObjectType(object))); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: TypeSet::clone(LifoAlloc *alloc, TemporaryTypeSet *result) const michael@0: { michael@0: JS_ASSERT(result->empty()); michael@0: michael@0: unsigned objectCount = baseObjectCount(); michael@0: unsigned capacity = (objectCount >= 2) ? HashSetCapacity(objectCount) : 0; michael@0: michael@0: TypeObjectKey **newSet; michael@0: if (capacity) { michael@0: newSet = alloc->newArray(capacity); michael@0: if (!newSet) michael@0: return false; michael@0: PodCopy(newSet, objectSet, capacity); michael@0: } michael@0: michael@0: new(result) TemporaryTypeSet(flags, capacity ? newSet : objectSet); michael@0: return true; michael@0: } michael@0: michael@0: TemporaryTypeSet * michael@0: TypeSet::clone(LifoAlloc *alloc) const michael@0: { michael@0: TemporaryTypeSet *res = alloc->new_(); michael@0: if (!res || !clone(alloc, res)) michael@0: return nullptr; michael@0: return res; michael@0: } michael@0: michael@0: TemporaryTypeSet * michael@0: TypeSet::filter(LifoAlloc *alloc, bool filterUndefined, bool filterNull) const michael@0: { michael@0: TemporaryTypeSet *res = clone(alloc); michael@0: if (!res) michael@0: return nullptr; michael@0: michael@0: if (filterUndefined) michael@0: res->flags = res->flags & ~TYPE_FLAG_UNDEFINED; michael@0: michael@0: if (filterNull) michael@0: res->flags = res->flags & ~TYPE_FLAG_NULL; michael@0: michael@0: return res; michael@0: } michael@0: michael@0: /* static */ TemporaryTypeSet * michael@0: TypeSet::unionSets(TypeSet *a, TypeSet *b, LifoAlloc *alloc) michael@0: { michael@0: TemporaryTypeSet *res = alloc->new_(a->baseFlags() | b->baseFlags(), michael@0: static_cast(nullptr)); michael@0: if (!res) michael@0: return nullptr; michael@0: michael@0: if (!res->unknownObject()) { michael@0: for (size_t i = 0; i < a->getObjectCount() && !res->unknownObject(); i++) { michael@0: if (TypeObjectKey *key = a->getObject(i)) michael@0: res->addType(Type::ObjectType(key), alloc); michael@0: } michael@0: for (size_t i = 0; i < b->getObjectCount() && !res->unknownObject(); i++) { michael@0: if (TypeObjectKey *key = b->getObject(i)) michael@0: res->addType(Type::ObjectType(key), alloc); michael@0: } michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // Compiler constraints michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: // Compiler constraints overview michael@0: // michael@0: // Constraints generated during Ion compilation capture assumptions made about michael@0: // heap properties that will trigger invalidation of the resulting Ion code if michael@0: // the constraint is violated. Constraints can only be attached to type sets on michael@0: // the main thread, so to allow compilation to occur almost entirely off thread michael@0: // the generation is split into two phases. michael@0: // michael@0: // During compilation, CompilerConstraint values are constructed in a list, michael@0: // recording the heap property type set which was read from and its expected michael@0: // contents, along with the assumption made about those contents. michael@0: // michael@0: // At the end of compilation, when linking the result on the main thread, the michael@0: // list of compiler constraints are read and converted to type constraints and michael@0: // attached to the type sets. If the property type sets have changed so that the michael@0: // assumptions no longer hold then the compilation is aborted and its result michael@0: // discarded. michael@0: michael@0: // Superclass of all constraints generated during Ion compilation. These may michael@0: // be allocated off the main thread, using the current Ion context's allocator. michael@0: class CompilerConstraint michael@0: { michael@0: public: michael@0: // Property being queried by the compiler. michael@0: HeapTypeSetKey property; michael@0: michael@0: // Contents of the property at the point when the query was performed. This michael@0: // may differ from the actual property types later in compilation as the michael@0: // main thread performs side effects. michael@0: TemporaryTypeSet *expected; michael@0: michael@0: CompilerConstraint(LifoAlloc *alloc, const HeapTypeSetKey &property) michael@0: : property(property), michael@0: expected(property.maybeTypes() ? property.maybeTypes()->clone(alloc) : nullptr) michael@0: {} michael@0: michael@0: // Generate the type constraint recording the assumption made by this michael@0: // compilation. Returns true if the assumption originally made still holds. michael@0: virtual bool generateTypeConstraint(JSContext *cx, RecompileInfo recompileInfo) = 0; michael@0: }; michael@0: michael@0: class types::CompilerConstraintList michael@0: { michael@0: public: michael@0: struct FrozenScript michael@0: { michael@0: JSScript *script; michael@0: TemporaryTypeSet *thisTypes; michael@0: TemporaryTypeSet *argTypes; michael@0: TemporaryTypeSet *bytecodeTypes; michael@0: }; michael@0: michael@0: private: michael@0: michael@0: // OOM during generation of some constraint. michael@0: bool failed_; michael@0: michael@0: #ifdef JS_ION michael@0: // Allocator used for constraints. michael@0: LifoAlloc *alloc_; michael@0: michael@0: // Constraints generated on heap properties. michael@0: Vector constraints; michael@0: michael@0: // Scripts whose stack type sets were frozen for the compilation. michael@0: Vector frozenScripts; michael@0: #endif michael@0: michael@0: public: michael@0: CompilerConstraintList(jit::TempAllocator &alloc) michael@0: : failed_(false) michael@0: #ifdef JS_ION michael@0: , alloc_(alloc.lifoAlloc()) michael@0: , constraints(alloc) michael@0: , frozenScripts(alloc) michael@0: #endif michael@0: {} michael@0: michael@0: void add(CompilerConstraint *constraint) { michael@0: #ifdef JS_ION michael@0: if (!constraint || !constraints.append(constraint)) michael@0: setFailed(); michael@0: #else michael@0: MOZ_CRASH(); michael@0: #endif michael@0: } michael@0: michael@0: void freezeScript(JSScript *script, michael@0: TemporaryTypeSet *thisTypes, michael@0: TemporaryTypeSet *argTypes, michael@0: TemporaryTypeSet *bytecodeTypes) michael@0: { michael@0: #ifdef JS_ION michael@0: FrozenScript entry; michael@0: entry.script = script; michael@0: entry.thisTypes = thisTypes; michael@0: entry.argTypes = argTypes; michael@0: entry.bytecodeTypes = bytecodeTypes; michael@0: if (!frozenScripts.append(entry)) michael@0: setFailed(); michael@0: #else michael@0: MOZ_CRASH(); michael@0: #endif michael@0: } michael@0: michael@0: size_t length() { michael@0: #ifdef JS_ION michael@0: return constraints.length(); michael@0: #else michael@0: MOZ_CRASH(); michael@0: #endif michael@0: } michael@0: michael@0: CompilerConstraint *get(size_t i) { michael@0: #ifdef JS_ION michael@0: return constraints[i]; michael@0: #else michael@0: MOZ_CRASH(); michael@0: #endif michael@0: } michael@0: michael@0: size_t numFrozenScripts() { michael@0: #ifdef JS_ION michael@0: return frozenScripts.length(); michael@0: #else michael@0: MOZ_CRASH(); michael@0: #endif michael@0: } michael@0: michael@0: const FrozenScript &frozenScript(size_t i) { michael@0: #ifdef JS_ION michael@0: return frozenScripts[i]; michael@0: #else michael@0: MOZ_CRASH(); michael@0: #endif michael@0: } michael@0: michael@0: bool failed() { michael@0: return failed_; michael@0: } michael@0: void setFailed() { michael@0: failed_ = true; michael@0: } michael@0: LifoAlloc *alloc() const { michael@0: #ifdef JS_ION michael@0: return alloc_; michael@0: #else michael@0: MOZ_CRASH(); michael@0: #endif michael@0: } michael@0: }; michael@0: michael@0: CompilerConstraintList * michael@0: types::NewCompilerConstraintList(jit::TempAllocator &alloc) michael@0: { michael@0: #ifdef JS_ION michael@0: return alloc.lifoAlloc()->new_(alloc); michael@0: #else michael@0: MOZ_CRASH(); michael@0: #endif michael@0: } michael@0: michael@0: /* static */ bool michael@0: TypeScript::FreezeTypeSets(CompilerConstraintList *constraints, JSScript *script, michael@0: TemporaryTypeSet **pThisTypes, michael@0: TemporaryTypeSet **pArgTypes, michael@0: TemporaryTypeSet **pBytecodeTypes) michael@0: { michael@0: LifoAlloc *alloc = constraints->alloc(); michael@0: StackTypeSet *existing = script->types->typeArray(); michael@0: michael@0: size_t count = NumTypeSets(script); michael@0: TemporaryTypeSet *types = alloc->newArrayUninitialized(count); michael@0: if (!types) michael@0: return false; michael@0: PodZero(types, count); michael@0: michael@0: for (size_t i = 0; i < count; i++) { michael@0: if (!existing[i].clone(alloc, &types[i])) michael@0: return false; michael@0: } michael@0: michael@0: *pThisTypes = types + (ThisTypes(script) - existing); michael@0: *pArgTypes = (script->functionNonDelazifying() && script->functionNonDelazifying()->nargs()) michael@0: ? (types + (ArgTypes(script, 0) - existing)) michael@0: : nullptr; michael@0: *pBytecodeTypes = types; michael@0: michael@0: constraints->freezeScript(script, *pThisTypes, *pArgTypes, *pBytecodeTypes); michael@0: return true; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: template michael@0: class CompilerConstraintInstance : public CompilerConstraint michael@0: { michael@0: T data; michael@0: michael@0: public: michael@0: CompilerConstraintInstance(LifoAlloc *alloc, const HeapTypeSetKey &property, const T &data) michael@0: : CompilerConstraint(alloc, property), data(data) michael@0: {} michael@0: michael@0: bool generateTypeConstraint(JSContext *cx, RecompileInfo recompileInfo); michael@0: }; michael@0: michael@0: // Constraint generated from a CompilerConstraint when linking the compilation. michael@0: template michael@0: class TypeCompilerConstraint : public TypeConstraint michael@0: { michael@0: // Compilation which this constraint may invalidate. michael@0: RecompileInfo compilation; michael@0: michael@0: T data; michael@0: michael@0: public: michael@0: TypeCompilerConstraint(RecompileInfo compilation, const T &data) michael@0: : compilation(compilation), data(data) michael@0: {} michael@0: michael@0: const char *kind() { return data.kind(); } michael@0: michael@0: void newType(JSContext *cx, TypeSet *source, Type type) { michael@0: if (data.invalidateOnNewType(type)) michael@0: cx->zone()->types.addPendingRecompile(cx, compilation); michael@0: } michael@0: michael@0: void newPropertyState(JSContext *cx, TypeSet *source) { michael@0: if (data.invalidateOnNewPropertyState(source)) michael@0: cx->zone()->types.addPendingRecompile(cx, compilation); michael@0: } michael@0: michael@0: void newObjectState(JSContext *cx, TypeObject *object) { michael@0: // Note: Once the object has unknown properties, no more notifications michael@0: // will be sent on changes to its state, so always invalidate any michael@0: // associated compilations. michael@0: if (object->unknownProperties() || data.invalidateOnNewObjectState(object)) michael@0: cx->zone()->types.addPendingRecompile(cx, compilation); michael@0: } michael@0: michael@0: bool sweep(TypeZone &zone, TypeConstraint **res) { michael@0: if (data.shouldSweep() || compilation.shouldSweep(zone)) michael@0: return false; michael@0: *res = zone.typeLifoAlloc.new_ >(compilation, data); michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: template michael@0: bool michael@0: CompilerConstraintInstance::generateTypeConstraint(JSContext *cx, RecompileInfo recompileInfo) michael@0: { michael@0: if (property.object()->unknownProperties()) michael@0: return false; michael@0: michael@0: if (!property.instantiate(cx)) michael@0: return false; michael@0: michael@0: if (!data.constraintHolds(cx, property, expected)) michael@0: return false; michael@0: michael@0: return property.maybeTypes()->addConstraint(cx, cx->typeLifoAlloc().new_ >(recompileInfo, data), michael@0: /* callExisting = */ false); michael@0: } michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: const Class * michael@0: TypeObjectKey::clasp() michael@0: { michael@0: return isTypeObject() ? asTypeObject()->clasp() : asSingleObject()->getClass(); michael@0: } michael@0: michael@0: TaggedProto michael@0: TypeObjectKey::proto() michael@0: { michael@0: JS_ASSERT(hasTenuredProto()); michael@0: return isTypeObject() ? asTypeObject()->proto() : asSingleObject()->getTaggedProto(); michael@0: } michael@0: michael@0: bool michael@0: ObjectImpl::hasTenuredProto() const michael@0: { michael@0: return type_->hasTenuredProto(); michael@0: } michael@0: michael@0: bool michael@0: TypeObjectKey::hasTenuredProto() michael@0: { michael@0: return isTypeObject() ? asTypeObject()->hasTenuredProto() : asSingleObject()->hasTenuredProto(); michael@0: } michael@0: michael@0: JSObject * michael@0: TypeObjectKey::singleton() michael@0: { michael@0: return isTypeObject() ? asTypeObject()->singleton() : asSingleObject(); michael@0: } michael@0: michael@0: TypeNewScript * michael@0: TypeObjectKey::newScript() michael@0: { michael@0: if (isTypeObject() && asTypeObject()->hasNewScript()) michael@0: return asTypeObject()->newScript(); michael@0: return nullptr; michael@0: } michael@0: michael@0: TypeObject * michael@0: TypeObjectKey::maybeType() michael@0: { michael@0: if (isTypeObject()) michael@0: return asTypeObject(); michael@0: if (asSingleObject()->hasLazyType()) michael@0: return nullptr; michael@0: return asSingleObject()->type(); michael@0: } michael@0: michael@0: bool michael@0: TypeObjectKey::unknownProperties() michael@0: { michael@0: if (TypeObject *type = maybeType()) michael@0: return type->unknownProperties(); michael@0: return false; michael@0: } michael@0: michael@0: HeapTypeSetKey michael@0: TypeObjectKey::property(jsid id) michael@0: { michael@0: JS_ASSERT(!unknownProperties()); michael@0: michael@0: HeapTypeSetKey property; michael@0: property.object_ = this; michael@0: property.id_ = id; michael@0: if (TypeObject *type = maybeType()) michael@0: property.maybeTypes_ = type->maybeGetProperty(id); michael@0: michael@0: return property; michael@0: } michael@0: michael@0: void michael@0: TypeObjectKey::ensureTrackedProperty(JSContext *cx, jsid id) michael@0: { michael@0: #ifdef JS_ION michael@0: // If we are accessing a lazily defined property which actually exists in michael@0: // the VM and has not been instantiated yet, instantiate it now if we are michael@0: // on the main thread and able to do so. michael@0: if (!JSID_IS_VOID(id) && !JSID_IS_EMPTY(id)) { michael@0: JS_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); michael@0: if (JSObject *obj = singleton()) { michael@0: if (obj->isNative() && obj->nativeLookupPure(id)) michael@0: EnsureTrackPropertyTypes(cx, obj, id); michael@0: } michael@0: } michael@0: #endif // JS_ION michael@0: } michael@0: michael@0: bool michael@0: HeapTypeSetKey::instantiate(JSContext *cx) michael@0: { michael@0: if (maybeTypes()) michael@0: return true; michael@0: if (object()->isSingleObject() && !object()->asSingleObject()->getType(cx)) { michael@0: cx->clearPendingException(); michael@0: return false; michael@0: } michael@0: maybeTypes_ = object()->maybeType()->getProperty(cx, id()); michael@0: return maybeTypes_ != nullptr; michael@0: } michael@0: michael@0: static bool michael@0: CheckFrozenTypeSet(JSContext *cx, TemporaryTypeSet *frozen, StackTypeSet *actual) michael@0: { michael@0: // Return whether the types frozen for a script during compilation are michael@0: // still valid. Also check for any new types added to the frozen set during michael@0: // compilation, and add them to the actual stack type sets. These new types michael@0: // indicate places where the compiler relaxed its possible inputs to be michael@0: // more tolerant of potential new types. michael@0: michael@0: if (!actual->isSubset(frozen)) michael@0: return false; michael@0: michael@0: if (!frozen->isSubset(actual)) { michael@0: TypeSet::TypeList list; michael@0: frozen->enumerateTypes(&list); michael@0: michael@0: for (size_t i = 0; i < list.length(); i++) michael@0: actual->addType(cx, list[i]); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: /* michael@0: * As for TypeConstraintFreeze, but describes an implicit freeze constraint michael@0: * added for stack types within a script. Applies to all compilations of the michael@0: * script, not just a single one. michael@0: */ michael@0: class TypeConstraintFreezeStack : public TypeConstraint michael@0: { michael@0: JSScript *script_; michael@0: michael@0: public: michael@0: TypeConstraintFreezeStack(JSScript *script) michael@0: : script_(script) michael@0: {} michael@0: michael@0: const char *kind() { return "freezeStack"; } michael@0: michael@0: void newType(JSContext *cx, TypeSet *source, Type type) { michael@0: /* michael@0: * Unlike TypeConstraintFreeze, triggering this constraint once does michael@0: * not disable it on future changes to the type set. michael@0: */ michael@0: cx->zone()->types.addPendingRecompile(cx, script_); michael@0: } michael@0: michael@0: bool sweep(TypeZone &zone, TypeConstraint **res) { michael@0: if (IsScriptAboutToBeFinalized(&script_)) michael@0: return false; michael@0: *res = zone.typeLifoAlloc.new_(script_); michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: bool michael@0: types::FinishCompilation(JSContext *cx, HandleScript script, ExecutionMode executionMode, michael@0: CompilerConstraintList *constraints, RecompileInfo *precompileInfo) michael@0: { michael@0: if (constraints->failed()) michael@0: return false; michael@0: michael@0: CompilerOutput co(script, executionMode); michael@0: michael@0: TypeZone &types = cx->zone()->types; michael@0: if (!types.compilerOutputs) { michael@0: types.compilerOutputs = cx->new_< Vector >(cx); michael@0: if (!types.compilerOutputs) michael@0: return false; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: for (size_t i = 0; i < types.compilerOutputs->length(); i++) { michael@0: const CompilerOutput &co = (*types.compilerOutputs)[i]; michael@0: JS_ASSERT_IF(co.isValid(), co.script() != script || co.mode() != executionMode); michael@0: } michael@0: #endif michael@0: michael@0: uint32_t index = types.compilerOutputs->length(); michael@0: if (!types.compilerOutputs->append(co)) michael@0: return false; michael@0: michael@0: *precompileInfo = RecompileInfo(index); michael@0: michael@0: bool succeeded = true; michael@0: michael@0: for (size_t i = 0; i < constraints->length(); i++) { michael@0: CompilerConstraint *constraint = constraints->get(i); michael@0: if (!constraint->generateTypeConstraint(cx, *precompileInfo)) michael@0: succeeded = false; michael@0: } michael@0: michael@0: for (size_t i = 0; i < constraints->numFrozenScripts(); i++) { michael@0: const CompilerConstraintList::FrozenScript &entry = constraints->frozenScript(i); michael@0: JS_ASSERT(entry.script->types); michael@0: michael@0: if (!CheckFrozenTypeSet(cx, entry.thisTypes, types::TypeScript::ThisTypes(entry.script))) michael@0: succeeded = false; michael@0: unsigned nargs = entry.script->functionNonDelazifying() michael@0: ? entry.script->functionNonDelazifying()->nargs() michael@0: : 0; michael@0: for (size_t i = 0; i < nargs; i++) { michael@0: if (!CheckFrozenTypeSet(cx, &entry.argTypes[i], types::TypeScript::ArgTypes(entry.script, i))) michael@0: succeeded = false; michael@0: } michael@0: for (size_t i = 0; i < entry.script->nTypeSets(); i++) { michael@0: if (!CheckFrozenTypeSet(cx, &entry.bytecodeTypes[i], &entry.script->types->typeArray()[i])) michael@0: succeeded = false; michael@0: } michael@0: michael@0: // If necessary, add constraints to trigger invalidation on the script michael@0: // after any future changes to the stack type sets. michael@0: if (entry.script->hasFreezeConstraints()) michael@0: continue; michael@0: entry.script->setHasFreezeConstraints(); michael@0: michael@0: size_t count = TypeScript::NumTypeSets(entry.script); michael@0: michael@0: StackTypeSet *array = entry.script->types->typeArray(); michael@0: for (size_t i = 0; i < count; i++) { michael@0: if (!array[i].addConstraint(cx, cx->typeLifoAlloc().new_(entry.script), false)) michael@0: succeeded = false; michael@0: } michael@0: } michael@0: michael@0: if (!succeeded || types.compilerOutputs->back().pendingInvalidation()) { michael@0: types.compilerOutputs->back().invalidate(); michael@0: script->resetUseCount(); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: CheckDefinitePropertiesTypeSet(JSContext *cx, TemporaryTypeSet *frozen, StackTypeSet *actual) michael@0: { michael@0: // The definite properties analysis happens on the main thread, so no new michael@0: // types can have been added to actual. The analysis may have updated the michael@0: // contents of |frozen| though with new speculative types, and these need michael@0: // to be reflected in |actual| for AddClearDefiniteFunctionUsesInScript michael@0: // to work. michael@0: if (!frozen->isSubset(actual)) { michael@0: TypeSet::TypeList list; michael@0: frozen->enumerateTypes(&list); michael@0: michael@0: for (size_t i = 0; i < list.length(); i++) michael@0: actual->addType(cx, list[i]); michael@0: } michael@0: } michael@0: michael@0: void michael@0: types::FinishDefinitePropertiesAnalysis(JSContext *cx, CompilerConstraintList *constraints) michael@0: { michael@0: #ifdef DEBUG michael@0: // Assert no new types have been added to the StackTypeSets. Do this before michael@0: // calling CheckDefinitePropertiesTypeSet, as it may add new types to the michael@0: // StackTypeSets and break these invariants if a script is inlined more michael@0: // than once. See also CheckDefinitePropertiesTypeSet. michael@0: for (size_t i = 0; i < constraints->numFrozenScripts(); i++) { michael@0: const CompilerConstraintList::FrozenScript &entry = constraints->frozenScript(i); michael@0: JSScript *script = entry.script; michael@0: JS_ASSERT(script->types); michael@0: michael@0: JS_ASSERT(TypeScript::ThisTypes(script)->isSubset(entry.thisTypes)); michael@0: michael@0: unsigned nargs = entry.script->functionNonDelazifying() michael@0: ? entry.script->functionNonDelazifying()->nargs() michael@0: : 0; michael@0: for (size_t j = 0; j < nargs; j++) michael@0: JS_ASSERT(TypeScript::ArgTypes(script, j)->isSubset(&entry.argTypes[j])); michael@0: michael@0: for (size_t j = 0; j < script->nTypeSets(); j++) michael@0: JS_ASSERT(script->types->typeArray()[j].isSubset(&entry.bytecodeTypes[j])); michael@0: } michael@0: #endif michael@0: michael@0: for (size_t i = 0; i < constraints->numFrozenScripts(); i++) { michael@0: const CompilerConstraintList::FrozenScript &entry = constraints->frozenScript(i); michael@0: JSScript *script = entry.script; michael@0: JS_ASSERT(script->types); michael@0: michael@0: if (!script->types) michael@0: MOZ_CRASH(); michael@0: michael@0: CheckDefinitePropertiesTypeSet(cx, entry.thisTypes, TypeScript::ThisTypes(script)); michael@0: michael@0: unsigned nargs = script->functionNonDelazifying() michael@0: ? script->functionNonDelazifying()->nargs() michael@0: : 0; michael@0: for (size_t j = 0; j < nargs; j++) michael@0: CheckDefinitePropertiesTypeSet(cx, &entry.argTypes[j], TypeScript::ArgTypes(script, j)); michael@0: michael@0: for (size_t j = 0; j < script->nTypeSets(); j++) michael@0: CheckDefinitePropertiesTypeSet(cx, &entry.bytecodeTypes[j], &script->types->typeArray()[j]); michael@0: } michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: // Constraint which triggers recompilation of a script if any type is added to a type set. */ michael@0: class ConstraintDataFreeze michael@0: { michael@0: public: michael@0: ConstraintDataFreeze() {} michael@0: michael@0: const char *kind() { return "freeze"; } michael@0: michael@0: bool invalidateOnNewType(Type type) { return true; } michael@0: bool invalidateOnNewPropertyState(TypeSet *property) { return true; } michael@0: bool invalidateOnNewObjectState(TypeObject *object) { return false; } michael@0: michael@0: bool constraintHolds(JSContext *cx, michael@0: const HeapTypeSetKey &property, TemporaryTypeSet *expected) michael@0: { michael@0: return expected michael@0: ? property.maybeTypes()->isSubset(expected) michael@0: : property.maybeTypes()->empty(); michael@0: } michael@0: michael@0: bool shouldSweep() { return false; } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: void michael@0: HeapTypeSetKey::freeze(CompilerConstraintList *constraints) michael@0: { michael@0: LifoAlloc *alloc = constraints->alloc(); michael@0: michael@0: typedef CompilerConstraintInstance T; michael@0: constraints->add(alloc->new_(alloc, *this, ConstraintDataFreeze())); michael@0: } michael@0: michael@0: static inline jit::MIRType michael@0: GetMIRTypeFromTypeFlags(TypeFlags flags) michael@0: { michael@0: switch (flags) { michael@0: case TYPE_FLAG_UNDEFINED: michael@0: return jit::MIRType_Undefined; michael@0: case TYPE_FLAG_NULL: michael@0: return jit::MIRType_Null; michael@0: case TYPE_FLAG_BOOLEAN: michael@0: return jit::MIRType_Boolean; michael@0: case TYPE_FLAG_INT32: michael@0: return jit::MIRType_Int32; michael@0: case (TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE): michael@0: return jit::MIRType_Double; michael@0: case TYPE_FLAG_STRING: michael@0: return jit::MIRType_String; michael@0: case TYPE_FLAG_LAZYARGS: michael@0: return jit::MIRType_MagicOptimizedArguments; michael@0: case TYPE_FLAG_ANYOBJECT: michael@0: return jit::MIRType_Object; michael@0: default: michael@0: return jit::MIRType_Value; michael@0: } michael@0: } michael@0: michael@0: jit::MIRType michael@0: TemporaryTypeSet::getKnownMIRType() michael@0: { michael@0: TypeFlags flags = baseFlags(); michael@0: jit::MIRType type; michael@0: michael@0: if (baseObjectCount()) michael@0: type = flags ? jit::MIRType_Value : jit::MIRType_Object; michael@0: else michael@0: type = GetMIRTypeFromTypeFlags(flags); michael@0: michael@0: /* michael@0: * If the type set is totally empty then it will be treated as unknown, michael@0: * but we still need to record the dependency as adding a new type can give michael@0: * it a definite type tag. This is not needed if there are enough types michael@0: * that the exact tag is unknown, as it will stay unknown as more types are michael@0: * added to the set. michael@0: */ michael@0: DebugOnly empty = flags == 0 && baseObjectCount() == 0; michael@0: JS_ASSERT_IF(empty, type == jit::MIRType_Value); michael@0: michael@0: return type; michael@0: } michael@0: michael@0: jit::MIRType michael@0: HeapTypeSetKey::knownMIRType(CompilerConstraintList *constraints) michael@0: { michael@0: TypeSet *types = maybeTypes(); michael@0: michael@0: if (!types || types->unknown()) michael@0: return jit::MIRType_Value; michael@0: michael@0: TypeFlags flags = types->baseFlags() & ~TYPE_FLAG_ANYOBJECT; michael@0: jit::MIRType type; michael@0: michael@0: if (types->unknownObject() || types->getObjectCount()) michael@0: type = flags ? jit::MIRType_Value : jit::MIRType_Object; michael@0: else michael@0: type = GetMIRTypeFromTypeFlags(flags); michael@0: michael@0: if (type != jit::MIRType_Value) michael@0: freeze(constraints); michael@0: michael@0: /* michael@0: * If the type set is totally empty then it will be treated as unknown, michael@0: * but we still need to record the dependency as adding a new type can give michael@0: * it a definite type tag. This is not needed if there are enough types michael@0: * that the exact tag is unknown, as it will stay unknown as more types are michael@0: * added to the set. michael@0: */ michael@0: JS_ASSERT_IF(types->empty(), type == jit::MIRType_Value); michael@0: michael@0: return type; michael@0: } michael@0: michael@0: bool michael@0: HeapTypeSetKey::isOwnProperty(CompilerConstraintList *constraints) michael@0: { michael@0: if (maybeTypes() && (!maybeTypes()->empty() || maybeTypes()->nonDataProperty())) michael@0: return true; michael@0: if (JSObject *obj = object()->singleton()) { michael@0: if (CanHaveEmptyPropertyTypesForOwnProperty(obj)) michael@0: return true; michael@0: } michael@0: freeze(constraints); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: HeapTypeSetKey::knownSubset(CompilerConstraintList *constraints, const HeapTypeSetKey &other) michael@0: { michael@0: if (!maybeTypes() || maybeTypes()->empty()) { michael@0: freeze(constraints); michael@0: return true; michael@0: } michael@0: if (!other.maybeTypes() || !maybeTypes()->isSubset(other.maybeTypes())) michael@0: return false; michael@0: freeze(constraints); michael@0: return true; michael@0: } michael@0: michael@0: JSObject * michael@0: TemporaryTypeSet::getSingleton() michael@0: { michael@0: if (baseFlags() != 0 || baseObjectCount() != 1) michael@0: return nullptr; michael@0: michael@0: return getSingleObject(0); michael@0: } michael@0: michael@0: JSObject * michael@0: HeapTypeSetKey::singleton(CompilerConstraintList *constraints) michael@0: { michael@0: HeapTypeSet *types = maybeTypes(); michael@0: michael@0: if (!types || types->nonDataProperty() || types->baseFlags() != 0 || types->getObjectCount() != 1) michael@0: return nullptr; michael@0: michael@0: JSObject *obj = types->getSingleObject(0); michael@0: michael@0: if (obj) michael@0: freeze(constraints); michael@0: michael@0: return obj; michael@0: } michael@0: michael@0: bool michael@0: HeapTypeSetKey::needsBarrier(CompilerConstraintList *constraints) michael@0: { michael@0: TypeSet *types = maybeTypes(); michael@0: if (!types) michael@0: return false; michael@0: bool result = types->unknownObject() michael@0: || types->getObjectCount() > 0 michael@0: || types->hasAnyFlag(TYPE_FLAG_STRING); michael@0: if (!result) michael@0: freeze(constraints); michael@0: return result; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: // Constraint which triggers recompilation if an object acquires particular flags. michael@0: class ConstraintDataFreezeObjectFlags michael@0: { michael@0: public: michael@0: // Flags we are watching for on this object. michael@0: TypeObjectFlags flags; michael@0: michael@0: ConstraintDataFreezeObjectFlags(TypeObjectFlags flags) michael@0: : flags(flags) michael@0: { michael@0: JS_ASSERT(flags); michael@0: } michael@0: michael@0: const char *kind() { return "freezeObjectFlags"; } michael@0: michael@0: bool invalidateOnNewType(Type type) { return false; } michael@0: bool invalidateOnNewPropertyState(TypeSet *property) { return false; } michael@0: bool invalidateOnNewObjectState(TypeObject *object) { michael@0: return object->hasAnyFlags(flags); michael@0: } michael@0: michael@0: bool constraintHolds(JSContext *cx, michael@0: const HeapTypeSetKey &property, TemporaryTypeSet *expected) michael@0: { michael@0: return !invalidateOnNewObjectState(property.object()->maybeType()); michael@0: } michael@0: michael@0: bool shouldSweep() { return false; } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: bool michael@0: TypeObjectKey::hasFlags(CompilerConstraintList *constraints, TypeObjectFlags flags) michael@0: { michael@0: JS_ASSERT(flags); michael@0: michael@0: if (TypeObject *type = maybeType()) { michael@0: if (type->hasAnyFlags(flags)) michael@0: return true; michael@0: } michael@0: michael@0: HeapTypeSetKey objectProperty = property(JSID_EMPTY); michael@0: LifoAlloc *alloc = constraints->alloc(); michael@0: michael@0: typedef CompilerConstraintInstance T; michael@0: constraints->add(alloc->new_(alloc, objectProperty, ConstraintDataFreezeObjectFlags(flags))); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: TemporaryTypeSet::hasObjectFlags(CompilerConstraintList *constraints, TypeObjectFlags flags) michael@0: { michael@0: if (unknownObject()) michael@0: return true; michael@0: michael@0: /* michael@0: * Treat type sets containing no objects as having all object flags, michael@0: * to spare callers from having to check this. michael@0: */ michael@0: if (baseObjectCount() == 0) michael@0: return true; michael@0: michael@0: unsigned count = getObjectCount(); michael@0: for (unsigned i = 0; i < count; i++) { michael@0: TypeObjectKey *object = getObject(i); michael@0: if (object && object->hasFlags(constraints, flags)) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: gc::InitialHeap michael@0: TypeObject::initialHeap(CompilerConstraintList *constraints) michael@0: { michael@0: // If this object is not required to be pretenured but could be in the michael@0: // future, add a constraint to trigger recompilation if the requirement michael@0: // changes. michael@0: michael@0: if (shouldPreTenure()) michael@0: return gc::TenuredHeap; michael@0: michael@0: if (!canPreTenure()) michael@0: return gc::DefaultHeap; michael@0: michael@0: HeapTypeSetKey objectProperty = TypeObjectKey::get(this)->property(JSID_EMPTY); michael@0: LifoAlloc *alloc = constraints->alloc(); michael@0: michael@0: typedef CompilerConstraintInstance T; michael@0: constraints->add(alloc->new_(alloc, objectProperty, ConstraintDataFreezeObjectFlags(OBJECT_FLAG_PRE_TENURE))); michael@0: michael@0: return gc::DefaultHeap; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: // Constraint which triggers recompilation on any type change in an inlined michael@0: // script. The freeze constraints added to stack type sets will only directly michael@0: // invalidate the script containing those stack type sets. To invalidate code michael@0: // for scripts into which the base script was inlined, ObjectStateChange is used. michael@0: class ConstraintDataFreezeObjectForInlinedCall michael@0: { michael@0: public: michael@0: ConstraintDataFreezeObjectForInlinedCall() michael@0: {} michael@0: michael@0: const char *kind() { return "freezeObjectForInlinedCall"; } michael@0: michael@0: bool invalidateOnNewType(Type type) { return false; } michael@0: bool invalidateOnNewPropertyState(TypeSet *property) { return false; } michael@0: bool invalidateOnNewObjectState(TypeObject *object) { michael@0: // We don't keep track of the exact dependencies the caller has on its michael@0: // inlined scripts' type sets, so always invalidate the caller. michael@0: return true; michael@0: } michael@0: michael@0: bool constraintHolds(JSContext *cx, michael@0: const HeapTypeSetKey &property, TemporaryTypeSet *expected) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: bool shouldSweep() { return false; } michael@0: }; michael@0: michael@0: // Constraint which triggers recompilation when the template object for a michael@0: // type's new script changes. michael@0: class ConstraintDataFreezeObjectForNewScriptTemplate michael@0: { michael@0: JSObject *templateObject; michael@0: michael@0: public: michael@0: ConstraintDataFreezeObjectForNewScriptTemplate(JSObject *templateObject) michael@0: : templateObject(templateObject) michael@0: {} michael@0: michael@0: const char *kind() { return "freezeObjectForNewScriptTemplate"; } michael@0: michael@0: bool invalidateOnNewType(Type type) { return false; } michael@0: bool invalidateOnNewPropertyState(TypeSet *property) { return false; } michael@0: bool invalidateOnNewObjectState(TypeObject *object) { michael@0: return !object->hasNewScript() || object->newScript()->templateObject != templateObject; michael@0: } michael@0: michael@0: bool constraintHolds(JSContext *cx, michael@0: const HeapTypeSetKey &property, TemporaryTypeSet *expected) michael@0: { michael@0: return !invalidateOnNewObjectState(property.object()->maybeType()); michael@0: } michael@0: michael@0: bool shouldSweep() { michael@0: // Note: |templateObject| is only used for equality testing. michael@0: return false; michael@0: } michael@0: }; michael@0: michael@0: // Constraint which triggers recompilation when a typed array's data becomes michael@0: // invalid. michael@0: class ConstraintDataFreezeObjectForTypedArrayData michael@0: { michael@0: void *viewData; michael@0: uint32_t length; michael@0: michael@0: public: michael@0: ConstraintDataFreezeObjectForTypedArrayData(TypedArrayObject &tarray) michael@0: : viewData(tarray.viewData()), michael@0: length(tarray.length()) michael@0: {} michael@0: michael@0: const char *kind() { return "freezeObjectForTypedArrayData"; } michael@0: michael@0: bool invalidateOnNewType(Type type) { return false; } michael@0: bool invalidateOnNewPropertyState(TypeSet *property) { return false; } michael@0: bool invalidateOnNewObjectState(TypeObject *object) { michael@0: TypedArrayObject &tarray = object->singleton()->as(); michael@0: return tarray.viewData() != viewData || tarray.length() != length; michael@0: } michael@0: michael@0: bool constraintHolds(JSContext *cx, michael@0: const HeapTypeSetKey &property, TemporaryTypeSet *expected) michael@0: { michael@0: return !invalidateOnNewObjectState(property.object()->maybeType()); michael@0: } michael@0: michael@0: bool shouldSweep() { michael@0: // Note: |viewData| is only used for equality testing. michael@0: return false; michael@0: } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: void michael@0: TypeObjectKey::watchStateChangeForInlinedCall(CompilerConstraintList *constraints) michael@0: { michael@0: HeapTypeSetKey objectProperty = property(JSID_EMPTY); michael@0: LifoAlloc *alloc = constraints->alloc(); michael@0: michael@0: typedef CompilerConstraintInstance T; michael@0: constraints->add(alloc->new_(alloc, objectProperty, ConstraintDataFreezeObjectForInlinedCall())); michael@0: } michael@0: michael@0: void michael@0: TypeObjectKey::watchStateChangeForNewScriptTemplate(CompilerConstraintList *constraints) michael@0: { michael@0: JSObject *templateObject = asTypeObject()->newScript()->templateObject; michael@0: HeapTypeSetKey objectProperty = property(JSID_EMPTY); michael@0: LifoAlloc *alloc = constraints->alloc(); michael@0: michael@0: typedef CompilerConstraintInstance T; michael@0: constraints->add(alloc->new_(alloc, objectProperty, michael@0: ConstraintDataFreezeObjectForNewScriptTemplate(templateObject))); michael@0: } michael@0: michael@0: void michael@0: TypeObjectKey::watchStateChangeForTypedArrayData(CompilerConstraintList *constraints) michael@0: { michael@0: TypedArrayObject &tarray = asSingleObject()->as(); michael@0: HeapTypeSetKey objectProperty = property(JSID_EMPTY); michael@0: LifoAlloc *alloc = constraints->alloc(); michael@0: michael@0: typedef CompilerConstraintInstance T; michael@0: constraints->add(alloc->new_(alloc, objectProperty, michael@0: ConstraintDataFreezeObjectForTypedArrayData(tarray))); michael@0: } michael@0: michael@0: static void michael@0: ObjectStateChange(ExclusiveContext *cxArg, TypeObject *object, bool markingUnknown) michael@0: { michael@0: if (object->unknownProperties()) michael@0: return; michael@0: michael@0: /* All constraints listening to state changes are on the empty id. */ michael@0: HeapTypeSet *types = object->maybeGetProperty(JSID_EMPTY); michael@0: michael@0: /* Mark as unknown after getting the types, to avoid assertion. */ michael@0: if (markingUnknown) michael@0: object->addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); michael@0: michael@0: if (types) { michael@0: if (JSContext *cx = cxArg->maybeJSContext()) { michael@0: TypeConstraint *constraint = types->constraintList; michael@0: while (constraint) { michael@0: constraint->newObjectState(cx, object); michael@0: constraint = constraint->next; michael@0: } michael@0: } else { michael@0: JS_ASSERT(!types->constraintList); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void michael@0: CheckNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun); michael@0: michael@0: namespace { michael@0: michael@0: class ConstraintDataFreezePropertyState michael@0: { michael@0: public: michael@0: enum Which { michael@0: NON_DATA, michael@0: NON_WRITABLE michael@0: } which; michael@0: michael@0: ConstraintDataFreezePropertyState(Which which) michael@0: : which(which) michael@0: {} michael@0: michael@0: const char *kind() { return (which == NON_DATA) ? "freezeNonDataProperty" : "freezeNonWritableProperty"; } michael@0: michael@0: bool invalidateOnNewType(Type type) { return false; } michael@0: bool invalidateOnNewPropertyState(TypeSet *property) { michael@0: return (which == NON_DATA) michael@0: ? property->nonDataProperty() michael@0: : property->nonWritableProperty(); michael@0: } michael@0: bool invalidateOnNewObjectState(TypeObject *object) { return false; } michael@0: michael@0: bool constraintHolds(JSContext *cx, michael@0: const HeapTypeSetKey &property, TemporaryTypeSet *expected) michael@0: { michael@0: return !invalidateOnNewPropertyState(property.maybeTypes()); michael@0: } michael@0: michael@0: bool shouldSweep() { return false; } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: bool michael@0: HeapTypeSetKey::nonData(CompilerConstraintList *constraints) michael@0: { michael@0: if (maybeTypes() && maybeTypes()->nonDataProperty()) michael@0: return true; michael@0: michael@0: LifoAlloc *alloc = constraints->alloc(); michael@0: michael@0: typedef CompilerConstraintInstance T; michael@0: constraints->add(alloc->new_(alloc, *this, michael@0: ConstraintDataFreezePropertyState(ConstraintDataFreezePropertyState::NON_DATA))); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: HeapTypeSetKey::nonWritable(CompilerConstraintList *constraints) michael@0: { michael@0: if (maybeTypes() && maybeTypes()->nonWritableProperty()) michael@0: return true; michael@0: michael@0: LifoAlloc *alloc = constraints->alloc(); michael@0: michael@0: typedef CompilerConstraintInstance T; michael@0: constraints->add(alloc->new_(alloc, *this, michael@0: ConstraintDataFreezePropertyState(ConstraintDataFreezePropertyState::NON_WRITABLE))); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: TemporaryTypeSet::filtersType(const TemporaryTypeSet *other, Type filteredType) const michael@0: { michael@0: if (other->unknown()) michael@0: return unknown(); michael@0: michael@0: for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) { michael@0: Type type = Type::PrimitiveType(TypeFlagPrimitive(flag)); michael@0: if (type != filteredType && other->hasType(type) && !hasType(type)) michael@0: return false; michael@0: } michael@0: michael@0: if (other->unknownObject()) michael@0: return unknownObject(); michael@0: michael@0: for (size_t i = 0; i < other->getObjectCount(); i++) { michael@0: TypeObjectKey *key = other->getObject(i); michael@0: if (key) { michael@0: Type type = Type::ObjectType(key); michael@0: if (type != filteredType && !hasType(type)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: TemporaryTypeSet::DoubleConversion michael@0: TemporaryTypeSet::convertDoubleElements(CompilerConstraintList *constraints) michael@0: { michael@0: if (unknownObject() || !getObjectCount()) michael@0: return AmbiguousDoubleConversion; michael@0: michael@0: bool alwaysConvert = true; michael@0: bool maybeConvert = false; michael@0: bool dontConvert = false; michael@0: michael@0: for (unsigned i = 0; i < getObjectCount(); i++) { michael@0: TypeObjectKey *type = getObject(i); michael@0: if (!type) michael@0: continue; michael@0: michael@0: if (type->unknownProperties()) { michael@0: alwaysConvert = false; michael@0: continue; michael@0: } michael@0: michael@0: HeapTypeSetKey property = type->property(JSID_VOID); michael@0: property.freeze(constraints); michael@0: michael@0: // We can't convert to double elements for objects which do not have michael@0: // double in their element types (as the conversion may render the type michael@0: // information incorrect), nor for non-array objects (as their elements michael@0: // may point to emptyObjectElements, which cannot be converted). michael@0: if (!property.maybeTypes() || michael@0: !property.maybeTypes()->hasType(Type::DoubleType()) || michael@0: type->clasp() != &ArrayObject::class_) michael@0: { michael@0: dontConvert = true; michael@0: alwaysConvert = false; michael@0: continue; michael@0: } michael@0: michael@0: // Only bother with converting known packed arrays whose possible michael@0: // element types are int or double. Other arrays require type tests michael@0: // when elements are accessed regardless of the conversion. michael@0: if (property.knownMIRType(constraints) == jit::MIRType_Double && michael@0: !type->hasFlags(constraints, OBJECT_FLAG_NON_PACKED)) michael@0: { michael@0: maybeConvert = true; michael@0: } else { michael@0: alwaysConvert = false; michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT_IF(alwaysConvert, maybeConvert); michael@0: michael@0: if (maybeConvert && dontConvert) michael@0: return AmbiguousDoubleConversion; michael@0: if (alwaysConvert) michael@0: return AlwaysConvertToDoubles; michael@0: if (maybeConvert) michael@0: return MaybeConvertToDoubles; michael@0: return DontConvertToDoubles; michael@0: } michael@0: michael@0: const Class * michael@0: TemporaryTypeSet::getKnownClass() michael@0: { michael@0: if (unknownObject()) michael@0: return nullptr; michael@0: michael@0: const Class *clasp = nullptr; michael@0: unsigned count = getObjectCount(); michael@0: michael@0: for (unsigned i = 0; i < count; i++) { michael@0: const Class *nclasp = getObjectClass(i); michael@0: if (!nclasp) michael@0: continue; michael@0: michael@0: if (clasp && clasp != nclasp) michael@0: return nullptr; michael@0: clasp = nclasp; michael@0: } michael@0: michael@0: return clasp; michael@0: } michael@0: michael@0: TemporaryTypeSet::ForAllResult michael@0: TemporaryTypeSet::forAllClasses(bool (*func)(const Class* clasp)) michael@0: { michael@0: if (unknownObject()) michael@0: return ForAllResult::MIXED; michael@0: michael@0: unsigned count = getObjectCount(); michael@0: if (count == 0) michael@0: return ForAllResult::EMPTY; michael@0: michael@0: bool true_results = false; michael@0: bool false_results = false; michael@0: for (unsigned i = 0; i < count; i++) { michael@0: const Class *clasp = getObjectClass(i); michael@0: if (!clasp) michael@0: return ForAllResult::MIXED; michael@0: if (func(clasp)) { michael@0: true_results = true; michael@0: if (false_results) return ForAllResult::MIXED; michael@0: } michael@0: else { michael@0: false_results = true; michael@0: if (true_results) return ForAllResult::MIXED; michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT(true_results != false_results); michael@0: michael@0: return true_results ? ForAllResult::ALL_TRUE : ForAllResult::ALL_FALSE; michael@0: } michael@0: michael@0: int michael@0: TemporaryTypeSet::getTypedArrayType() michael@0: { michael@0: const Class *clasp = getKnownClass(); michael@0: michael@0: if (clasp && IsTypedArrayClass(clasp)) michael@0: return clasp - &TypedArrayObject::classes[0]; michael@0: return ScalarTypeDescr::TYPE_MAX; michael@0: } michael@0: michael@0: bool michael@0: TemporaryTypeSet::isDOMClass() michael@0: { michael@0: if (unknownObject()) michael@0: return false; michael@0: michael@0: unsigned count = getObjectCount(); michael@0: for (unsigned i = 0; i < count; i++) { michael@0: const Class *clasp = getObjectClass(i); michael@0: if (clasp && !clasp->isDOMClass()) michael@0: return false; michael@0: } michael@0: michael@0: return count > 0; michael@0: } michael@0: michael@0: bool michael@0: TemporaryTypeSet::maybeCallable() michael@0: { michael@0: if (!maybeObject()) michael@0: return false; michael@0: michael@0: if (unknownObject()) michael@0: return true; michael@0: michael@0: unsigned count = getObjectCount(); michael@0: for (unsigned i = 0; i < count; i++) { michael@0: const Class *clasp = getObjectClass(i); michael@0: if (clasp && clasp->isCallable()) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: TemporaryTypeSet::maybeEmulatesUndefined() michael@0: { michael@0: if (!maybeObject()) michael@0: return false; michael@0: michael@0: if (unknownObject()) michael@0: return true; michael@0: michael@0: unsigned count = getObjectCount(); michael@0: for (unsigned i = 0; i < count; i++) { michael@0: // The object emulates undefined if clasp->emulatesUndefined() or if michael@0: // it's a WrapperObject, see EmulatesUndefined. Since all wrappers are michael@0: // proxies, we can just check for that. michael@0: const Class *clasp = getObjectClass(i); michael@0: if (clasp && (clasp->emulatesUndefined() || clasp->isProxy())) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: JSObject * michael@0: TemporaryTypeSet::getCommonPrototype() michael@0: { michael@0: if (unknownObject()) michael@0: return nullptr; michael@0: michael@0: JSObject *proto = nullptr; michael@0: unsigned count = getObjectCount(); michael@0: michael@0: for (unsigned i = 0; i < count; i++) { michael@0: TypeObjectKey *object = getObject(i); michael@0: if (!object) michael@0: continue; michael@0: michael@0: if (!object->hasTenuredProto()) michael@0: return nullptr; michael@0: michael@0: TaggedProto nproto = object->proto(); michael@0: if (proto) { michael@0: if (nproto != proto) michael@0: return nullptr; michael@0: } else { michael@0: if (!nproto.isObject()) michael@0: return nullptr; michael@0: proto = nproto.toObject(); michael@0: } michael@0: } michael@0: michael@0: return proto; michael@0: } michael@0: michael@0: bool michael@0: TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList *constraints, jsid id) michael@0: { michael@0: if (unknownObject()) michael@0: return true; michael@0: michael@0: for (unsigned i = 0; i < getObjectCount(); i++) { michael@0: TypeObjectKey *type = getObject(i); michael@0: if (!type) michael@0: continue; michael@0: michael@0: if (type->unknownProperties()) michael@0: return true; michael@0: michael@0: HeapTypeSetKey property = type->property(id); michael@0: if (property.needsBarrier(constraints)) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // TypeCompartment michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: TypeCompartment::TypeCompartment() michael@0: { michael@0: PodZero(this); michael@0: } michael@0: michael@0: TypeObject * michael@0: TypeCompartment::newTypeObject(ExclusiveContext *cx, const Class *clasp, Handle proto, michael@0: TypeObjectFlags initialFlags) michael@0: { michael@0: JS_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject())); michael@0: michael@0: if (cx->isJSContext()) { michael@0: if (proto.isObject() && IsInsideNursery(cx->asJSContext()->runtime(), proto.toObject())) michael@0: initialFlags |= OBJECT_FLAG_NURSERY_PROTO; michael@0: } michael@0: michael@0: TypeObject *object = js::NewTypeObject(cx); michael@0: if (!object) michael@0: return nullptr; michael@0: new(object) TypeObject(clasp, proto, initialFlags); michael@0: michael@0: return object; michael@0: } michael@0: michael@0: TypeObject * michael@0: TypeCompartment::addAllocationSiteTypeObject(JSContext *cx, AllocationSiteKey key) michael@0: { michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: if (!allocationSiteTable) { michael@0: allocationSiteTable = cx->new_(); michael@0: if (!allocationSiteTable || !allocationSiteTable->init()) { michael@0: js_delete(allocationSiteTable); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: AllocationSiteTable::AddPtr p = allocationSiteTable->lookupForAdd(key); michael@0: JS_ASSERT(!p); michael@0: michael@0: TypeObject *res = nullptr; michael@0: michael@0: jsbytecode *pc = key.script->offsetToPC(key.offset); michael@0: RootedScript keyScript(cx, key.script); michael@0: michael@0: if (!res) { michael@0: RootedObject proto(cx); michael@0: if (!GetBuiltinPrototype(cx, key.kind, &proto)) michael@0: return nullptr; michael@0: michael@0: Rooted tagged(cx, TaggedProto(proto)); michael@0: res = newTypeObject(cx, GetClassForProtoKey(key.kind), tagged, OBJECT_FLAG_FROM_ALLOCATION_SITE); michael@0: if (!res) michael@0: return nullptr; michael@0: key.script = keyScript; michael@0: } michael@0: michael@0: if (JSOp(*pc) == JSOP_NEWOBJECT) { michael@0: /* michael@0: * This object is always constructed the same way and will not be michael@0: * observed by other code before all properties have been added. Mark michael@0: * all the properties as definite properties of the object. michael@0: */ michael@0: RootedObject baseobj(cx, key.script->getObject(GET_UINT32_INDEX(pc))); michael@0: michael@0: if (!res->addDefiniteProperties(cx, baseobj)) michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!allocationSiteTable->add(p, key, res)) michael@0: return nullptr; michael@0: michael@0: return res; michael@0: } michael@0: michael@0: static inline jsid michael@0: GetAtomId(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset) michael@0: { michael@0: PropertyName *name = script->getName(GET_UINT32_INDEX(pc + offset)); michael@0: return IdToTypeId(NameToId(name)); michael@0: } michael@0: michael@0: bool michael@0: types::UseNewType(JSContext *cx, JSScript *script, jsbytecode *pc) michael@0: { michael@0: /* michael@0: * Make a heuristic guess at a use of JSOP_NEW that the constructed object michael@0: * should have a fresh type object. We do this when the NEW is immediately michael@0: * followed by a simple assignment to an object's .prototype field. michael@0: * This is designed to catch common patterns for subclassing in JS: michael@0: * michael@0: * function Super() { ... } michael@0: * function Sub1() { ... } michael@0: * function Sub2() { ... } michael@0: * michael@0: * Sub1.prototype = new Super(); michael@0: * Sub2.prototype = new Super(); michael@0: * michael@0: * Using distinct type objects for the particular prototypes of Sub1 and michael@0: * Sub2 lets us continue to distinguish the two subclasses and any extra michael@0: * properties added to those prototype objects. michael@0: */ michael@0: if (JSOp(*pc) == JSOP_NEW) michael@0: pc += JSOP_NEW_LENGTH; michael@0: else if (JSOp(*pc) == JSOP_SPREADNEW) michael@0: pc += JSOP_SPREADNEW_LENGTH; michael@0: else michael@0: return false; michael@0: if (JSOp(*pc) == JSOP_SETPROP) { michael@0: jsid id = GetAtomId(cx, script, pc, 0); michael@0: if (id == id_prototype(cx)) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: NewObjectKind michael@0: types::UseNewTypeForInitializer(JSScript *script, jsbytecode *pc, JSProtoKey key) michael@0: { michael@0: /* michael@0: * Objects created outside loops in global and eval scripts should have michael@0: * singleton types. For now this is only done for plain objects and typed michael@0: * arrays, but not normal arrays. michael@0: */ michael@0: michael@0: if (script->functionNonDelazifying() && !script->treatAsRunOnce()) michael@0: return GenericObject; michael@0: michael@0: if (key != JSProto_Object && !(key >= JSProto_Int8Array && key <= JSProto_Uint8ClampedArray)) michael@0: return GenericObject; michael@0: michael@0: /* michael@0: * All loops in the script will have a JSTRY_ITER or JSTRY_LOOP try note michael@0: * indicating their boundary. michael@0: */ michael@0: michael@0: if (!script->hasTrynotes()) michael@0: return SingletonObject; michael@0: michael@0: unsigned offset = script->pcToOffset(pc); michael@0: michael@0: JSTryNote *tn = script->trynotes()->vector; michael@0: JSTryNote *tnlimit = tn + script->trynotes()->length; michael@0: for (; tn < tnlimit; tn++) { michael@0: if (tn->kind != JSTRY_ITER && tn->kind != JSTRY_LOOP) michael@0: continue; michael@0: michael@0: unsigned startOffset = script->mainOffset() + tn->start; michael@0: unsigned endOffset = startOffset + tn->length; michael@0: michael@0: if (offset >= startOffset && offset < endOffset) michael@0: return GenericObject; michael@0: } michael@0: michael@0: return SingletonObject; michael@0: } michael@0: michael@0: NewObjectKind michael@0: types::UseNewTypeForInitializer(JSScript *script, jsbytecode *pc, const Class *clasp) michael@0: { michael@0: return UseNewTypeForInitializer(script, pc, JSCLASS_CACHED_PROTO_KEY(clasp)); michael@0: } michael@0: michael@0: static inline bool michael@0: ClassCanHaveExtraProperties(const Class *clasp) michael@0: { michael@0: JS_ASSERT(clasp->resolve); michael@0: return clasp->resolve != JS_ResolveStub michael@0: || clasp->ops.lookupGeneric michael@0: || clasp->ops.getGeneric michael@0: || IsTypedArrayClass(clasp); michael@0: } michael@0: michael@0: static inline bool michael@0: PrototypeHasIndexedProperty(CompilerConstraintList *constraints, JSObject *obj) michael@0: { michael@0: do { michael@0: TypeObjectKey *type = TypeObjectKey::get(obj); michael@0: if (ClassCanHaveExtraProperties(type->clasp())) michael@0: return true; michael@0: if (type->unknownProperties()) michael@0: return true; michael@0: HeapTypeSetKey index = type->property(JSID_VOID); michael@0: if (index.nonData(constraints) || index.isOwnProperty(constraints)) michael@0: return true; michael@0: if (!obj->hasTenuredProto()) michael@0: return true; michael@0: obj = obj->getProto(); michael@0: } while (obj); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: types::ArrayPrototypeHasIndexedProperty(CompilerConstraintList *constraints, JSScript *script) michael@0: { michael@0: if (JSObject *proto = script->global().maybeGetArrayPrototype()) michael@0: return PrototypeHasIndexedProperty(constraints, proto); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: types::TypeCanHaveExtraIndexedProperties(CompilerConstraintList *constraints, michael@0: TemporaryTypeSet *types) michael@0: { michael@0: const Class *clasp = types->getKnownClass(); michael@0: michael@0: // Note: typed arrays have indexed properties not accounted for by type michael@0: // information, though these are all in bounds and will be accounted for michael@0: // by JIT paths. michael@0: if (!clasp || (ClassCanHaveExtraProperties(clasp) && !IsTypedArrayClass(clasp))) michael@0: return true; michael@0: michael@0: if (types->hasObjectFlags(constraints, types::OBJECT_FLAG_SPARSE_INDEXES)) michael@0: return true; michael@0: michael@0: JSObject *proto = types->getCommonPrototype(); michael@0: if (!proto) michael@0: return true; michael@0: michael@0: return PrototypeHasIndexedProperty(constraints, proto); michael@0: } michael@0: michael@0: void michael@0: TypeZone::processPendingRecompiles(FreeOp *fop) michael@0: { michael@0: if (!pendingRecompiles) michael@0: return; michael@0: michael@0: /* Steal the list of scripts to recompile, else we will try to recursively recompile them. */ michael@0: Vector *pending = pendingRecompiles; michael@0: pendingRecompiles = nullptr; michael@0: michael@0: JS_ASSERT(!pending->empty()); michael@0: michael@0: #ifdef JS_ION michael@0: jit::Invalidate(*this, fop, *pending); michael@0: #endif michael@0: michael@0: fop->delete_(pending); michael@0: } michael@0: michael@0: void michael@0: TypeZone::addPendingRecompile(JSContext *cx, const RecompileInfo &info) michael@0: { michael@0: CompilerOutput *co = info.compilerOutput(cx); michael@0: if (!co || !co->isValid() || co->pendingInvalidation()) michael@0: return; michael@0: michael@0: InferSpew(ISpewOps, "addPendingRecompile: %p:%s:%d", michael@0: co->script(), co->script()->filename(), co->script()->lineno()); michael@0: michael@0: co->setPendingInvalidation(); michael@0: michael@0: if (!pendingRecompiles) { michael@0: pendingRecompiles = cx->new_< Vector >(cx); michael@0: if (!pendingRecompiles) michael@0: CrashAtUnhandlableOOM("Could not update pendingRecompiles"); michael@0: } michael@0: michael@0: if (!pendingRecompiles->append(info)) michael@0: CrashAtUnhandlableOOM("Could not update pendingRecompiles"); michael@0: } michael@0: michael@0: void michael@0: TypeZone::addPendingRecompile(JSContext *cx, JSScript *script) michael@0: { michael@0: JS_ASSERT(script); michael@0: michael@0: #ifdef JS_ION michael@0: CancelOffThreadIonCompile(cx->compartment(), script); michael@0: michael@0: // Let the script warm up again before attempting another compile. michael@0: if (jit::IsBaselineEnabled(cx)) michael@0: script->resetUseCount(); michael@0: michael@0: if (script->hasIonScript()) michael@0: addPendingRecompile(cx, script->ionScript()->recompileInfo()); michael@0: michael@0: if (script->hasParallelIonScript()) michael@0: addPendingRecompile(cx, script->parallelIonScript()->recompileInfo()); michael@0: #endif michael@0: michael@0: // When one script is inlined into another the caller listens to state michael@0: // changes on the callee's script, so trigger these to force recompilation michael@0: // of any such callers. michael@0: if (script->functionNonDelazifying() && !script->functionNonDelazifying()->hasLazyType()) michael@0: ObjectStateChange(cx, script->functionNonDelazifying()->type(), false); michael@0: } michael@0: michael@0: void michael@0: TypeCompartment::markSetsUnknown(JSContext *cx, TypeObject *target) michael@0: { michael@0: JS_ASSERT(this == &cx->compartment()->types); michael@0: JS_ASSERT(!(target->flags() & OBJECT_FLAG_SETS_MARKED_UNKNOWN)); michael@0: JS_ASSERT(!target->singleton()); michael@0: JS_ASSERT(target->unknownProperties()); michael@0: michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: /* Mark type sets which contain obj as having a generic object types. */ michael@0: michael@0: for (gc::CellIter i(cx->zone(), gc::FINALIZE_TYPE_OBJECT); !i.done(); i.next()) { michael@0: TypeObject *object = i.get(); michael@0: unsigned count = object->getPropertyCount(); michael@0: for (unsigned i = 0; i < count; i++) { michael@0: Property *prop = object->getProperty(i); michael@0: if (prop && prop->types.hasType(Type::ObjectType(target))) michael@0: prop->types.addType(cx, Type::AnyObjectType()); michael@0: } michael@0: } michael@0: michael@0: for (gc::CellIter i(cx->zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) { michael@0: RootedScript script(cx, i.get()); michael@0: if (script->types) { michael@0: unsigned count = TypeScript::NumTypeSets(script); michael@0: StackTypeSet *typeArray = script->types->typeArray(); michael@0: for (unsigned i = 0; i < count; i++) { michael@0: if (typeArray[i].hasType(Type::ObjectType(target))) michael@0: typeArray[i].addType(cx, Type::AnyObjectType()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: target->addFlags(OBJECT_FLAG_SETS_MARKED_UNKNOWN); michael@0: } michael@0: michael@0: void michael@0: TypeCompartment::print(JSContext *cx, bool force) michael@0: { michael@0: #ifdef DEBUG michael@0: gc::AutoSuppressGC suppressGC(cx); michael@0: michael@0: JSCompartment *compartment = this->compartment(); michael@0: AutoEnterAnalysis enter(nullptr, compartment); michael@0: michael@0: if (!force && !InferSpewActive(ISpewResult)) michael@0: return; michael@0: michael@0: for (gc::CellIter i(compartment->zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) { michael@0: // Note: use cx->runtime() instead of cx to work around IsInRequest(cx) michael@0: // assertion failures when we're called from DestroyContext. michael@0: RootedScript script(cx->runtime(), i.get()); michael@0: if (script->types) michael@0: script->types->printTypes(cx, script); michael@0: } michael@0: michael@0: for (gc::CellIter i(compartment->zone(), gc::FINALIZE_TYPE_OBJECT); !i.done(); i.next()) { michael@0: TypeObject *object = i.get(); michael@0: object->print(); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // TypeCompartment tables michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: /* michael@0: * The arrayTypeTable and objectTypeTable are per-compartment tables for making michael@0: * common type objects to model the contents of large script singletons and michael@0: * JSON objects. These are vanilla Arrays and native Objects, so we distinguish michael@0: * the types of different ones by looking at the types of their properties. michael@0: * michael@0: * All singleton/JSON arrays which have the same prototype, are homogenous and michael@0: * of the same element type will share a type object. All singleton/JSON michael@0: * objects which have the same shape and property types will also share a type michael@0: * object. We don't try to collate arrays or objects that have type mismatches. michael@0: */ michael@0: michael@0: static inline bool michael@0: NumberTypes(Type a, Type b) michael@0: { michael@0: return (a.isPrimitive(JSVAL_TYPE_INT32) || a.isPrimitive(JSVAL_TYPE_DOUBLE)) michael@0: && (b.isPrimitive(JSVAL_TYPE_INT32) || b.isPrimitive(JSVAL_TYPE_DOUBLE)); michael@0: } michael@0: michael@0: /* michael@0: * As for GetValueType, but requires object types to be non-singletons with michael@0: * their default prototype. These are the only values that should appear in michael@0: * arrays and objects whose type can be fixed. michael@0: */ michael@0: static inline Type michael@0: GetValueTypeForTable(const Value &v) michael@0: { michael@0: Type type = GetValueType(v); michael@0: JS_ASSERT(!type.isSingleObject()); michael@0: return type; michael@0: } michael@0: michael@0: struct types::ArrayTableKey : public DefaultHasher michael@0: { michael@0: Type type; michael@0: JSObject *proto; michael@0: michael@0: ArrayTableKey() michael@0: : type(Type::UndefinedType()), proto(nullptr) michael@0: {} michael@0: michael@0: ArrayTableKey(Type type, JSObject *proto) michael@0: : type(type), proto(proto) michael@0: {} michael@0: michael@0: static inline uint32_t hash(const ArrayTableKey &v) { michael@0: return (uint32_t) (v.type.raw() ^ ((uint32_t)(size_t)v.proto >> 2)); michael@0: } michael@0: michael@0: static inline bool match(const ArrayTableKey &v1, const ArrayTableKey &v2) { michael@0: return v1.type == v2.type && v1.proto == v2.proto; michael@0: } michael@0: }; michael@0: michael@0: void michael@0: TypeCompartment::setTypeToHomogenousArray(ExclusiveContext *cx, michael@0: JSObject *obj, Type elementType) michael@0: { michael@0: JS_ASSERT(cx->compartment()->activeAnalysis); michael@0: michael@0: if (!arrayTypeTable) { michael@0: arrayTypeTable = cx->new_(); michael@0: if (!arrayTypeTable || !arrayTypeTable->init()) { michael@0: arrayTypeTable = nullptr; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: ArrayTableKey key(elementType, obj->getProto()); michael@0: DependentAddPtr p(cx, *arrayTypeTable, key); michael@0: if (p) { michael@0: obj->setType(p->value()); michael@0: } else { michael@0: /* Make a new type to use for future arrays with the same elements. */ michael@0: RootedObject objProto(cx, obj->getProto()); michael@0: TypeObject *objType = newTypeObject(cx, &ArrayObject::class_, objProto); michael@0: if (!objType) michael@0: return; michael@0: obj->setType(objType); michael@0: michael@0: if (!objType->unknownProperties()) michael@0: objType->addPropertyType(cx, JSID_VOID, elementType); michael@0: michael@0: key.proto = objProto; michael@0: (void) p.add(cx, *arrayTypeTable, key, objType); michael@0: } michael@0: } michael@0: michael@0: void michael@0: TypeCompartment::fixArrayType(ExclusiveContext *cx, JSObject *obj) michael@0: { michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: /* michael@0: * If the array is of homogenous type, pick a type object which will be michael@0: * shared with all other singleton/JSON arrays of the same type. michael@0: * If the array is heterogenous, keep the existing type object, which has michael@0: * unknown properties. michael@0: */ michael@0: JS_ASSERT(obj->is()); michael@0: michael@0: unsigned len = obj->getDenseInitializedLength(); michael@0: if (len == 0) michael@0: return; michael@0: michael@0: Type type = GetValueTypeForTable(obj->getDenseElement(0)); michael@0: michael@0: for (unsigned i = 1; i < len; i++) { michael@0: Type ntype = GetValueTypeForTable(obj->getDenseElement(i)); michael@0: if (ntype != type) { michael@0: if (NumberTypes(type, ntype)) michael@0: type = Type::DoubleType(); michael@0: else michael@0: return; michael@0: } michael@0: } michael@0: michael@0: setTypeToHomogenousArray(cx, obj, type); michael@0: } michael@0: michael@0: void michael@0: types::FixRestArgumentsType(ExclusiveContext *cx, JSObject *obj) michael@0: { michael@0: cx->compartment()->types.fixRestArgumentsType(cx, obj); michael@0: } michael@0: michael@0: void michael@0: TypeCompartment::fixRestArgumentsType(ExclusiveContext *cx, JSObject *obj) michael@0: { michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: /* michael@0: * Tracking element types for rest argument arrays is not worth it, but we michael@0: * still want it to be known that it's a dense array. michael@0: */ michael@0: JS_ASSERT(obj->is()); michael@0: michael@0: setTypeToHomogenousArray(cx, obj, Type::UnknownType()); michael@0: } michael@0: michael@0: /* michael@0: * N.B. We could also use the initial shape of the object (before its type is michael@0: * fixed) as the key in the object table, but since all references in the table michael@0: * are weak the hash entries would usually be collected on GC even if objects michael@0: * with the new type/shape are still live. michael@0: */ michael@0: struct types::ObjectTableKey michael@0: { michael@0: jsid *properties; michael@0: uint32_t nproperties; michael@0: uint32_t nfixed; michael@0: michael@0: struct Lookup { michael@0: IdValuePair *properties; michael@0: uint32_t nproperties; michael@0: uint32_t nfixed; michael@0: michael@0: Lookup(IdValuePair *properties, uint32_t nproperties, uint32_t nfixed) michael@0: : properties(properties), nproperties(nproperties), nfixed(nfixed) michael@0: {} michael@0: }; michael@0: michael@0: static inline HashNumber hash(const Lookup &lookup) { michael@0: return (HashNumber) (JSID_BITS(lookup.properties[lookup.nproperties - 1].id) ^ michael@0: lookup.nproperties ^ michael@0: lookup.nfixed); michael@0: } michael@0: michael@0: static inline bool match(const ObjectTableKey &v, const Lookup &lookup) { michael@0: if (lookup.nproperties != v.nproperties || lookup.nfixed != v.nfixed) michael@0: return false; michael@0: for (size_t i = 0; i < lookup.nproperties; i++) { michael@0: if (lookup.properties[i].id != v.properties[i]) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: struct types::ObjectTableEntry michael@0: { michael@0: ReadBarriered object; michael@0: ReadBarriered shape; michael@0: Type *types; michael@0: }; michael@0: michael@0: static inline void michael@0: UpdateObjectTableEntryTypes(ExclusiveContext *cx, ObjectTableEntry &entry, michael@0: IdValuePair *properties, size_t nproperties) michael@0: { michael@0: if (entry.object->unknownProperties()) michael@0: return; michael@0: for (size_t i = 0; i < nproperties; i++) { michael@0: Type type = entry.types[i]; michael@0: Type ntype = GetValueTypeForTable(properties[i].value); michael@0: if (ntype == type) michael@0: continue; michael@0: if (ntype.isPrimitive(JSVAL_TYPE_INT32) && michael@0: type.isPrimitive(JSVAL_TYPE_DOUBLE)) michael@0: { michael@0: /* The property types already reflect 'int32'. */ michael@0: } else { michael@0: if (ntype.isPrimitive(JSVAL_TYPE_DOUBLE) && michael@0: type.isPrimitive(JSVAL_TYPE_INT32)) michael@0: { michael@0: /* Include 'double' in the property types to avoid the update below later. */ michael@0: entry.types[i] = Type::DoubleType(); michael@0: } michael@0: entry.object->addPropertyType(cx, IdToTypeId(properties[i].id), ntype); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: TypeCompartment::fixObjectType(ExclusiveContext *cx, JSObject *obj) michael@0: { michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: if (!objectTypeTable) { michael@0: objectTypeTable = cx->new_(); michael@0: if (!objectTypeTable || !objectTypeTable->init()) { michael@0: js_delete(objectTypeTable); michael@0: objectTypeTable = nullptr; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Use the same type object for all singleton/JSON objects with the same michael@0: * base shape, i.e. the same fields written in the same order. michael@0: */ michael@0: JS_ASSERT(obj->is()); michael@0: michael@0: /* michael@0: * Exclude some objects we can't readily associate common types for based on their michael@0: * shape. Objects with metadata are excluded so that the metadata does not need to michael@0: * be included in the table lookup (the metadata object might be in the nursery). michael@0: */ michael@0: if (obj->slotSpan() == 0 || obj->inDictionaryMode() || !obj->hasEmptyElements() || obj->getMetadata()) michael@0: return; michael@0: michael@0: Vector properties(cx); michael@0: if (!properties.resize(obj->slotSpan())) michael@0: return; michael@0: michael@0: Shape *shape = obj->lastProperty(); michael@0: while (!shape->isEmptyShape()) { michael@0: IdValuePair &entry = properties[shape->slot()]; michael@0: entry.id = shape->propid(); michael@0: entry.value = obj->getSlot(shape->slot()); michael@0: shape = shape->previous(); michael@0: } michael@0: michael@0: ObjectTableKey::Lookup lookup(properties.begin(), properties.length(), obj->numFixedSlots()); michael@0: ObjectTypeTable::AddPtr p = objectTypeTable->lookupForAdd(lookup); michael@0: michael@0: if (p) { michael@0: JS_ASSERT(obj->getProto() == p->value().object->proto().toObject()); michael@0: JS_ASSERT(obj->lastProperty() == p->value().shape); michael@0: michael@0: UpdateObjectTableEntryTypes(cx, p->value(), properties.begin(), properties.length()); michael@0: obj->setType(p->value().object); michael@0: return; michael@0: } michael@0: michael@0: /* Make a new type to use for the object and similar future ones. */ michael@0: Rooted objProto(cx, obj->getTaggedProto()); michael@0: TypeObject *objType = newTypeObject(cx, &JSObject::class_, objProto); michael@0: if (!objType || !objType->addDefiniteProperties(cx, obj)) michael@0: return; michael@0: michael@0: if (obj->isIndexed()) michael@0: objType->setFlags(cx, OBJECT_FLAG_SPARSE_INDEXES); michael@0: michael@0: ScopedJSFreePtr ids(cx->pod_calloc(properties.length())); michael@0: if (!ids) michael@0: return; michael@0: michael@0: ScopedJSFreePtr types(cx->pod_calloc(properties.length())); michael@0: if (!types) michael@0: return; michael@0: michael@0: for (size_t i = 0; i < properties.length(); i++) { michael@0: ids[i] = properties[i].id; michael@0: types[i] = GetValueTypeForTable(obj->getSlot(i)); michael@0: if (!objType->unknownProperties()) michael@0: objType->addPropertyType(cx, IdToTypeId(ids[i]), types[i]); michael@0: } michael@0: michael@0: ObjectTableKey key; michael@0: key.properties = ids; michael@0: key.nproperties = properties.length(); michael@0: key.nfixed = obj->numFixedSlots(); michael@0: JS_ASSERT(ObjectTableKey::match(key, lookup)); michael@0: michael@0: ObjectTableEntry entry; michael@0: entry.object = objType; michael@0: entry.shape = obj->lastProperty(); michael@0: entry.types = types; michael@0: michael@0: obj->setType(objType); michael@0: michael@0: p = objectTypeTable->lookupForAdd(lookup); michael@0: if (objectTypeTable->add(p, key, entry)) { michael@0: ids.forget(); michael@0: types.forget(); michael@0: } michael@0: } michael@0: michael@0: JSObject * michael@0: TypeCompartment::newTypedObject(JSContext *cx, IdValuePair *properties, size_t nproperties) michael@0: { michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: if (!objectTypeTable) { michael@0: objectTypeTable = cx->new_(); michael@0: if (!objectTypeTable || !objectTypeTable->init()) { michael@0: js_delete(objectTypeTable); michael@0: objectTypeTable = nullptr; michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Use the object type table to allocate an object with the specified michael@0: * properties, filling in its final type and shape and failing if no cache michael@0: * entry could be found for the properties. michael@0: */ michael@0: michael@0: /* michael@0: * Filter out a few cases where we don't want to use the object type table. michael@0: * Note that if the properties contain any duplicates or dense indexes, michael@0: * the lookup below will fail as such arrays of properties cannot be stored michael@0: * in the object type table --- fixObjectType populates the table with michael@0: * properties read off its input object, which cannot be duplicates, and michael@0: * ignores objects with dense indexes. michael@0: */ michael@0: if (!nproperties || nproperties >= PropertyTree::MAX_HEIGHT) michael@0: return nullptr; michael@0: michael@0: gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties); michael@0: size_t nfixed = gc::GetGCKindSlots(allocKind, &JSObject::class_); michael@0: michael@0: ObjectTableKey::Lookup lookup(properties, nproperties, nfixed); michael@0: ObjectTypeTable::AddPtr p = objectTypeTable->lookupForAdd(lookup); michael@0: michael@0: if (!p) michael@0: return nullptr; michael@0: michael@0: RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, allocKind)); michael@0: if (!obj) { michael@0: cx->clearPendingException(); michael@0: return nullptr; michael@0: } michael@0: JS_ASSERT(obj->getProto() == p->value().object->proto().toObject()); michael@0: michael@0: RootedShape shape(cx, p->value().shape); michael@0: if (!JSObject::setLastProperty(cx, obj, shape)) { michael@0: cx->clearPendingException(); michael@0: return nullptr; michael@0: } michael@0: michael@0: UpdateObjectTableEntryTypes(cx, p->value(), properties, nproperties); michael@0: michael@0: for (size_t i = 0; i < nproperties; i++) michael@0: obj->setSlot(i, properties[i].value); michael@0: michael@0: obj->setType(p->value().object); michael@0: return obj; michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // TypeObject michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: void michael@0: TypeObject::setProto(JSContext *cx, TaggedProto proto) michael@0: { michael@0: JS_ASSERT(singleton()); michael@0: michael@0: if (proto.isObject() && IsInsideNursery(cx->runtime(), proto.toObject())) michael@0: addFlags(OBJECT_FLAG_NURSERY_PROTO); michael@0: michael@0: setProtoUnchecked(proto); michael@0: } michael@0: michael@0: static inline void michael@0: UpdatePropertyType(ExclusiveContext *cx, HeapTypeSet *types, JSObject *obj, Shape *shape, michael@0: bool indexed) michael@0: { michael@0: if (!shape->writable()) michael@0: types->setNonWritableProperty(cx); michael@0: michael@0: if (shape->hasGetterValue() || shape->hasSetterValue()) { michael@0: types->setNonDataProperty(cx); michael@0: types->TypeSet::addType(Type::UnknownType(), &cx->typeLifoAlloc()); michael@0: } else if (shape->hasDefaultGetter() && shape->hasSlot()) { michael@0: if (!indexed && types->canSetDefinite(shape->slot())) michael@0: types->setDefinite(shape->slot()); michael@0: michael@0: const Value &value = obj->nativeGetSlot(shape->slot()); michael@0: michael@0: /* michael@0: * Don't add initial undefined types for properties of global objects michael@0: * that are not collated into the JSID_VOID property (see propertySet michael@0: * comment). michael@0: */ michael@0: if (indexed || !value.isUndefined() || !CanHaveEmptyPropertyTypesForOwnProperty(obj)) { michael@0: Type type = GetValueType(value); michael@0: types->TypeSet::addType(type, &cx->typeLifoAlloc()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: TypeObject::updateNewPropertyTypes(ExclusiveContext *cx, jsid id, HeapTypeSet *types) michael@0: { michael@0: InferSpew(ISpewOps, "typeSet: %sT%p%s property %s %s", michael@0: InferSpewColor(types), types, InferSpewColorReset(), michael@0: TypeObjectString(this), TypeIdString(id)); michael@0: michael@0: if (!singleton() || !singleton()->isNative()) michael@0: return; michael@0: michael@0: /* michael@0: * Fill the property in with any type the object already has in an own michael@0: * property. We are only interested in plain native properties and michael@0: * dense elements which don't go through a barrier when read by the VM michael@0: * or jitcode. michael@0: */ michael@0: michael@0: if (JSID_IS_VOID(id)) { michael@0: /* Go through all shapes on the object to get integer-valued properties. */ michael@0: RootedShape shape(cx, singleton()->lastProperty()); michael@0: while (!shape->isEmptyShape()) { michael@0: if (JSID_IS_VOID(IdToTypeId(shape->propid()))) michael@0: UpdatePropertyType(cx, types, singleton(), shape, true); michael@0: shape = shape->previous(); michael@0: } michael@0: michael@0: /* Also get values of any dense elements in the object. */ michael@0: for (size_t i = 0; i < singleton()->getDenseInitializedLength(); i++) { michael@0: const Value &value = singleton()->getDenseElement(i); michael@0: if (!value.isMagic(JS_ELEMENTS_HOLE)) { michael@0: Type type = GetValueType(value); michael@0: types->TypeSet::addType(type, &cx->typeLifoAlloc()); michael@0: } michael@0: } michael@0: } else if (!JSID_IS_EMPTY(id)) { michael@0: RootedId rootedId(cx, id); michael@0: Shape *shape = singleton()->nativeLookup(cx, rootedId); michael@0: if (shape) michael@0: UpdatePropertyType(cx, types, singleton(), shape, false); michael@0: } michael@0: michael@0: if (singleton()->watched()) { michael@0: /* michael@0: * Mark the property as non-data, to inhibit optimizations on it michael@0: * and avoid bypassing the watchpoint handler. michael@0: */ michael@0: types->setNonDataProperty(cx); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: TypeObject::addDefiniteProperties(ExclusiveContext *cx, JSObject *obj) michael@0: { michael@0: if (unknownProperties()) michael@0: return true; michael@0: michael@0: /* Mark all properties of obj as definite properties of this type. */ michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: RootedShape shape(cx, obj->lastProperty()); michael@0: while (!shape->isEmptyShape()) { michael@0: jsid id = IdToTypeId(shape->propid()); michael@0: if (!JSID_IS_VOID(id) && obj->isFixedSlot(shape->slot())) { michael@0: TypeSet *types = getProperty(cx, id); michael@0: if (!types) michael@0: return false; michael@0: types->setDefinite(shape->slot()); michael@0: } michael@0: shape = shape->previous(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: TypeObject::matchDefiniteProperties(HandleObject obj) michael@0: { michael@0: unsigned count = getPropertyCount(); michael@0: for (unsigned i = 0; i < count; i++) { michael@0: Property *prop = getProperty(i); michael@0: if (!prop) michael@0: continue; michael@0: if (prop->types.definiteProperty()) { michael@0: unsigned slot = prop->types.definiteSlot(); michael@0: michael@0: bool found = false; michael@0: Shape *shape = obj->lastProperty(); michael@0: while (!shape->isEmptyShape()) { michael@0: if (shape->slot() == slot && shape->propid() == prop->id) { michael@0: found = true; michael@0: break; michael@0: } michael@0: shape = shape->previous(); michael@0: } michael@0: if (!found) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static inline void michael@0: InlineAddTypeProperty(ExclusiveContext *cx, TypeObject *obj, jsid id, Type type) michael@0: { michael@0: JS_ASSERT(id == IdToTypeId(id)); michael@0: michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: HeapTypeSet *types = obj->getProperty(cx, id); michael@0: if (!types || types->hasType(type)) michael@0: return; michael@0: michael@0: InferSpew(ISpewOps, "externalType: property %s %s: %s", michael@0: TypeObjectString(obj), TypeIdString(id), TypeString(type)); michael@0: types->addType(cx, type); michael@0: } michael@0: michael@0: void michael@0: TypeObject::addPropertyType(ExclusiveContext *cx, jsid id, Type type) michael@0: { michael@0: InlineAddTypeProperty(cx, this, id, type); michael@0: } michael@0: michael@0: void michael@0: TypeObject::addPropertyType(ExclusiveContext *cx, jsid id, const Value &value) michael@0: { michael@0: InlineAddTypeProperty(cx, this, id, GetValueType(value)); michael@0: } michael@0: michael@0: void michael@0: TypeObject::markPropertyNonData(ExclusiveContext *cx, jsid id) michael@0: { michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: id = IdToTypeId(id); michael@0: michael@0: HeapTypeSet *types = getProperty(cx, id); michael@0: if (types) michael@0: types->setNonDataProperty(cx); michael@0: } michael@0: michael@0: void michael@0: TypeObject::markPropertyNonWritable(ExclusiveContext *cx, jsid id) michael@0: { michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: id = IdToTypeId(id); michael@0: michael@0: HeapTypeSet *types = getProperty(cx, id); michael@0: if (types) michael@0: types->setNonWritableProperty(cx); michael@0: } michael@0: michael@0: bool michael@0: TypeObject::isPropertyNonData(jsid id) michael@0: { michael@0: TypeSet *types = maybeGetProperty(id); michael@0: if (types) michael@0: return types->nonDataProperty(); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: TypeObject::isPropertyNonWritable(jsid id) michael@0: { michael@0: TypeSet *types = maybeGetProperty(id); michael@0: if (types) michael@0: return types->nonWritableProperty(); michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: TypeObject::markStateChange(ExclusiveContext *cxArg) michael@0: { michael@0: if (unknownProperties()) michael@0: return; michael@0: michael@0: AutoEnterAnalysis enter(cxArg); michael@0: HeapTypeSet *types = maybeGetProperty(JSID_EMPTY); michael@0: if (types) { michael@0: if (JSContext *cx = cxArg->maybeJSContext()) { michael@0: TypeConstraint *constraint = types->constraintList; michael@0: while (constraint) { michael@0: constraint->newObjectState(cx, this); michael@0: constraint = constraint->next; michael@0: } michael@0: } else { michael@0: JS_ASSERT(!types->constraintList); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: TypeObject::setFlags(ExclusiveContext *cx, TypeObjectFlags flags) michael@0: { michael@0: if (hasAllFlags(flags)) michael@0: return; michael@0: michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: if (singleton()) { michael@0: /* Make sure flags are consistent with persistent object state. */ michael@0: JS_ASSERT_IF(flags & OBJECT_FLAG_ITERATED, michael@0: singleton()->lastProperty()->hasObjectFlag(BaseShape::ITERATED_SINGLETON)); michael@0: } michael@0: michael@0: addFlags(flags); michael@0: michael@0: InferSpew(ISpewOps, "%s: setFlags 0x%x", TypeObjectString(this), flags); michael@0: michael@0: ObjectStateChange(cx, this, false); michael@0: } michael@0: michael@0: void michael@0: TypeObject::markUnknown(ExclusiveContext *cx) michael@0: { michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: JS_ASSERT(cx->compartment()->activeAnalysis); michael@0: JS_ASSERT(!unknownProperties()); michael@0: michael@0: if (!(flags() & OBJECT_FLAG_ADDENDUM_CLEARED)) michael@0: clearAddendum(cx); michael@0: michael@0: InferSpew(ISpewOps, "UnknownProperties: %s", TypeObjectString(this)); michael@0: michael@0: ObjectStateChange(cx, this, true); michael@0: michael@0: /* michael@0: * Existing constraints may have already been added to this object, which we need michael@0: * to do the right thing for. We can't ensure that we will mark all unknown michael@0: * objects before they have been accessed, as the __proto__ of a known object michael@0: * could be dynamically set to an unknown object, and we can decide to ignore michael@0: * properties of an object during analysis (i.e. hashmaps). Adding unknown for michael@0: * any properties accessed already accounts for possible values read from them. michael@0: */ michael@0: michael@0: unsigned count = getPropertyCount(); michael@0: for (unsigned i = 0; i < count; i++) { michael@0: Property *prop = getProperty(i); michael@0: if (prop) { michael@0: prop->types.addType(cx, Type::UnknownType()); michael@0: prop->types.setNonDataProperty(cx); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: TypeObject::clearAddendum(ExclusiveContext *cx) michael@0: { michael@0: JS_ASSERT(!(flags() & OBJECT_FLAG_ADDENDUM_CLEARED)); michael@0: michael@0: addFlags(OBJECT_FLAG_ADDENDUM_CLEARED); michael@0: michael@0: /* michael@0: * It is possible for the object to not have a new script or other michael@0: * addendum yet, but to have one added in the future. When michael@0: * analyzing properties of new scripts we mix in adding michael@0: * constraints to trigger clearNewScript with changes to the type michael@0: * sets themselves (from breakTypeBarriers). It is possible that michael@0: * we could trigger one of these constraints before michael@0: * AnalyzeNewScriptProperties has finished, in which case we want michael@0: * to make sure that call fails. michael@0: */ michael@0: if (!addendum) michael@0: return; michael@0: michael@0: switch (addendum->kind) { michael@0: case TypeObjectAddendum::NewScript: michael@0: clearNewScriptAddendum(cx); michael@0: break; michael@0: michael@0: case TypeObjectAddendum::TypedObject: michael@0: clearTypedObjectAddendum(cx); michael@0: break; michael@0: } michael@0: michael@0: /* We nullptr out addendum *before* freeing it so the write barrier works. */ michael@0: TypeObjectAddendum *savedAddendum = addendum; michael@0: addendum = nullptr; michael@0: js_free(savedAddendum); michael@0: michael@0: markStateChange(cx); michael@0: } michael@0: michael@0: void michael@0: TypeObject::clearNewScriptAddendum(ExclusiveContext *cx) michael@0: { michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: /* michael@0: * Any definite properties we added due to analysis of the new script when michael@0: * the type object was created are now invalid: objects with the same type michael@0: * can be created by using 'new' on a different script or through some michael@0: * other mechanism (e.g. Object.create). Rather than clear out the definite michael@0: * bits on the object's properties, just mark such properties as having michael@0: * been deleted/reconfigured, which will have the same effect on JITs michael@0: * wanting to use the definite bits to optimize property accesses. michael@0: */ michael@0: for (unsigned i = 0; i < getPropertyCount(); i++) { michael@0: Property *prop = getProperty(i); michael@0: if (!prop) michael@0: continue; michael@0: if (prop->types.definiteProperty()) michael@0: prop->types.setNonDataProperty(cx); michael@0: } michael@0: michael@0: /* michael@0: * If we cleared the new script while in the middle of initializing an michael@0: * object, it will still have the new script's shape and reflect the no michael@0: * longer correct state of the object once its initialization is completed. michael@0: * We can't really detect the possibility of this statically, but the new michael@0: * script keeps track of where each property is initialized so we can walk michael@0: * the stack and fix up any such objects. michael@0: */ michael@0: if (cx->isJSContext()) { michael@0: Vector pcOffsets(cx); michael@0: for (ScriptFrameIter iter(cx->asJSContext()); !iter.done(); ++iter) { michael@0: pcOffsets.append(iter.script()->pcToOffset(iter.pc())); michael@0: if (!iter.isConstructing() || michael@0: iter.callee() != newScript()->fun || michael@0: !iter.thisv().isObject() || michael@0: iter.thisv().toObject().hasLazyType() || michael@0: iter.thisv().toObject().type() != this) michael@0: { michael@0: continue; michael@0: } michael@0: michael@0: // Found a matching frame. michael@0: RootedObject obj(cx, &iter.thisv().toObject()); michael@0: michael@0: // Whether all identified 'new' properties have been initialized. michael@0: bool finished = false; michael@0: michael@0: // If not finished, number of properties that have been added. michael@0: uint32_t numProperties = 0; michael@0: michael@0: // Whether the current SETPROP is within an inner frame which has michael@0: // finished entirely. michael@0: bool pastProperty = false; michael@0: michael@0: // Index in pcOffsets of the outermost frame. michael@0: int callDepth = pcOffsets.length() - 1; michael@0: michael@0: // Index in pcOffsets of the frame currently being checked for a SETPROP. michael@0: int setpropDepth = callDepth; michael@0: michael@0: for (TypeNewScript::Initializer *init = newScript()->initializerList;; init++) { michael@0: if (init->kind == TypeNewScript::Initializer::SETPROP) { michael@0: if (!pastProperty && pcOffsets[setpropDepth] < init->offset) { michael@0: // Have not yet reached this setprop. michael@0: break; michael@0: } michael@0: // This setprop has executed, reset state for the next one. michael@0: numProperties++; michael@0: pastProperty = false; michael@0: setpropDepth = callDepth; michael@0: } else if (init->kind == TypeNewScript::Initializer::SETPROP_FRAME) { michael@0: if (!pastProperty) { michael@0: if (pcOffsets[setpropDepth] < init->offset) { michael@0: // Have not yet reached this inner call. michael@0: break; michael@0: } else if (pcOffsets[setpropDepth] > init->offset) { michael@0: // Have advanced past this inner call. michael@0: pastProperty = true; michael@0: } else if (setpropDepth == 0) { michael@0: // Have reached this call but not yet in it. michael@0: break; michael@0: } else { michael@0: // Somewhere inside this inner call. michael@0: setpropDepth--; michael@0: } michael@0: } michael@0: } else { michael@0: JS_ASSERT(init->kind == TypeNewScript::Initializer::DONE); michael@0: finished = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!finished) michael@0: (void) JSObject::rollbackProperties(cx, obj, numProperties); michael@0: } michael@0: } else { michael@0: // Threads with an ExclusiveContext are not allowed to run scripts. michael@0: JS_ASSERT(!cx->perThreadData->activation()); michael@0: } michael@0: } michael@0: michael@0: void michael@0: TypeObject::maybeClearNewScriptAddendumOnOOM() michael@0: { michael@0: if (!isMarked()) michael@0: return; michael@0: michael@0: if (!addendum || addendum->kind != TypeObjectAddendum::NewScript) michael@0: return; michael@0: michael@0: for (unsigned i = 0; i < getPropertyCount(); i++) { michael@0: Property *prop = getProperty(i); michael@0: if (!prop) michael@0: continue; michael@0: if (prop->types.definiteProperty()) michael@0: prop->types.setNonDataPropertyIgnoringConstraints(); michael@0: } michael@0: michael@0: // This method is called during GC sweeping, so there is no write barrier michael@0: // that needs to be triggered. michael@0: js_free(addendum); michael@0: addendum.unsafeSet(nullptr); michael@0: } michael@0: michael@0: void michael@0: TypeObject::clearTypedObjectAddendum(ExclusiveContext *cx) michael@0: { michael@0: } michael@0: michael@0: void michael@0: TypeObject::print() michael@0: { michael@0: TaggedProto tagged(proto()); michael@0: fprintf(stderr, "%s : %s", michael@0: TypeObjectString(this), michael@0: tagged.isObject() ? TypeString(Type::ObjectType(tagged.toObject())) michael@0: : (tagged.isLazy() ? "(lazy)" : "(null)")); michael@0: michael@0: if (unknownProperties()) { michael@0: fprintf(stderr, " unknown"); michael@0: } else { michael@0: if (!hasAnyFlags(OBJECT_FLAG_SPARSE_INDEXES)) michael@0: fprintf(stderr, " dense"); michael@0: if (!hasAnyFlags(OBJECT_FLAG_NON_PACKED)) michael@0: fprintf(stderr, " packed"); michael@0: if (!hasAnyFlags(OBJECT_FLAG_LENGTH_OVERFLOW)) michael@0: fprintf(stderr, " noLengthOverflow"); michael@0: if (hasAnyFlags(OBJECT_FLAG_ITERATED)) michael@0: fprintf(stderr, " iterated"); michael@0: if (interpretedFunction) michael@0: fprintf(stderr, " ifun"); michael@0: } michael@0: michael@0: unsigned count = getPropertyCount(); michael@0: michael@0: if (count == 0) { michael@0: fprintf(stderr, " {}\n"); michael@0: return; michael@0: } michael@0: michael@0: fprintf(stderr, " {"); michael@0: michael@0: for (unsigned i = 0; i < count; i++) { michael@0: Property *prop = getProperty(i); michael@0: if (prop) { michael@0: fprintf(stderr, "\n %s:", TypeIdString(prop->id)); michael@0: prop->types.print(); michael@0: } michael@0: } michael@0: michael@0: fprintf(stderr, "\n}\n"); michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // Type Analysis michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: /* michael@0: * Persistent constraint clearing out newScript and definite properties from michael@0: * an object should a property on another object get a getter or setter. michael@0: */ michael@0: class TypeConstraintClearDefiniteGetterSetter : public TypeConstraint michael@0: { michael@0: public: michael@0: TypeObject *object; michael@0: michael@0: TypeConstraintClearDefiniteGetterSetter(TypeObject *object) michael@0: : object(object) michael@0: {} michael@0: michael@0: const char *kind() { return "clearDefiniteGetterSetter"; } michael@0: michael@0: void newPropertyState(JSContext *cx, TypeSet *source) michael@0: { michael@0: if (!object->hasNewScript()) michael@0: return; michael@0: /* michael@0: * Clear out the newScript shape and definite property information from michael@0: * an object if the source type set could be a setter or could be michael@0: * non-writable. michael@0: */ michael@0: if (!(object->flags() & OBJECT_FLAG_ADDENDUM_CLEARED) && michael@0: (source->nonDataProperty() || source->nonWritableProperty())) michael@0: { michael@0: object->clearAddendum(cx); michael@0: } michael@0: } michael@0: michael@0: void newType(JSContext *cx, TypeSet *source, Type type) {} michael@0: michael@0: bool sweep(TypeZone &zone, TypeConstraint **res) { michael@0: if (IsTypeObjectAboutToBeFinalized(&object)) michael@0: return false; michael@0: *res = zone.typeLifoAlloc.new_(object); michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: bool michael@0: types::AddClearDefiniteGetterSetterForPrototypeChain(JSContext *cx, TypeObject *type, HandleId id) michael@0: { michael@0: /* michael@0: * Ensure that if the properties named here could have a getter, setter or michael@0: * a permanent property in any transitive prototype, the definite michael@0: * properties get cleared from the type. michael@0: */ michael@0: RootedObject parent(cx, type->proto().toObjectOrNull()); michael@0: while (parent) { michael@0: TypeObject *parentObject = parent->getType(cx); michael@0: if (!parentObject || parentObject->unknownProperties()) michael@0: return false; michael@0: HeapTypeSet *parentTypes = parentObject->getProperty(cx, id); michael@0: if (!parentTypes || parentTypes->nonDataProperty() || parentTypes->nonWritableProperty()) michael@0: return false; michael@0: if (!parentTypes->addConstraint(cx, cx->typeLifoAlloc().new_(type))) michael@0: return false; michael@0: parent = parent->getProto(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Constraint which clears definite properties on an object should a type set michael@0: * contain any types other than a single object. michael@0: */ michael@0: class TypeConstraintClearDefiniteSingle : public TypeConstraint michael@0: { michael@0: public: michael@0: TypeObject *object; michael@0: michael@0: TypeConstraintClearDefiniteSingle(TypeObject *object) michael@0: : object(object) michael@0: {} michael@0: michael@0: const char *kind() { return "clearDefiniteSingle"; } michael@0: michael@0: void newType(JSContext *cx, TypeSet *source, Type type) { michael@0: if (object->flags() & OBJECT_FLAG_ADDENDUM_CLEARED) michael@0: return; michael@0: michael@0: if (source->baseFlags() || source->getObjectCount() > 1) michael@0: object->clearAddendum(cx); michael@0: } michael@0: michael@0: bool sweep(TypeZone &zone, TypeConstraint **res) { michael@0: if (IsTypeObjectAboutToBeFinalized(&object)) michael@0: return false; michael@0: *res = zone.typeLifoAlloc.new_(object); michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: bool michael@0: types::AddClearDefiniteFunctionUsesInScript(JSContext *cx, TypeObject *type, michael@0: JSScript *script, JSScript *calleeScript) michael@0: { michael@0: // Look for any uses of the specified calleeScript in type sets for michael@0: // |script|, and add constraints to ensure that if the type sets' contents michael@0: // change then the definite properties are cleared from the type. michael@0: // This ensures that the inlining performed when the definite properties michael@0: // analysis was done is stable. michael@0: michael@0: TypeObjectKey *calleeKey = Type::ObjectType(calleeScript->functionNonDelazifying()).objectKey(); michael@0: michael@0: unsigned count = TypeScript::NumTypeSets(script); michael@0: StackTypeSet *typeArray = script->types->typeArray(); michael@0: michael@0: for (unsigned i = 0; i < count; i++) { michael@0: StackTypeSet *types = &typeArray[i]; michael@0: if (!types->unknownObject() && types->getObjectCount() == 1) { michael@0: if (calleeKey != types->getObject(0)) { michael@0: // Also check if the object is the Function.call or michael@0: // Function.apply native. IonBuilder uses the presence of these michael@0: // functions during inlining. michael@0: JSObject *singleton = types->getSingleObject(0); michael@0: if (!singleton || !singleton->is()) michael@0: continue; michael@0: JSFunction *fun = &singleton->as(); michael@0: if (!fun->isNative()) michael@0: continue; michael@0: if (fun->native() != js_fun_call && fun->native() != js_fun_apply) michael@0: continue; michael@0: } michael@0: // This is a type set that might have been used when inlining michael@0: // |calleeScript| into |script|. michael@0: if (!types->addConstraint(cx, cx->typeLifoAlloc().new_(type))) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Either make the newScript information for type when it is constructed michael@0: * by the specified script, or regenerate the constraints for an existing michael@0: * newScript on the type after they were cleared by a GC. michael@0: */ michael@0: static void michael@0: CheckNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun) michael@0: { michael@0: JS_ASSERT(cx->compartment()->activeAnalysis); michael@0: michael@0: #ifdef JS_ION michael@0: if (type->unknownProperties()) michael@0: return; michael@0: michael@0: /* Strawman object to add properties to and watch for duplicates. */ michael@0: RootedObject baseobj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, gc::FINALIZE_OBJECT16)); michael@0: if (!baseobj) michael@0: return; michael@0: michael@0: Vector initializerList(cx); michael@0: michael@0: if (!jit::AnalyzeNewScriptProperties(cx, fun, type, baseobj, &initializerList) || michael@0: baseobj->slotSpan() == 0 || michael@0: !!(type->flags() & OBJECT_FLAG_ADDENDUM_CLEARED)) michael@0: { michael@0: if (type->hasNewScript()) michael@0: type->clearAddendum(cx); michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * If the type already has a new script, we are just regenerating the type michael@0: * constraints and don't need to make another TypeNewScript. Make sure that michael@0: * the properties added to baseobj match the type's definite properties. michael@0: */ michael@0: if (type->hasNewScript()) { michael@0: if (!type->matchDefiniteProperties(baseobj)) michael@0: type->clearAddendum(cx); michael@0: return; michael@0: } michael@0: JS_ASSERT(!type->hasNewScript()); michael@0: JS_ASSERT(!(type->flags() & OBJECT_FLAG_ADDENDUM_CLEARED)); michael@0: michael@0: gc::AllocKind kind = gc::GetGCObjectKind(baseobj->slotSpan()); michael@0: michael@0: /* We should not have overflowed the maximum number of fixed slots for an object. */ michael@0: JS_ASSERT(gc::GetGCKindSlots(kind) >= baseobj->slotSpan()); michael@0: michael@0: TypeNewScript::Initializer done(TypeNewScript::Initializer::DONE, 0); michael@0: michael@0: /* michael@0: * The base object may have been created with a different finalize kind michael@0: * than we will use for subsequent new objects. Generate an object with the michael@0: * appropriate final shape. michael@0: */ michael@0: Rooted rootedType(cx, type); michael@0: RootedShape shape(cx, baseobj->lastProperty()); michael@0: baseobj = NewReshapedObject(cx, rootedType, baseobj->getParent(), kind, shape, MaybeSingletonObject); michael@0: if (!baseobj || michael@0: !type->addDefiniteProperties(cx, baseobj) || michael@0: !initializerList.append(done)) michael@0: { michael@0: return; michael@0: } michael@0: michael@0: size_t numBytes = sizeof(TypeNewScript) michael@0: + (initializerList.length() * sizeof(TypeNewScript::Initializer)); michael@0: TypeNewScript *newScript = (TypeNewScript *) cx->calloc_(numBytes); michael@0: if (!newScript) michael@0: return; michael@0: michael@0: new (newScript) TypeNewScript(); michael@0: michael@0: type->setAddendum(newScript); michael@0: michael@0: newScript->fun = fun; michael@0: newScript->templateObject = baseobj; michael@0: michael@0: newScript->initializerList = (TypeNewScript::Initializer *) michael@0: ((char *) newScript + sizeof(TypeNewScript)); michael@0: PodCopy(newScript->initializerList, michael@0: initializerList.begin(), michael@0: initializerList.length()); michael@0: #endif // JS_ION michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // Interface functions michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: void michael@0: types::TypeMonitorCallSlow(JSContext *cx, JSObject *callee, const CallArgs &args, michael@0: bool constructing) michael@0: { michael@0: unsigned nargs = callee->as().nargs(); michael@0: JSScript *script = callee->as().nonLazyScript(); michael@0: michael@0: if (!constructing) michael@0: TypeScript::SetThis(cx, script, args.thisv()); michael@0: michael@0: /* michael@0: * Add constraints going up to the minimum of the actual and formal count. michael@0: * If there are more actuals than formals the later values can only be michael@0: * accessed through the arguments object, which is monitored. michael@0: */ michael@0: unsigned arg = 0; michael@0: for (; arg < args.length() && arg < nargs; arg++) michael@0: TypeScript::SetArgument(cx, script, arg, args[arg]); michael@0: michael@0: /* Watch for fewer actuals than formals to the call. */ michael@0: for (; arg < nargs; arg++) michael@0: TypeScript::SetArgument(cx, script, arg, UndefinedValue()); michael@0: } michael@0: michael@0: static inline bool michael@0: IsAboutToBeFinalized(TypeObjectKey *key) michael@0: { michael@0: /* Mask out the low bit indicating whether this is a type or JS object. */ michael@0: gc::Cell *tmp = reinterpret_cast(uintptr_t(key) & ~1); michael@0: bool isAboutToBeFinalized = IsCellAboutToBeFinalized(&tmp); michael@0: JS_ASSERT(tmp == reinterpret_cast(uintptr_t(key) & ~1)); michael@0: return isAboutToBeFinalized; michael@0: } michael@0: michael@0: void michael@0: types::FillBytecodeTypeMap(JSScript *script, uint32_t *bytecodeMap) michael@0: { michael@0: uint32_t added = 0; michael@0: for (jsbytecode *pc = script->code(); pc < script->codeEnd(); pc += GetBytecodeLength(pc)) { michael@0: JSOp op = JSOp(*pc); michael@0: if (js_CodeSpec[op].format & JOF_TYPESET) { michael@0: bytecodeMap[added++] = script->pcToOffset(pc); michael@0: if (added == script->nTypeSets()) michael@0: break; michael@0: } michael@0: } michael@0: JS_ASSERT(added == script->nTypeSets()); michael@0: } michael@0: michael@0: void michael@0: types::TypeMonitorResult(JSContext *cx, JSScript *script, jsbytecode *pc, const js::Value &rval) michael@0: { michael@0: /* Allow the non-TYPESET scenario to simplify stubs used in compound opcodes. */ michael@0: if (!(js_CodeSpec[*pc].format & JOF_TYPESET)) michael@0: return; michael@0: michael@0: if (!script->hasBaselineScript()) michael@0: return; michael@0: michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: Type type = GetValueType(rval); michael@0: StackTypeSet *types = TypeScript::BytecodeTypes(script, pc); michael@0: if (types->hasType(type)) michael@0: return; michael@0: michael@0: InferSpew(ISpewOps, "bytecodeType: #%u:%05u: %s", michael@0: script->id(), script->pcToOffset(pc), TypeString(type)); michael@0: types->addType(cx, type); michael@0: } michael@0: michael@0: bool michael@0: types::UseNewTypeForClone(JSFunction *fun) michael@0: { michael@0: if (!fun->isInterpreted()) michael@0: return false; michael@0: michael@0: if (fun->hasScript() && fun->nonLazyScript()->shouldCloneAtCallsite()) michael@0: return true; michael@0: michael@0: if (fun->isArrow()) michael@0: return false; michael@0: michael@0: if (fun->hasSingletonType()) michael@0: return false; michael@0: michael@0: /* michael@0: * When a function is being used as a wrapper for another function, it michael@0: * improves precision greatly to distinguish between different instances of michael@0: * the wrapper; otherwise we will conflate much of the information about michael@0: * the wrapped functions. michael@0: * michael@0: * An important example is the Class.create function at the core of the michael@0: * Prototype.js library, which looks like: michael@0: * michael@0: * var Class = { michael@0: * create: function() { michael@0: * return function() { michael@0: * this.initialize.apply(this, arguments); michael@0: * } michael@0: * } michael@0: * }; michael@0: * michael@0: * Each instance of the innermost function will have a different wrapped michael@0: * initialize method. We capture this, along with similar cases, by looking michael@0: * for short scripts which use both .apply and arguments. For such scripts, michael@0: * whenever creating a new instance of the function we both give that michael@0: * instance a singleton type and clone the underlying script. michael@0: */ michael@0: michael@0: uint32_t begin, end; michael@0: if (fun->hasScript()) { michael@0: if (!fun->nonLazyScript()->usesArgumentsAndApply()) michael@0: return false; michael@0: begin = fun->nonLazyScript()->sourceStart(); michael@0: end = fun->nonLazyScript()->sourceEnd(); michael@0: } else { michael@0: if (!fun->lazyScript()->usesArgumentsAndApply()) michael@0: return false; michael@0: begin = fun->lazyScript()->begin(); michael@0: end = fun->lazyScript()->end(); michael@0: } michael@0: michael@0: return end - begin <= 100; michael@0: } michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // TypeScript michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: bool michael@0: JSScript::makeTypes(JSContext *cx) michael@0: { michael@0: JS_ASSERT(!types); michael@0: michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: unsigned count = TypeScript::NumTypeSets(this); michael@0: michael@0: TypeScript *typeScript = (TypeScript *) michael@0: cx->calloc_(TypeScript::SizeIncludingTypeArray(count)); michael@0: if (!typeScript) michael@0: return false; michael@0: michael@0: new(typeScript) TypeScript(); michael@0: michael@0: TypeSet *typeArray = typeScript->typeArray(); michael@0: michael@0: for (unsigned i = 0; i < count; i++) michael@0: new (&typeArray[i]) StackTypeSet(); michael@0: michael@0: types = typeScript; michael@0: michael@0: #ifdef DEBUG michael@0: for (unsigned i = 0; i < nTypeSets(); i++) { michael@0: InferSpew(ISpewOps, "typeSet: %sT%p%s bytecode%u #%u", michael@0: InferSpewColor(&typeArray[i]), &typeArray[i], InferSpewColorReset(), michael@0: i, id()); michael@0: } michael@0: TypeSet *thisTypes = TypeScript::ThisTypes(this); michael@0: InferSpew(ISpewOps, "typeSet: %sT%p%s this #%u", michael@0: InferSpewColor(thisTypes), thisTypes, InferSpewColorReset(), michael@0: id()); michael@0: unsigned nargs = functionNonDelazifying() ? functionNonDelazifying()->nargs() : 0; michael@0: for (unsigned i = 0; i < nargs; i++) { michael@0: TypeSet *types = TypeScript::ArgTypes(this, i); michael@0: InferSpew(ISpewOps, "typeSet: %sT%p%s arg%u #%u", michael@0: InferSpewColor(types), types, InferSpewColorReset(), michael@0: i, id()); michael@0: } michael@0: #endif michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSFunction::setTypeForScriptedFunction(ExclusiveContext *cx, HandleFunction fun, michael@0: bool singleton /* = false */) michael@0: { michael@0: if (singleton) { michael@0: if (!setSingletonType(cx, fun)) michael@0: return false; michael@0: } else { michael@0: RootedObject funProto(cx, fun->getProto()); michael@0: TypeObject *type = michael@0: cx->compartment()->types.newTypeObject(cx, &JSFunction::class_, funProto); michael@0: if (!type) michael@0: return false; michael@0: michael@0: fun->setType(type); michael@0: type->interpretedFunction = fun; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // JSObject michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: bool michael@0: JSObject::shouldSplicePrototype(JSContext *cx) michael@0: { michael@0: /* michael@0: * During bootstrapping, if inference is enabled we need to make sure not michael@0: * to splice a new prototype in for Function.prototype or the global michael@0: * object if their __proto__ had previously been set to null, as this michael@0: * will change the prototype for all other objects with the same type. michael@0: */ michael@0: if (getProto() != nullptr) michael@0: return false; michael@0: return hasSingletonType(); michael@0: } michael@0: michael@0: bool michael@0: JSObject::splicePrototype(JSContext *cx, const Class *clasp, Handle proto) michael@0: { michael@0: JS_ASSERT(cx->compartment() == compartment()); michael@0: michael@0: RootedObject self(cx, this); michael@0: michael@0: /* michael@0: * For singleton types representing only a single JSObject, the proto michael@0: * can be rearranged as needed without destroying type information for michael@0: * the old or new types. michael@0: */ michael@0: JS_ASSERT(self->hasSingletonType()); michael@0: michael@0: /* Inner objects may not appear on prototype chains. */ michael@0: JS_ASSERT_IF(proto.isObject(), !proto.toObject()->getClass()->ext.outerObject); michael@0: michael@0: /* michael@0: * Force type instantiation when splicing lazy types. This may fail, michael@0: * in which case inference will be disabled for the compartment. michael@0: */ michael@0: Rooted type(cx, self->getType(cx)); michael@0: if (!type) michael@0: return false; michael@0: Rooted protoType(cx, nullptr); michael@0: if (proto.isObject()) { michael@0: protoType = proto.toObject()->getType(cx); michael@0: if (!protoType) michael@0: return false; michael@0: } michael@0: michael@0: type->setClasp(clasp); michael@0: type->setProto(cx, proto); michael@0: return true; michael@0: } michael@0: michael@0: /* static */ TypeObject * michael@0: JSObject::makeLazyType(JSContext *cx, HandleObject obj) michael@0: { michael@0: JS_ASSERT(obj->hasLazyType()); michael@0: JS_ASSERT(cx->compartment() == obj->compartment()); michael@0: michael@0: /* De-lazification of functions can GC, so we need to do it up here. */ michael@0: if (obj->is() && obj->as().isInterpretedLazy()) { michael@0: RootedFunction fun(cx, &obj->as()); michael@0: if (!fun->getOrCreateScript(cx)) michael@0: return nullptr; michael@0: } michael@0: michael@0: // Find flags which need to be specified immediately on the object. michael@0: // Don't track whether singletons are packed. michael@0: TypeObjectFlags initialFlags = OBJECT_FLAG_NON_PACKED; michael@0: michael@0: if (obj->lastProperty()->hasObjectFlag(BaseShape::ITERATED_SINGLETON)) michael@0: initialFlags |= OBJECT_FLAG_ITERATED; michael@0: michael@0: if (obj->isIndexed()) michael@0: initialFlags |= OBJECT_FLAG_SPARSE_INDEXES; michael@0: michael@0: if (obj->is() && obj->as().length() > INT32_MAX) michael@0: initialFlags |= OBJECT_FLAG_LENGTH_OVERFLOW; michael@0: michael@0: Rooted proto(cx, obj->getTaggedProto()); michael@0: TypeObject *type = cx->compartment()->types.newTypeObject(cx, obj->getClass(), proto, initialFlags); michael@0: if (!type) michael@0: return nullptr; michael@0: michael@0: AutoEnterAnalysis enter(cx); michael@0: michael@0: /* Fill in the type according to the state of this object. */ michael@0: michael@0: type->initSingleton(obj); michael@0: michael@0: if (obj->is() && obj->as().isInterpreted()) michael@0: type->interpretedFunction = &obj->as(); michael@0: michael@0: obj->type_ = type; michael@0: michael@0: return type; michael@0: } michael@0: michael@0: /* static */ inline HashNumber michael@0: TypeObjectWithNewScriptEntry::hash(const Lookup &lookup) michael@0: { michael@0: return PointerHasher::hash(lookup.hashProto.raw()) ^ michael@0: PointerHasher::hash(lookup.clasp) ^ michael@0: PointerHasher::hash(lookup.newFunction); michael@0: } michael@0: michael@0: /* static */ inline bool michael@0: TypeObjectWithNewScriptEntry::match(const TypeObjectWithNewScriptEntry &key, const Lookup &lookup) michael@0: { michael@0: return key.object->proto() == lookup.matchProto && michael@0: key.object->clasp() == lookup.clasp && michael@0: key.newFunction == lookup.newFunction; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: bool michael@0: JSObject::hasNewType(const Class *clasp, TypeObject *type) michael@0: { michael@0: TypeObjectWithNewScriptSet &table = compartment()->newTypeObjects; michael@0: michael@0: if (!table.initialized()) michael@0: return false; michael@0: michael@0: TypeObjectWithNewScriptSet::Ptr p = table.lookup(TypeObjectWithNewScriptSet::Lookup(clasp, this, nullptr)); michael@0: return p && p->object == type; michael@0: } michael@0: #endif /* DEBUG */ michael@0: michael@0: /* static */ bool michael@0: JSObject::setNewTypeUnknown(JSContext *cx, const Class *clasp, HandleObject obj) michael@0: { michael@0: if (!obj->setFlag(cx, js::BaseShape::NEW_TYPE_UNKNOWN)) michael@0: return false; michael@0: michael@0: /* michael@0: * If the object already has a new type, mark that type as unknown. It will michael@0: * not have the SETS_MARKED_UNKNOWN bit set, so may require a type set michael@0: * crawl if prototypes of the object change dynamically in the future. michael@0: */ michael@0: TypeObjectWithNewScriptSet &table = cx->compartment()->newTypeObjects; michael@0: if (table.initialized()) { michael@0: if (TypeObjectWithNewScriptSet::Ptr p = table.lookup(TypeObjectWithNewScriptSet::Lookup(clasp, obj.get(), nullptr))) michael@0: MarkTypeObjectUnknownProperties(cx, p->object); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: #ifdef JSGC_GENERATIONAL michael@0: /* michael@0: * This class is used to add a post barrier on the newTypeObjects set, as the michael@0: * key is calculated from a prototype object which may be moved by generational michael@0: * GC. michael@0: */ michael@0: class NewTypeObjectsSetRef : public BufferableRef michael@0: { michael@0: TypeObjectWithNewScriptSet *set; michael@0: const Class *clasp; michael@0: JSObject *proto; michael@0: JSFunction *newFunction; michael@0: michael@0: public: michael@0: NewTypeObjectsSetRef(TypeObjectWithNewScriptSet *s, const Class *clasp, JSObject *proto, JSFunction *newFunction) michael@0: : set(s), clasp(clasp), proto(proto), newFunction(newFunction) michael@0: {} michael@0: michael@0: void mark(JSTracer *trc) { michael@0: JSObject *prior = proto; michael@0: trc->setTracingLocation(&*prior); michael@0: Mark(trc, &proto, "newTypeObjects set prototype"); michael@0: if (prior == proto) michael@0: return; michael@0: michael@0: TypeObjectWithNewScriptSet::Ptr p = set->lookup(TypeObjectWithNewScriptSet::Lookup(clasp, prior, proto, newFunction)); michael@0: JS_ASSERT(p); // newTypeObjects set must still contain original entry. michael@0: michael@0: set->rekeyAs(TypeObjectWithNewScriptSet::Lookup(clasp, prior, proto, newFunction), michael@0: TypeObjectWithNewScriptSet::Lookup(clasp, proto, newFunction), *p); michael@0: } michael@0: }; michael@0: #endif michael@0: michael@0: TypeObject * michael@0: ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto, JSFunction *fun) michael@0: { michael@0: JS_ASSERT_IF(fun, proto.isObject()); michael@0: JS_ASSERT_IF(proto.isObject(), isInsideCurrentCompartment(proto.toObject())); michael@0: michael@0: TypeObjectWithNewScriptSet &newTypeObjects = compartment()->newTypeObjects; michael@0: michael@0: if (!newTypeObjects.initialized() && !newTypeObjects.init()) michael@0: return nullptr; michael@0: michael@0: // Canonicalize new functions to use the original one associated with its script. michael@0: if (fun) { michael@0: if (fun->hasScript()) michael@0: fun = fun->nonLazyScript()->functionNonDelazifying(); michael@0: else if (fun->isInterpretedLazy() && !fun->isSelfHostedBuiltin()) michael@0: fun = fun->lazyScript()->functionNonDelazifying(); michael@0: else michael@0: fun = nullptr; michael@0: } michael@0: michael@0: TypeObjectWithNewScriptSet::AddPtr p = michael@0: newTypeObjects.lookupForAdd(TypeObjectWithNewScriptSet::Lookup(clasp, proto, fun)); michael@0: if (p) { michael@0: TypeObject *type = p->object; michael@0: JS_ASSERT(type->clasp() == clasp); michael@0: JS_ASSERT(type->proto() == proto); michael@0: JS_ASSERT_IF(type->hasNewScript(), type->newScript()->fun == fun); michael@0: return type; michael@0: } michael@0: michael@0: AutoEnterAnalysis enter(this); michael@0: michael@0: if (proto.isObject() && !proto.toObject()->setDelegate(this)) michael@0: return nullptr; michael@0: michael@0: TypeObjectFlags initialFlags = 0; michael@0: if (!proto.isObject() || proto.toObject()->lastProperty()->hasObjectFlag(BaseShape::NEW_TYPE_UNKNOWN)) { michael@0: // The new type is not present in any type sets, so mark the object as michael@0: // unknown in all type sets it appears in. This allows the prototype of michael@0: // such objects to mutate freely without triggering an expensive walk of michael@0: // the compartment's type sets. (While scripts normally don't mutate michael@0: // __proto__, the browser will for proxies and such, and we need to michael@0: // accommodate this behavior). michael@0: initialFlags = OBJECT_FLAG_UNKNOWN_MASK | OBJECT_FLAG_SETS_MARKED_UNKNOWN; michael@0: } michael@0: michael@0: Rooted protoRoot(this, proto); michael@0: TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot, initialFlags); michael@0: if (!type) michael@0: return nullptr; michael@0: michael@0: if (!newTypeObjects.add(p, TypeObjectWithNewScriptEntry(type, fun))) michael@0: return nullptr; michael@0: michael@0: #ifdef JSGC_GENERATIONAL michael@0: if (proto.isObject() && hasNursery() && nursery().isInside(proto.toObject())) { michael@0: asJSContext()->runtime()->gcStoreBuffer.putGeneric( michael@0: NewTypeObjectsSetRef(&newTypeObjects, clasp, proto.toObject(), fun)); michael@0: } michael@0: #endif michael@0: michael@0: if (proto.isObject()) { michael@0: RootedObject obj(this, proto.toObject()); michael@0: michael@0: if (fun) michael@0: CheckNewScriptProperties(asJSContext(), type, fun); michael@0: michael@0: /* michael@0: * Some builtin objects have slotful native properties baked in at michael@0: * creation via the Shape::{insert,get}initialShape mechanism. Since michael@0: * these properties are never explicitly defined on new objects, update michael@0: * the type information for them here. michael@0: */ michael@0: michael@0: if (obj->is()) { michael@0: AddTypePropertyId(this, type, NameToId(names().source), Type::StringType()); michael@0: AddTypePropertyId(this, type, NameToId(names().global), Type::BooleanType()); michael@0: AddTypePropertyId(this, type, NameToId(names().ignoreCase), Type::BooleanType()); michael@0: AddTypePropertyId(this, type, NameToId(names().multiline), Type::BooleanType()); michael@0: AddTypePropertyId(this, type, NameToId(names().sticky), Type::BooleanType()); michael@0: AddTypePropertyId(this, type, NameToId(names().lastIndex), Type::Int32Type()); michael@0: } michael@0: michael@0: if (obj->is()) michael@0: AddTypePropertyId(this, type, NameToId(names().length), Type::Int32Type()); michael@0: michael@0: if (obj->is()) { michael@0: AddTypePropertyId(this, type, NameToId(names().fileName), Type::StringType()); michael@0: AddTypePropertyId(this, type, NameToId(names().lineNumber), Type::Int32Type()); michael@0: AddTypePropertyId(this, type, NameToId(names().columnNumber), Type::Int32Type()); michael@0: AddTypePropertyId(this, type, NameToId(names().stack), Type::StringType()); michael@0: } michael@0: } michael@0: michael@0: return type; michael@0: } michael@0: michael@0: #if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL) michael@0: void michael@0: JSCompartment::checkNewTypeObjectTableAfterMovingGC() michael@0: { michael@0: /* michael@0: * Assert that the postbarriers have worked and that nothing is left in michael@0: * newTypeObjects that points into the nursery, and that the hash table michael@0: * entries are discoverable. michael@0: */ michael@0: JS::shadow::Runtime *rt = JS::shadow::Runtime::asShadowRuntime(runtimeFromMainThread()); michael@0: for (TypeObjectWithNewScriptSet::Enum e(newTypeObjects); !e.empty(); e.popFront()) { michael@0: TypeObjectWithNewScriptEntry entry = e.front(); michael@0: JS_ASSERT(!IsInsideNursery(rt, entry.newFunction)); michael@0: TaggedProto proto = entry.object->proto(); michael@0: JS_ASSERT_IF(proto.isObject(), !IsInsideNursery(rt, proto.toObject())); michael@0: TypeObjectWithNewScriptEntry::Lookup michael@0: lookup(entry.object->clasp(), proto, entry.newFunction); michael@0: TypeObjectWithNewScriptSet::Ptr ptr = newTypeObjects.lookup(lookup); michael@0: JS_ASSERT(ptr.found() && &*ptr == &e.front()); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: TypeObject * michael@0: ExclusiveContext::getSingletonType(const Class *clasp, TaggedProto proto) michael@0: { michael@0: JS_ASSERT_IF(proto.isObject(), compartment() == proto.toObject()->compartment()); michael@0: michael@0: AutoEnterAnalysis enter(this); michael@0: michael@0: TypeObjectWithNewScriptSet &table = compartment()->lazyTypeObjects; michael@0: michael@0: if (!table.initialized() && !table.init()) michael@0: return nullptr; michael@0: michael@0: TypeObjectWithNewScriptSet::AddPtr p = table.lookupForAdd(TypeObjectWithNewScriptSet::Lookup(clasp, proto, nullptr)); michael@0: if (p) { michael@0: TypeObject *type = p->object; michael@0: JS_ASSERT(type->lazy()); michael@0: michael@0: return type; michael@0: } michael@0: michael@0: Rooted protoRoot(this, proto); michael@0: TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot); michael@0: if (!type) michael@0: return nullptr; michael@0: michael@0: if (!table.add(p, TypeObjectWithNewScriptEntry(type, nullptr))) michael@0: return nullptr; michael@0: michael@0: type->initSingleton((JSObject *) TypeObject::LAZY_SINGLETON); michael@0: MOZ_ASSERT(type->singleton(), "created type must be a proper singleton"); michael@0: michael@0: return type; michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // Tracing michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: void michael@0: ConstraintTypeSet::sweep(Zone *zone, bool *oom) michael@0: { michael@0: /* michael@0: * Purge references to type objects that are no longer live. Type sets hold michael@0: * only weak references. For type sets containing more than one object, michael@0: * live entries in the object hash need to be copied to the zone's michael@0: * new arena. michael@0: */ michael@0: unsigned objectCount = baseObjectCount(); michael@0: if (objectCount >= 2) { michael@0: unsigned oldCapacity = HashSetCapacity(objectCount); michael@0: TypeObjectKey **oldArray = objectSet; michael@0: michael@0: clearObjects(); michael@0: objectCount = 0; michael@0: for (unsigned i = 0; i < oldCapacity; i++) { michael@0: TypeObjectKey *object = oldArray[i]; michael@0: if (object && !IsAboutToBeFinalized(object)) { michael@0: TypeObjectKey **pentry = michael@0: HashSetInsert michael@0: (zone->types.typeLifoAlloc, objectSet, objectCount, object); michael@0: if (pentry) { michael@0: *pentry = object; michael@0: } else { michael@0: *oom = true; michael@0: flags |= TYPE_FLAG_ANYOBJECT; michael@0: clearObjects(); michael@0: objectCount = 0; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: setBaseObjectCount(objectCount); michael@0: } else if (objectCount == 1) { michael@0: TypeObjectKey *object = (TypeObjectKey *) objectSet; michael@0: if (IsAboutToBeFinalized(object)) { michael@0: objectSet = nullptr; michael@0: setBaseObjectCount(0); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Type constraints only hold weak references. Copy constraints referring michael@0: * to data that is still live into the zone's new arena. michael@0: */ michael@0: TypeConstraint *constraint = constraintList; michael@0: constraintList = nullptr; michael@0: while (constraint) { michael@0: TypeConstraint *copy; michael@0: if (constraint->sweep(zone->types, ©)) { michael@0: if (copy) { michael@0: copy->next = constraintList; michael@0: constraintList = copy; michael@0: } else { michael@0: *oom = true; michael@0: } michael@0: } michael@0: constraint = constraint->next; michael@0: } michael@0: } michael@0: michael@0: inline void michael@0: TypeObject::clearProperties() michael@0: { michael@0: setBasePropertyCount(0); michael@0: propertySet = nullptr; michael@0: } michael@0: michael@0: /* michael@0: * Before sweeping the arenas themselves, scan all type objects in a michael@0: * compartment to fixup weak references: property type sets referencing dead michael@0: * JS and type objects, and singleton JS objects whose type is not referenced michael@0: * elsewhere. This also releases memory associated with dead type objects, michael@0: * so that type objects do not need later finalization. michael@0: */ michael@0: inline void michael@0: TypeObject::sweep(FreeOp *fop, bool *oom) michael@0: { michael@0: if (!isMarked()) { michael@0: if (addendum) michael@0: fop->free_(addendum); michael@0: return; michael@0: } michael@0: michael@0: LifoAlloc &typeLifoAlloc = zone()->types.typeLifoAlloc; michael@0: michael@0: /* michael@0: * Properties were allocated from the old arena, and need to be copied over michael@0: * to the new one. michael@0: */ michael@0: unsigned propertyCount = basePropertyCount(); michael@0: if (propertyCount >= 2) { michael@0: unsigned oldCapacity = HashSetCapacity(propertyCount); michael@0: Property **oldArray = propertySet; michael@0: michael@0: clearProperties(); michael@0: propertyCount = 0; michael@0: for (unsigned i = 0; i < oldCapacity; i++) { michael@0: Property *prop = oldArray[i]; michael@0: if (prop) { michael@0: if (singleton() && !prop->types.constraintList && !zone()->isPreservingCode()) { michael@0: /* michael@0: * Don't copy over properties of singleton objects when their michael@0: * presence will not be required by jitcode or type constraints michael@0: * (i.e. for the definite properties analysis). The contents of michael@0: * these type sets will be regenerated as necessary. michael@0: */ michael@0: continue; michael@0: } michael@0: michael@0: Property *newProp = typeLifoAlloc.new_(*prop); michael@0: if (newProp) { michael@0: Property **pentry = michael@0: HashSetInsert michael@0: (typeLifoAlloc, propertySet, propertyCount, prop->id); michael@0: if (pentry) { michael@0: *pentry = newProp; michael@0: newProp->types.sweep(zone(), oom); michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: *oom = true; michael@0: addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); michael@0: clearProperties(); michael@0: return; michael@0: } michael@0: } michael@0: setBasePropertyCount(propertyCount); michael@0: } else if (propertyCount == 1) { michael@0: Property *prop = (Property *) propertySet; michael@0: if (singleton() && !prop->types.constraintList && !zone()->isPreservingCode()) { michael@0: // Skip, as above. michael@0: clearProperties(); michael@0: } else { michael@0: Property *newProp = typeLifoAlloc.new_(*prop); michael@0: if (newProp) { michael@0: propertySet = (Property **) newProp; michael@0: newProp->types.sweep(zone(), oom); michael@0: } else { michael@0: *oom = true; michael@0: addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); michael@0: clearProperties(); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: TypeCompartment::clearTables() michael@0: { michael@0: if (allocationSiteTable && allocationSiteTable->initialized()) michael@0: allocationSiteTable->clear(); michael@0: if (arrayTypeTable && arrayTypeTable->initialized()) michael@0: arrayTypeTable->clear(); michael@0: if (objectTypeTable && objectTypeTable->initialized()) michael@0: objectTypeTable->clear(); michael@0: } michael@0: michael@0: void michael@0: TypeCompartment::sweep(FreeOp *fop) michael@0: { michael@0: /* michael@0: * Iterate through the array/object type tables and remove all entries michael@0: * referencing collected data. These tables only hold weak references. michael@0: */ michael@0: michael@0: if (arrayTypeTable) { michael@0: for (ArrayTypeTable::Enum e(*arrayTypeTable); !e.empty(); e.popFront()) { michael@0: const ArrayTableKey &key = e.front().key(); michael@0: JS_ASSERT(key.type.isUnknown() || !key.type.isSingleObject()); michael@0: michael@0: bool remove = false; michael@0: TypeObject *typeObject = nullptr; michael@0: if (!key.type.isUnknown() && key.type.isTypeObject()) { michael@0: typeObject = key.type.typeObject(); michael@0: if (IsTypeObjectAboutToBeFinalized(&typeObject)) michael@0: remove = true; michael@0: } michael@0: if (IsTypeObjectAboutToBeFinalized(e.front().value().unsafeGet())) michael@0: remove = true; michael@0: michael@0: if (remove) { michael@0: e.removeFront(); michael@0: } else if (typeObject && typeObject != key.type.typeObject()) { michael@0: ArrayTableKey newKey; michael@0: newKey.type = Type::ObjectType(typeObject); michael@0: newKey.proto = key.proto; michael@0: e.rekeyFront(newKey); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (objectTypeTable) { michael@0: for (ObjectTypeTable::Enum e(*objectTypeTable); !e.empty(); e.popFront()) { michael@0: const ObjectTableKey &key = e.front().key(); michael@0: ObjectTableEntry &entry = e.front().value(); michael@0: michael@0: bool remove = false; michael@0: if (IsTypeObjectAboutToBeFinalized(entry.object.unsafeGet())) michael@0: remove = true; michael@0: if (IsShapeAboutToBeFinalized(entry.shape.unsafeGet())) michael@0: remove = true; michael@0: for (unsigned i = 0; !remove && i < key.nproperties; i++) { michael@0: if (JSID_IS_STRING(key.properties[i])) { michael@0: JSString *str = JSID_TO_STRING(key.properties[i]); michael@0: if (IsStringAboutToBeFinalized(&str)) michael@0: remove = true; michael@0: JS_ASSERT(AtomToId((JSAtom *)str) == key.properties[i]); michael@0: } michael@0: JS_ASSERT(!entry.types[i].isSingleObject()); michael@0: TypeObject *typeObject = nullptr; michael@0: if (entry.types[i].isTypeObject()) { michael@0: typeObject = entry.types[i].typeObject(); michael@0: if (IsTypeObjectAboutToBeFinalized(&typeObject)) michael@0: remove = true; michael@0: else if (typeObject != entry.types[i].typeObject()) michael@0: entry.types[i] = Type::ObjectType(typeObject); michael@0: } michael@0: } michael@0: michael@0: if (remove) { michael@0: js_free(key.properties); michael@0: js_free(entry.types); michael@0: e.removeFront(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (allocationSiteTable) { michael@0: for (AllocationSiteTable::Enum e(*allocationSiteTable); !e.empty(); e.popFront()) { michael@0: AllocationSiteKey key = e.front().key(); michael@0: bool keyDying = IsScriptAboutToBeFinalized(&key.script); michael@0: bool valDying = IsTypeObjectAboutToBeFinalized(e.front().value().unsafeGet()); michael@0: if (keyDying || valDying) michael@0: e.removeFront(); michael@0: else if (key.script != e.front().key().script) michael@0: e.rekeyFront(key); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: JSCompartment::sweepNewTypeObjectTable(TypeObjectWithNewScriptSet &table) michael@0: { michael@0: gcstats::AutoPhase ap(runtimeFromMainThread()->gcStats, michael@0: gcstats::PHASE_SWEEP_TABLES_TYPE_OBJECT); michael@0: michael@0: JS_ASSERT(zone()->isGCSweeping()); michael@0: if (table.initialized()) { michael@0: for (TypeObjectWithNewScriptSet::Enum e(table); !e.empty(); e.popFront()) { michael@0: TypeObjectWithNewScriptEntry entry = e.front(); michael@0: if (IsTypeObjectAboutToBeFinalized(entry.object.unsafeGet())) { michael@0: e.removeFront(); michael@0: } else if (entry.newFunction && IsObjectAboutToBeFinalized(&entry.newFunction)) { michael@0: e.removeFront(); michael@0: } else if (entry.object != e.front().object) { michael@0: TypeObjectWithNewScriptSet::Lookup lookup(entry.object->clasp(), michael@0: entry.object->proto(), michael@0: entry.newFunction); michael@0: e.rekeyFront(lookup, entry); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: TypeCompartment::~TypeCompartment() michael@0: { michael@0: js_delete(arrayTypeTable); michael@0: js_delete(objectTypeTable); michael@0: js_delete(allocationSiteTable); michael@0: } michael@0: michael@0: /* static */ void michael@0: TypeScript::Sweep(FreeOp *fop, JSScript *script, bool *oom) michael@0: { michael@0: JSCompartment *compartment = script->compartment(); michael@0: JS_ASSERT(compartment->zone()->isGCSweeping()); michael@0: michael@0: unsigned num = NumTypeSets(script); michael@0: StackTypeSet *typeArray = script->types->typeArray(); michael@0: michael@0: /* Remove constraints and references to dead objects from the persistent type sets. */ michael@0: for (unsigned i = 0; i < num; i++) michael@0: typeArray[i].sweep(compartment->zone(), oom); michael@0: } michael@0: michael@0: void michael@0: TypeScript::destroy() michael@0: { michael@0: js_free(this); michael@0: } michael@0: michael@0: void michael@0: Zone::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, michael@0: size_t *typePool, michael@0: size_t *baselineStubsOptimized) michael@0: { michael@0: *typePool += types.typeLifoAlloc.sizeOfExcludingThis(mallocSizeOf); michael@0: #ifdef JS_ION michael@0: if (jitZone()) { michael@0: *baselineStubsOptimized += michael@0: jitZone()->optimizedStubSpace()->sizeOfExcludingThis(mallocSizeOf); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: TypeCompartment::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, michael@0: size_t *allocationSiteTables, michael@0: size_t *arrayTypeTables, michael@0: size_t *objectTypeTables) michael@0: { michael@0: if (allocationSiteTable) michael@0: *allocationSiteTables += allocationSiteTable->sizeOfIncludingThis(mallocSizeOf); michael@0: michael@0: if (arrayTypeTable) michael@0: *arrayTypeTables += arrayTypeTable->sizeOfIncludingThis(mallocSizeOf); michael@0: michael@0: if (objectTypeTable) { michael@0: *objectTypeTables += objectTypeTable->sizeOfIncludingThis(mallocSizeOf); michael@0: michael@0: for (ObjectTypeTable::Enum e(*objectTypeTable); michael@0: !e.empty(); michael@0: e.popFront()) michael@0: { michael@0: const ObjectTableKey &key = e.front().key(); michael@0: const ObjectTableEntry &value = e.front().value(); michael@0: michael@0: /* key.ids and values.types have the same length. */ michael@0: *objectTypeTables += mallocSizeOf(key.properties) + mallocSizeOf(value.types); michael@0: } michael@0: } michael@0: } michael@0: michael@0: size_t michael@0: TypeObject::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: return mallocSizeOf(addendum); michael@0: } michael@0: michael@0: TypeZone::TypeZone(Zone *zone) michael@0: : zone_(zone), michael@0: typeLifoAlloc(TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), michael@0: compilerOutputs(nullptr), michael@0: pendingRecompiles(nullptr) michael@0: { michael@0: } michael@0: michael@0: TypeZone::~TypeZone() michael@0: { michael@0: js_delete(compilerOutputs); michael@0: js_delete(pendingRecompiles); michael@0: } michael@0: michael@0: void michael@0: TypeZone::sweep(FreeOp *fop, bool releaseTypes, bool *oom) michael@0: { michael@0: JS_ASSERT(zone()->isGCSweeping()); michael@0: michael@0: JSRuntime *rt = fop->runtime(); michael@0: michael@0: /* michael@0: * Clear the analysis pool, but don't release its data yet. While michael@0: * sweeping types any live data will be allocated into the pool. michael@0: */ michael@0: LifoAlloc oldAlloc(typeLifoAlloc.defaultChunkSize()); michael@0: oldAlloc.steal(&typeLifoAlloc); michael@0: michael@0: /* Sweep and find compressed indexes for each compiler output. */ michael@0: size_t newCompilerOutputCount = 0; michael@0: michael@0: #ifdef JS_ION michael@0: if (compilerOutputs) { michael@0: for (size_t i = 0; i < compilerOutputs->length(); i++) { michael@0: CompilerOutput &output = (*compilerOutputs)[i]; michael@0: if (output.isValid()) { michael@0: JSScript *script = output.script(); michael@0: if (IsScriptAboutToBeFinalized(&script)) { michael@0: jit::GetIonScript(script, output.mode())->recompileInfoRef() = uint32_t(-1); michael@0: output.invalidate(); michael@0: } else { michael@0: output.setSweepIndex(newCompilerOutputCount++); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: { michael@0: gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_DISCARD_TI); michael@0: michael@0: for (CellIterUnderGC i(zone(), FINALIZE_SCRIPT); !i.done(); i.next()) { michael@0: JSScript *script = i.get(); michael@0: if (script->types) { michael@0: types::TypeScript::Sweep(fop, script, oom); michael@0: michael@0: if (releaseTypes) { michael@0: if (script->hasParallelIonScript()) { michael@0: #ifdef JS_ION michael@0: // It's possible that we preserved the parallel michael@0: // IonScript. The heuristic for their preservation is michael@0: // independent of general JIT code preservation. michael@0: MOZ_ASSERT(jit::ShouldPreserveParallelJITCode(rt, script)); michael@0: script->parallelIonScript()->recompileInfoRef().shouldSweep(*this); michael@0: #else michael@0: MOZ_CRASH(); michael@0: #endif michael@0: } else { michael@0: script->types->destroy(); michael@0: script->types = nullptr; michael@0: michael@0: /* michael@0: * Freeze constraints on stack type sets need to be michael@0: * regenerated the next time the script is analyzed. michael@0: */ michael@0: script->clearHasFreezeConstraints(); michael@0: } michael@0: michael@0: JS_ASSERT(!script->hasIonScript()); michael@0: } else { michael@0: /* Update the recompile indexes in any IonScripts still on the script. */ michael@0: if (script->hasIonScript()) michael@0: script->ionScript()->recompileInfoRef().shouldSweep(*this); michael@0: if (script->hasParallelIonScript()) michael@0: script->parallelIonScript()->recompileInfoRef().shouldSweep(*this); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: { michael@0: gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_SWEEP_TYPES); michael@0: michael@0: for (gc::CellIterUnderGC iter(zone(), gc::FINALIZE_TYPE_OBJECT); michael@0: !iter.done(); iter.next()) michael@0: { michael@0: TypeObject *object = iter.get(); michael@0: object->sweep(fop, oom); michael@0: } michael@0: michael@0: for (CompartmentsInZoneIter comp(zone()); !comp.done(); comp.next()) michael@0: comp->types.sweep(fop); michael@0: } michael@0: michael@0: if (compilerOutputs) { michael@0: size_t sweepIndex = 0; michael@0: for (size_t i = 0; i < compilerOutputs->length(); i++) { michael@0: CompilerOutput output = (*compilerOutputs)[i]; michael@0: if (output.isValid()) { michael@0: JS_ASSERT(sweepIndex == output.sweepIndex()); michael@0: output.invalidateSweepIndex(); michael@0: (*compilerOutputs)[sweepIndex++] = output; michael@0: } michael@0: } michael@0: JS_ASSERT(sweepIndex == newCompilerOutputCount); michael@0: JS_ALWAYS_TRUE(compilerOutputs->resize(newCompilerOutputCount)); michael@0: } michael@0: michael@0: { michael@0: gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_FREE_TI_ARENA); michael@0: rt->freeLifoAlloc.transferFrom(&oldAlloc); michael@0: } michael@0: } michael@0: michael@0: void michael@0: TypeZone::clearAllNewScriptAddendumsOnOOM() michael@0: { michael@0: for (gc::CellIterUnderGC iter(zone(), gc::FINALIZE_TYPE_OBJECT); michael@0: !iter.done(); iter.next()) michael@0: { michael@0: TypeObject *object = iter.get(); michael@0: object->maybeClearNewScriptAddendumOnOOM(); michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void michael@0: TypeScript::printTypes(JSContext *cx, HandleScript script) const michael@0: { michael@0: JS_ASSERT(script->types == this); michael@0: michael@0: if (!script->hasBaselineScript()) michael@0: return; michael@0: michael@0: AutoEnterAnalysis enter(nullptr, script->compartment()); michael@0: michael@0: if (script->functionNonDelazifying()) michael@0: fprintf(stderr, "Function"); michael@0: else if (script->isForEval()) michael@0: fprintf(stderr, "Eval"); michael@0: else michael@0: fprintf(stderr, "Main"); michael@0: fprintf(stderr, " #%u %s:%d ", script->id(), script->filename(), (int) script->lineno()); michael@0: michael@0: if (script->functionNonDelazifying()) { michael@0: if (js::PropertyName *name = script->functionNonDelazifying()->name()) { michael@0: const jschar *chars = name->getChars(nullptr); michael@0: JSString::dumpChars(chars, name->length()); michael@0: } michael@0: } michael@0: michael@0: fprintf(stderr, "\n this:"); michael@0: TypeScript::ThisTypes(script)->print(); michael@0: michael@0: for (unsigned i = 0; michael@0: script->functionNonDelazifying() && i < script->functionNonDelazifying()->nargs(); michael@0: i++) michael@0: { michael@0: fprintf(stderr, "\n arg%u:", i); michael@0: TypeScript::ArgTypes(script, i)->print(); michael@0: } michael@0: fprintf(stderr, "\n"); michael@0: michael@0: for (jsbytecode *pc = script->code(); pc < script->codeEnd(); pc += GetBytecodeLength(pc)) { michael@0: PrintBytecode(cx, script, pc); michael@0: michael@0: if (js_CodeSpec[*pc].format & JOF_TYPESET) { michael@0: StackTypeSet *types = TypeScript::BytecodeTypes(script, pc); michael@0: fprintf(stderr, " typeset %u:", unsigned(types - typeArray())); michael@0: types->print(); michael@0: fprintf(stderr, "\n"); michael@0: } michael@0: } michael@0: michael@0: fprintf(stderr, "\n"); michael@0: } michael@0: #endif /* DEBUG */ michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // Binary data michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: void michael@0: TypeObject::setAddendum(TypeObjectAddendum *addendum) michael@0: { michael@0: this->addendum = addendum; michael@0: } michael@0: michael@0: bool michael@0: TypeObject::addTypedObjectAddendum(JSContext *cx, Handle descr) michael@0: { michael@0: // Type descriptors are always pre-tenured. This is both because michael@0: // we expect them to live a long time and so that they can be michael@0: // safely accessed during ion compilation. michael@0: JS_ASSERT(!IsInsideNursery(cx->runtime(), descr)); michael@0: JS_ASSERT(descr); michael@0: michael@0: if (flags() & OBJECT_FLAG_ADDENDUM_CLEARED) michael@0: return true; michael@0: michael@0: JS_ASSERT(!unknownProperties()); michael@0: michael@0: if (addendum) { michael@0: JS_ASSERT(hasTypedObject()); michael@0: JS_ASSERT(&typedObject()->descr() == descr); michael@0: return true; michael@0: } michael@0: michael@0: TypeTypedObject *typedObject = js_new(descr); michael@0: if (!typedObject) michael@0: return false; michael@0: addendum = typedObject; michael@0: return true; michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // Type object addenda constructor michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: TypeObjectAddendum::TypeObjectAddendum(Kind kind) michael@0: : kind(kind) michael@0: {} michael@0: michael@0: TypeNewScript::TypeNewScript() michael@0: : TypeObjectAddendum(NewScript) michael@0: {} michael@0: michael@0: TypeTypedObject::TypeTypedObject(Handle descr) michael@0: : TypeObjectAddendum(TypedObject), michael@0: descr_(descr) michael@0: { michael@0: } michael@0: michael@0: TypeDescr & michael@0: js::types::TypeTypedObject::descr() { michael@0: return descr_->as(); michael@0: }