| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include "jsinfer.h" |
| |
| #include "mozilla/DebugOnly.h" |
| #include "mozilla/PodOperations.h" |
| |
| #include "jsapi.h" |
| #include "jsfriendapi.h" |
| #include "jsgc.h" |
| #include "jsobj.h" |
| #include "jsscript.h" |
| #include "jscntxt.h" |
| #include "jsstr.h" |
| #include "jsworkers.h" |
| #include "prmjtime.h" |
| |
| #ifdef JS_ION |
| #include "jit/BaselineJIT.h" |
| #include "jit/Ion.h" |
| #include "jit/IonCompartment.h" |
| #endif |
| #include "gc/Marking.h" |
| #include "js/MemoryMetrics.h" |
| #include "vm/Shape.h" |
| |
| #include "jsanalyzeinlines.h" |
| #include "jsatominlines.h" |
| #include "jsgcinlines.h" |
| #include "jsinferinlines.h" |
| #include "jsopcodeinlines.h" |
| #include "jsobjinlines.h" |
| #include "jsscriptinlines.h" |
| |
| #include "vm/Stack-inl.h" |
| |
| #ifdef __SUNPRO_CC |
| #include <alloca.h> |
| #endif |
| |
| using namespace js; |
| using namespace js::gc; |
| using namespace js::types; |
| using namespace js::analyze; |
| |
| using mozilla::DebugOnly; |
| using mozilla::PodArrayZero; |
| using mozilla::PodCopy; |
| using mozilla::PodZero; |
| |
| static inline jsid |
| id_prototype(JSContext *cx) { |
| return NameToId(cx->names().classPrototype); |
| } |
| |
| static inline jsid |
| id_length(JSContext *cx) { |
| return NameToId(cx->names().length); |
| } |
| |
| static inline jsid |
| id___proto__(JSContext *cx) { |
| return NameToId(cx->names().proto); |
| } |
| |
| static inline jsid |
| id_constructor(JSContext *cx) { |
| return NameToId(cx->names().constructor); |
| } |
| |
| static inline jsid |
| id_caller(JSContext *cx) { |
| return NameToId(cx->names().caller); |
| } |
| |
| #ifdef DEBUG |
| const char * |
| types::TypeIdStringImpl(jsid id) |
| { |
| if (JSID_IS_VOID(id)) |
| return "(index)"; |
| if (JSID_IS_EMPTY(id)) |
| return "(new)"; |
| static char bufs[4][100]; |
| static unsigned which = 0; |
| which = (which + 1) & 3; |
| PutEscapedString(bufs[which], 100, JSID_TO_FLAT_STRING(id), 0); |
| return bufs[which]; |
| } |
| #endif |
| |
| ///////////////////////////////////////////////////////////////////// |
| // Logging |
| ///////////////////////////////////////////////////////////////////// |
| |
| static bool InferSpewActive(SpewChannel channel) |
| { |
| static bool active[SPEW_COUNT]; |
| static bool checked = false; |
| if (!checked) { |
| checked = true; |
| PodArrayZero(active); |
| #if defined(STARBOARD) |
| return false; |
| #else |
| const char *env = getenv("INFERFLAGS"); |
| if (!env) |
| return false; |
| if (strstr(env, "ops")) |
| active[ISpewOps] = true; |
| if (strstr(env, "result")) |
| active[ISpewResult] = true; |
| if (strstr(env, "full")) { |
| for (unsigned i = 0; i < SPEW_COUNT; i++) |
| active[i] = true; |
| } |
| #endif |
| } |
| return active[channel]; |
| } |
| |
| #ifdef DEBUG |
| |
| static bool InferSpewColorable() |
| { |
| #if defined(STARBOARD) |
| return false; |
| #else |
| /* Only spew colors on xterm-color to not screw up emacs. */ |
| static bool colorable = false; |
| static bool checked = false; |
| if (!checked) { |
| checked = true; |
| const char *env = getenv("TERM"); |
| if (!env) |
| return false; |
| if (strcmp(env, "xterm-color") == 0 || strcmp(env, "xterm-256color") == 0) |
| colorable = true; |
| } |
| return colorable; |
| #endif |
| } |
| |
| const char * |
| types::InferSpewColorReset() |
| { |
| if (!InferSpewColorable()) |
| return ""; |
| return "\x1b[0m"; |
| } |
| |
| const char * |
| types::InferSpewColor(TypeConstraint *constraint) |
| { |
| /* Type constraints are printed out using foreground colors. */ |
| static const char * const colors[] = { "\x1b[31m", "\x1b[32m", "\x1b[33m", |
| "\x1b[34m", "\x1b[35m", "\x1b[36m", |
| "\x1b[37m" }; |
| if (!InferSpewColorable()) |
| return ""; |
| return colors[DefaultHasher<TypeConstraint *>::hash(constraint) % 7]; |
| } |
| |
| const char * |
| types::InferSpewColor(TypeSet *types) |
| { |
| /* Type sets are printed out using bold colors. */ |
| static const char * const colors[] = { "\x1b[1;31m", "\x1b[1;32m", "\x1b[1;33m", |
| "\x1b[1;34m", "\x1b[1;35m", "\x1b[1;36m", |
| "\x1b[1;37m" }; |
| if (!InferSpewColorable()) |
| return ""; |
| return colors[DefaultHasher<TypeSet *>::hash(types) % 7]; |
| } |
| |
| const char * |
| types::TypeString(Type type) |
| { |
| if (type.isPrimitive()) { |
| switch (type.primitive()) { |
| case JSVAL_TYPE_UNDEFINED: |
| return "void"; |
| case JSVAL_TYPE_NULL: |
| return "null"; |
| case JSVAL_TYPE_BOOLEAN: |
| return "bool"; |
| case JSVAL_TYPE_INT32: |
| return "int"; |
| case JSVAL_TYPE_DOUBLE: |
| return "float"; |
| case JSVAL_TYPE_STRING: |
| return "string"; |
| case JSVAL_TYPE_MAGIC: |
| return "lazyargs"; |
| default: |
| JS_NOT_REACHED("Bad type"); |
| return ""; |
| } |
| } |
| if (type.isUnknown()) |
| return "unknown"; |
| if (type.isAnyObject()) |
| return " object"; |
| |
| static char bufs[4][40]; |
| static unsigned which = 0; |
| which = (which + 1) & 3; |
| |
| if (type.isSingleObject()) |
| JS_snprintf(bufs[which], 40, "<0x%p>", (void *) type.singleObject()); |
| else |
| JS_snprintf(bufs[which], 40, "[0x%p]", (void *) type.typeObject()); |
| |
| return bufs[which]; |
| } |
| |
| const char * |
| types::TypeObjectString(TypeObject *type) |
| { |
| return TypeString(Type::ObjectType(type)); |
| } |
| |
| unsigned JSScript::id() { |
| if (!id_) { |
| id_ = ++compartment()->types.scriptCount; |
| InferSpew(ISpewOps, "script #%u: %p %s:%d", |
| id_, this, filename() ? filename() : "<null>", lineno); |
| } |
| return id_; |
| } |
| |
| void |
| types::InferSpew(SpewChannel channel, const char *fmt, ...) |
| { |
| if (!InferSpewActive(channel)) |
| return; |
| |
| va_list ap; |
| va_start(ap, fmt); |
| fprintf(stdout, "[infer] "); |
| vfprintf(stdout, fmt, ap); |
| fprintf(stdout, "\n"); |
| va_end(ap); |
| } |
| |
| bool |
| types::TypeHasProperty(JSContext *cx, TypeObject *obj, jsid id, const Value &value) |
| { |
| /* |
| * Check the correctness of the type information in the object's property |
| * against an actual value. |
| */ |
| if (cx->typeInferenceEnabled() && !obj->unknownProperties() && !value.isUndefined()) { |
| id = IdToTypeId(id); |
| |
| /* Watch for properties which inference does not monitor. */ |
| if (id == id___proto__(cx) || id == id_constructor(cx) || id == id_caller(cx)) |
| return true; |
| |
| /* |
| * If we called in here while resolving a type constraint, we may be in the |
| * middle of resolving a standard class and the type sets will not be updated |
| * until the outer TypeSet::add finishes. |
| */ |
| if (cx->compartment()->types.pendingCount) |
| return true; |
| |
| Type type = GetValueType(cx, value); |
| |
| AutoEnterAnalysis enter(cx); |
| |
| /* |
| * We don't track types for properties inherited from prototypes which |
| * haven't yet been accessed during analysis of the inheriting object. |
| * Don't do the property instantiation now. |
| */ |
| TypeSet *types = obj->maybeGetProperty(id, cx); |
| if (!types) |
| return true; |
| |
| /* |
| * If the types inherited from prototypes are not being propagated into |
| * this set (because we haven't analyzed code which accesses the |
| * property), skip. |
| */ |
| if (!types->hasPropagatedProperty()) |
| return true; |
| |
| if (!types->hasType(type)) { |
| TypeFailure(cx, "Missing type in object %s %s: %s", |
| TypeObjectString(obj), TypeIdString(id), TypeString(type)); |
| } |
| } |
| return true; |
| } |
| |
| #endif |
| |
| void |
| types::TypeFailure(JSContext *cx, const char *fmt, ...) |
| { |
| char msgbuf[1024]; /* Larger error messages will be truncated */ |
| char errbuf[1024]; |
| |
| va_list ap; |
| va_start(ap, fmt); |
| JS_vsnprintf(errbuf, sizeof(errbuf), fmt, ap); |
| va_end(ap); |
| |
| JS_snprintf(msgbuf, sizeof(msgbuf), "[infer failure] %s", errbuf); |
| |
| /* Dump type state, even if INFERFLAGS is unset. */ |
| cx->compartment()->types.print(cx, true); |
| |
| MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__); |
| MOZ_CRASH(); |
| } |
| |
| ///////////////////////////////////////////////////////////////////// |
| // TypeSet |
| ///////////////////////////////////////////////////////////////////// |
| |
| TypeSet::TypeSet(Type type) |
| : flags(0), objectSet(NULL), constraintList(NULL) |
| { |
| if (type.isUnknown()) { |
| flags |= TYPE_FLAG_BASE_MASK; |
| } else if (type.isPrimitive()) { |
| flags = PrimitiveTypeFlag(type.primitive()); |
| if (flags == TYPE_FLAG_DOUBLE) |
| flags |= TYPE_FLAG_INT32; |
| } else if (type.isAnyObject()) { |
| flags |= TYPE_FLAG_ANYOBJECT; |
| } else if (type.isTypeObject() && type.typeObject()->unknownProperties()) { |
| flags |= TYPE_FLAG_ANYOBJECT; |
| } else { |
| setBaseObjectCount(1); |
| objectSet = reinterpret_cast<TypeObjectKey**>(type.objectKey()); |
| } |
| } |
| |
| bool |
| TypeSet::isSubset(TypeSet *other) |
| { |
| if ((baseFlags() & other->baseFlags()) != baseFlags()) |
| return false; |
| |
| if (unknownObject()) { |
| JS_ASSERT(other->unknownObject()); |
| } else { |
| for (unsigned i = 0; i < getObjectCount(); i++) { |
| TypeObjectKey *obj = getObject(i); |
| if (!obj) |
| continue; |
| if (!other->hasType(Type::ObjectType(obj))) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool |
| TypeSet::isSubsetIgnorePrimitives(TypeSet *other) |
| { |
| TypeFlags otherFlags = other->baseFlags() | TYPE_FLAG_PRIMITIVE; |
| if ((baseFlags() & otherFlags) != baseFlags()) |
| return false; |
| |
| if (unknownObject()) { |
| JS_ASSERT(other->unknownObject()); |
| } else { |
| for (unsigned i = 0; i < getObjectCount(); i++) { |
| TypeObjectKey *obj = getObject(i); |
| if (!obj) |
| continue; |
| if (!other->hasType(Type::ObjectType(obj))) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool |
| TypeSet::intersectionEmpty(TypeSet *other) |
| { |
| // For unknown/unknownObject there is no reason they couldn't intersect. |
| // I.e. we eagerly return their intersection isn't empty. |
| // That's ok, since we can't make predictions that can be checked to not hold. |
| if (unknown() || other->unknown()) |
| return false; |
| |
| if (unknownObject() && other->unknownObject()) |
| return false; |
| |
| if (unknownObject() && other->getObjectCount() > 0) |
| return false; |
| |
| if (other->unknownObject() && getObjectCount() > 0) |
| return false; |
| |
| // Test if there is an intersection in the baseFlags |
| if ((baseFlags() & other->baseFlags()) != 0) |
| return false; |
| |
| // Test if there are object that are in both TypeSets |
| if (!unknownObject()) { |
| for (unsigned i = 0; i < getObjectCount(); i++) { |
| TypeObjectKey *obj = getObject(i); |
| if (!obj) |
| continue; |
| if (other->hasType(Type::ObjectType(obj))) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| inline void |
| TypeSet::addTypesToConstraint(JSContext *cx, TypeConstraint *constraint) |
| { |
| /* |
| * Build all types in the set into a vector before triggering the |
| * constraint, as doing so may modify this type set. |
| */ |
| Vector<Type> types(cx); |
| |
| /* If any type is possible, there's no need to worry about specifics. */ |
| if (flags & TYPE_FLAG_UNKNOWN) { |
| if (!types.append(Type::UnknownType())) |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| } else { |
| /* Enqueue type set members stored as bits. */ |
| for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) { |
| if (flags & flag) { |
| Type type = Type::PrimitiveType(TypeFlagPrimitive(flag)); |
| if (!types.append(type)) |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| } |
| } |
| |
| /* If any object is possible, skip specifics. */ |
| if (flags & TYPE_FLAG_ANYOBJECT) { |
| if (!types.append(Type::AnyObjectType())) |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| } else { |
| /* Enqueue specific object types. */ |
| unsigned count = getObjectCount(); |
| for (unsigned i = 0; i < count; i++) { |
| TypeObjectKey *object = getObject(i); |
| if (object) { |
| if (!types.append(Type::ObjectType(object))) |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| } |
| } |
| } |
| } |
| |
| for (unsigned i = 0; i < types.length(); i++) |
| constraint->newType(cx, this, types[i]); |
| } |
| |
| inline void |
| TypeSet::add(JSContext *cx, TypeConstraint *constraint, bool callExisting) |
| { |
| if (!constraint) { |
| /* OOM failure while constructing the constraint. */ |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| return; |
| } |
| |
| JS_ASSERT(cx->compartment()->activeAnalysis); |
| |
| InferSpew(ISpewOps, "addConstraint: %sT%p%s %sC%p%s %s", |
| InferSpewColor(this), this, InferSpewColorReset(), |
| InferSpewColor(constraint), constraint, InferSpewColorReset(), |
| constraint->kind()); |
| |
| JS_ASSERT(constraint->next == NULL); |
| constraint->next = constraintList; |
| constraintList = constraint; |
| |
| if (callExisting) |
| addTypesToConstraint(cx, constraint); |
| } |
| |
| void |
| TypeSet::print() |
| { |
| if (flags & TYPE_FLAG_OWN_PROPERTY) |
| printf(" [own]"); |
| if (flags & TYPE_FLAG_CONFIGURED_PROPERTY) |
| printf(" [configured]"); |
| |
| if (definiteProperty()) |
| printf(" [definite:%d]", definiteSlot()); |
| |
| if (baseFlags() == 0 && !baseObjectCount()) { |
| printf(" missing"); |
| return; |
| } |
| |
| if (flags & TYPE_FLAG_UNKNOWN) |
| printf(" unknown"); |
| if (flags & TYPE_FLAG_ANYOBJECT) |
| printf(" object"); |
| |
| if (flags & TYPE_FLAG_UNDEFINED) |
| printf(" void"); |
| if (flags & TYPE_FLAG_NULL) |
| printf(" null"); |
| if (flags & TYPE_FLAG_BOOLEAN) |
| printf(" bool"); |
| if (flags & TYPE_FLAG_INT32) |
| printf(" int"); |
| if (flags & TYPE_FLAG_DOUBLE) |
| printf(" float"); |
| if (flags & TYPE_FLAG_STRING) |
| printf(" string"); |
| if (flags & TYPE_FLAG_LAZYARGS) |
| printf(" lazyargs"); |
| |
| uint32_t objectCount = baseObjectCount(); |
| if (objectCount) { |
| printf(" object[%u]", objectCount); |
| |
| unsigned count = getObjectCount(); |
| for (unsigned i = 0; i < count; i++) { |
| TypeObjectKey *object = getObject(i); |
| if (object) |
| printf(" %s", TypeString(Type::ObjectType(object))); |
| } |
| } |
| } |
| |
| StackTypeSet * |
| StackTypeSet::make(JSContext *cx, const char *name) |
| { |
| JS_ASSERT(cx->compartment()->activeAnalysis); |
| |
| StackTypeSet *res = cx->analysisLifoAlloc().new_<StackTypeSet>(); |
| if (!res) { |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| return NULL; |
| } |
| |
| InferSpew(ISpewOps, "typeSet: %sT%p%s intermediate %s", |
| InferSpewColor(res), res, InferSpewColorReset(), |
| name); |
| res->setPurged(); |
| |
| return res; |
| } |
| |
| StackTypeSet * |
| TypeSet::clone(LifoAlloc *alloc) const |
| { |
| unsigned objectCount = baseObjectCount(); |
| unsigned capacity = (objectCount >= 2) ? HashSetCapacity(objectCount) : 0; |
| |
| StackTypeSet *res = alloc->new_<StackTypeSet>(); |
| if (!res) |
| return NULL; |
| |
| TypeObjectKey **newSet; |
| if (capacity) { |
| newSet = alloc->newArray<TypeObjectKey*>(capacity); |
| if (!newSet) |
| return NULL; |
| PodCopy(newSet, objectSet, capacity); |
| } |
| |
| res->flags = this->flags; |
| res->objectSet = capacity ? newSet : this->objectSet; |
| |
| return res; |
| } |
| |
| bool |
| TypeSet::addObject(TypeObjectKey *key, LifoAlloc *alloc) |
| { |
| JS_ASSERT(!constraintList); |
| |
| uint32_t objectCount = baseObjectCount(); |
| TypeObjectKey **pentry = HashSetInsert<TypeObjectKey *,TypeObjectKey,TypeObjectKey> |
| (*alloc, objectSet, objectCount, key); |
| if (!pentry) |
| return false; |
| if (*pentry) |
| return true; |
| *pentry = key; |
| |
| setBaseObjectCount(objectCount); |
| |
| if (objectCount == TYPE_FLAG_OBJECT_COUNT_LIMIT) { |
| flags |= TYPE_FLAG_ANYOBJECT; |
| clearObjects(); |
| } |
| |
| return true; |
| } |
| |
| /* static */ StackTypeSet * |
| TypeSet::unionSets(TypeSet *a, TypeSet *b, LifoAlloc *alloc) |
| { |
| StackTypeSet *res = alloc->new_<StackTypeSet>(); |
| if (!res) |
| return NULL; |
| |
| res->flags = a->baseFlags() | b->baseFlags(); |
| |
| if (!res->unknownObject()) { |
| for (size_t i = 0; i < a->getObjectCount() && !res->unknownObject(); i++) { |
| TypeObjectKey *key = a->getObject(i); |
| if (key && !res->addObject(key, alloc)) |
| return NULL; |
| } |
| for (size_t i = 0; i < b->getObjectCount() && !res->unknownObject(); i++) { |
| TypeObjectKey *key = b->getObject(i); |
| if (key && !res->addObject(key, alloc)) |
| return NULL; |
| } |
| } |
| |
| return res; |
| } |
| |
| ///////////////////////////////////////////////////////////////////// |
| // TypeSet constraints |
| ///////////////////////////////////////////////////////////////////// |
| |
| /* Standard subset constraint, propagate all types from one set to another. */ |
| class TypeConstraintSubset : public TypeConstraint |
| { |
| public: |
| TypeSet *target; |
| |
| TypeConstraintSubset(TypeSet *target) |
| : target(target) |
| { |
| JS_ASSERT(target); |
| } |
| |
| const char *kind() { return "subset"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type) |
| { |
| /* Basic subset constraint, move all types to the target. */ |
| target->addType(cx, type); |
| } |
| }; |
| |
| void |
| StackTypeSet::addSubset(JSContext *cx, TypeSet *target) |
| { |
| add(cx, cx->analysisLifoAlloc().new_<TypeConstraintSubset>(target)); |
| } |
| |
| void |
| HeapTypeSet::addSubset(JSContext *cx, TypeSet *target) |
| { |
| JS_ASSERT(!target->purged()); |
| add(cx, cx->typeLifoAlloc().new_<TypeConstraintSubset>(target)); |
| } |
| |
| enum PropertyAccessKind { |
| PROPERTY_WRITE, |
| PROPERTY_READ, |
| PROPERTY_READ_EXISTING |
| }; |
| |
| /* Constraints for reads/writes on object properties. */ |
| template <PropertyAccessKind access> |
| class TypeConstraintProp : public TypeConstraint |
| { |
| JSScript *script_; |
| |
| public: |
| jsbytecode *pc; |
| |
| /* |
| * If assign is true, the target is used to update a property of the object. |
| * If assign is false, the target is assigned the value of the property. |
| */ |
| StackTypeSet *target; |
| |
| /* Property being accessed. This is unrooted. */ |
| jsid id; |
| |
| TypeConstraintProp(JSScript *script, jsbytecode *pc, StackTypeSet *target, jsid id) |
| : script_(script), pc(pc), target(target), id(id) |
| { |
| JS_ASSERT(script && pc && target); |
| } |
| |
| const char *kind() { return "prop"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type); |
| }; |
| |
| typedef TypeConstraintProp<PROPERTY_WRITE> TypeConstraintSetProperty; |
| typedef TypeConstraintProp<PROPERTY_READ> TypeConstraintGetProperty; |
| typedef TypeConstraintProp<PROPERTY_READ_EXISTING> TypeConstraintGetPropertyExisting; |
| |
| void |
| StackTypeSet::addGetProperty(JSContext *cx, JSScript *script, jsbytecode *pc, |
| StackTypeSet *target, jsid id) |
| { |
| /* |
| * GetProperty constraints are normally used with property read input type |
| * sets, except for array_pop/array_shift special casing. |
| */ |
| JS_ASSERT(js_CodeSpec[*pc].format & JOF_INVOKE); |
| |
| add(cx, cx->analysisLifoAlloc().new_<TypeConstraintGetProperty>(script, pc, target, id)); |
| } |
| |
| void |
| StackTypeSet::addSetProperty(JSContext *cx, JSScript *script, jsbytecode *pc, |
| StackTypeSet *target, jsid id) |
| { |
| add(cx, cx->analysisLifoAlloc().new_<TypeConstraintSetProperty>(script, pc, target, id)); |
| } |
| |
| void |
| HeapTypeSet::addGetProperty(JSContext *cx, JSScript *script, jsbytecode *pc, |
| StackTypeSet *target, jsid id) |
| { |
| JS_ASSERT(!target->purged()); |
| add(cx, cx->typeLifoAlloc().new_<TypeConstraintGetProperty>(script, pc, target, id)); |
| } |
| |
| /* |
| * Constraints for updating the 'this' types of callees on CALLPROP/CALLELEM. |
| * These are derived from the types on the properties themselves, rather than |
| * those pushed in the 'this' slot at the call site, which allows us to retain |
| * correlations between the type of the 'this' object and the associated |
| * callee scripts at polymorphic call sites. |
| */ |
| template <PropertyAccessKind access> |
| class TypeConstraintCallProp : public TypeConstraint |
| { |
| JSScript *script_; |
| |
| public: |
| jsbytecode *callpc; |
| |
| /* Property being accessed. */ |
| jsid id; |
| |
| TypeConstraintCallProp(JSScript *script, jsbytecode *callpc, jsid id) |
| : script_(script), callpc(callpc), id(id) |
| { |
| JS_ASSERT(script && callpc); |
| } |
| |
| const char *kind() { return "callprop"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type); |
| }; |
| |
| typedef TypeConstraintCallProp<PROPERTY_READ> TypeConstraintCallProperty; |
| typedef TypeConstraintCallProp<PROPERTY_READ_EXISTING> TypeConstraintCallPropertyExisting; |
| |
| void |
| HeapTypeSet::addCallProperty(JSContext *cx, JSScript *script, jsbytecode *pc, jsid id) |
| { |
| /* |
| * For calls which will go through JSOP_NEW, don't add any constraints to |
| * modify the 'this' types of callees. The initial 'this' value will be |
| * outright ignored. |
| */ |
| jsbytecode *callpc = script->analysis()->getCallPC(pc); |
| if (JSOp(*callpc) == JSOP_NEW) |
| return; |
| |
| add(cx, cx->typeLifoAlloc().new_<TypeConstraintCallProperty>(script, callpc, id)); |
| } |
| |
| /* |
| * Constraints for generating 'set' property constraints on a SETELEM only if |
| * the element type may be a number. For SETELEM we only account for integer |
| * indexes, and if the element cannot be an integer (e.g. it must be a string) |
| * then we lose precision by treating it like one. |
| */ |
| class TypeConstraintSetElement : public TypeConstraint |
| { |
| JSScript *script_; |
| |
| public: |
| jsbytecode *pc; |
| |
| StackTypeSet *objectTypes; |
| StackTypeSet *valueTypes; |
| |
| TypeConstraintSetElement(JSScript *script, jsbytecode *pc, |
| StackTypeSet *objectTypes, StackTypeSet *valueTypes) |
| : script_(script), pc(pc), |
| objectTypes(objectTypes), valueTypes(valueTypes) |
| { |
| JS_ASSERT(script && pc); |
| } |
| |
| const char *kind() { return "setelement"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type); |
| }; |
| |
| void |
| StackTypeSet::addSetElement(JSContext *cx, JSScript *script, jsbytecode *pc, |
| StackTypeSet *objectTypes, StackTypeSet *valueTypes) |
| { |
| add(cx, cx->analysisLifoAlloc().new_<TypeConstraintSetElement>(script, pc, objectTypes, |
| valueTypes)); |
| } |
| |
| /* |
| * Constraints for watching call edges as they are discovered and invoking native |
| * function handlers, adding constraints for arguments, receiver objects and the |
| * return value, and updating script foundOffsets. |
| */ |
| class TypeConstraintCall : public TypeConstraint |
| { |
| public: |
| /* Call site being tracked. */ |
| TypeCallsite *callsite; |
| |
| TypeConstraintCall(TypeCallsite *callsite) |
| : callsite(callsite) |
| {} |
| |
| const char *kind() { return "call"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type); |
| }; |
| |
| void |
| StackTypeSet::addCall(JSContext *cx, TypeCallsite *site) |
| { |
| add(cx, cx->analysisLifoAlloc().new_<TypeConstraintCall>(site)); |
| } |
| |
| /* Constraints for arithmetic operations. */ |
| class TypeConstraintArith : public TypeConstraint |
| { |
| JSScript *script_; |
| |
| public: |
| jsbytecode *pc; |
| |
| /* Type set receiving the result of the arithmetic. */ |
| TypeSet *target; |
| |
| /* For addition operations, the other operand. */ |
| TypeSet *other; |
| |
| TypeConstraintArith(JSScript *script, jsbytecode *pc, TypeSet *target, TypeSet *other) |
| : script_(script), pc(pc), target(target), other(other) |
| { |
| JS_ASSERT(target); |
| } |
| |
| const char *kind() { return "arith"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type); |
| }; |
| |
| void |
| StackTypeSet::addArith(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target, |
| TypeSet *other) |
| { |
| add(cx, cx->analysisLifoAlloc().new_<TypeConstraintArith>(script, pc, target, other)); |
| } |
| |
| /* Subset constraint which transforms primitive values into appropriate objects. */ |
| class TypeConstraintTransformThis : public TypeConstraint |
| { |
| JSScript *script_; |
| |
| public: |
| TypeSet *target; |
| |
| TypeConstraintTransformThis(JSScript *script, TypeSet *target) |
| : script_(script), target(target) |
| {} |
| |
| const char *kind() { return "transformthis"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type); |
| }; |
| |
| void |
| StackTypeSet::addTransformThis(JSContext *cx, JSScript *script, TypeSet *target) |
| { |
| add(cx, cx->analysisLifoAlloc().new_<TypeConstraintTransformThis>(script, target)); |
| } |
| |
| /* |
| * Constraint which adds a particular type to the 'this' types of all |
| * discovered scripted functions. |
| */ |
| class TypeConstraintPropagateThis : public TypeConstraint |
| { |
| JSScript *script_; |
| |
| public: |
| jsbytecode *callpc; |
| Type type; |
| StackTypeSet *types; |
| |
| TypeConstraintPropagateThis(JSScript *script, jsbytecode *callpc, Type type, StackTypeSet *types) |
| : script_(script), callpc(callpc), type(type), types(types) |
| {} |
| |
| const char *kind() { return "propagatethis"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type); |
| }; |
| |
| void |
| StackTypeSet::addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, |
| Type type, StackTypeSet *types) |
| { |
| add(cx, cx->analysisLifoAlloc().new_<TypeConstraintPropagateThis>(script, pc, type, types)); |
| } |
| |
| /* Subset constraint which filters out primitive types. */ |
| class TypeConstraintFilterPrimitive : public TypeConstraint |
| { |
| public: |
| TypeSet *target; |
| |
| TypeConstraintFilterPrimitive(TypeSet *target) |
| : target(target) |
| {} |
| |
| const char *kind() { return "filter"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type) |
| { |
| if (type.isPrimitive()) |
| return; |
| |
| target->addType(cx, type); |
| } |
| }; |
| |
| void |
| HeapTypeSet::addFilterPrimitives(JSContext *cx, TypeSet *target) |
| { |
| add(cx, cx->typeLifoAlloc().new_<TypeConstraintFilterPrimitive>(target)); |
| } |
| |
| /* If id is a normal slotful 'own' property of an object, get its shape. */ |
| static inline Shape * |
| GetSingletonShape(JSContext *cx, JSObject *obj, jsid idArg) |
| { |
| if (!obj->isNative()) |
| return NULL; |
| RootedId id(cx, idArg); |
| Shape *shape = obj->nativeLookup(cx, id); |
| if (shape && shape->hasDefaultGetter() && shape->hasSlot()) |
| return shape; |
| return NULL; |
| } |
| |
| void |
| ScriptAnalysis::pruneTypeBarriers(JSContext *cx, uint32_t offset) |
| { |
| TypeBarrier **pbarrier = &getCode(offset).typeBarriers; |
| while (*pbarrier) { |
| TypeBarrier *barrier = *pbarrier; |
| if (barrier->target->hasType(barrier->type)) { |
| /* Barrier is now obsolete, it can be removed. */ |
| *pbarrier = barrier->next; |
| continue; |
| } |
| if (barrier->singleton) { |
| JS_ASSERT(barrier->type.isPrimitive(JSVAL_TYPE_UNDEFINED)); |
| Shape *shape = GetSingletonShape(cx, barrier->singleton, barrier->singletonId); |
| if (shape && !barrier->singleton->nativeGetSlot(shape->slot()).isUndefined()) { |
| /* |
| * When we analyzed the script the singleton had an 'own' |
| * property which was undefined (probably a 'var' variable |
| * added to a global object), but now it is defined. The only |
| * way it can become undefined again is if an explicit assign |
| * or deletion on the property occurs, which will update the |
| * type set for the property directly and trigger construction |
| * of a normal type barrier. |
| */ |
| *pbarrier = barrier->next; |
| continue; |
| } |
| } |
| pbarrier = &barrier->next; |
| } |
| } |
| |
| /* |
| * Cheesy limit on the number of objects we will tolerate in an observed type |
| * set before refusing to add new type barriers for objects. |
| * :FIXME: this heuristic sucks, and doesn't handle calls. |
| */ |
| static const uint32_t BARRIER_OBJECT_LIMIT = 10; |
| |
| void ScriptAnalysis::breakTypeBarriers(JSContext *cx, uint32_t offset, bool all) |
| { |
| pruneTypeBarriers(cx, offset); |
| |
| bool resetResolving = !cx->compartment()->types.resolving; |
| if (resetResolving) |
| cx->compartment()->types.resolving = true; |
| |
| TypeBarrier **pbarrier = &getCode(offset).typeBarriers; |
| while (*pbarrier) { |
| TypeBarrier *barrier = *pbarrier; |
| if (barrier->target->hasType(barrier->type) ) { |
| /* |
| * Barrier is now obsolete, it can be removed. This is not |
| * redundant with the pruneTypeBarriers() call above, as breaking |
| * previous type barriers may have modified the target type set. |
| */ |
| *pbarrier = barrier->next; |
| } else if (all) { |
| /* Force removal of the barrier. */ |
| barrier->target->addType(cx, barrier->type); |
| *pbarrier = barrier->next; |
| } else if (!barrier->type.isUnknown() && |
| !barrier->type.isAnyObject() && |
| barrier->type.isObject() && |
| barrier->target->getObjectCount() >= BARRIER_OBJECT_LIMIT) { |
| /* Maximum number of objects in the set exceeded. */ |
| barrier->target->addType(cx, barrier->type); |
| *pbarrier = barrier->next; |
| } else { |
| pbarrier = &barrier->next; |
| } |
| } |
| |
| if (resetResolving) { |
| cx->compartment()->types.resolving = false; |
| cx->compartment()->types.resolvePending(cx); |
| } |
| } |
| |
| void ScriptAnalysis::breakTypeBarriersSSA(JSContext *cx, const SSAValue &v) |
| { |
| if (v.kind() != SSAValue::PUSHED) |
| return; |
| |
| uint32_t offset = v.pushedOffset(); |
| if (JSOp(script_->code[offset]) == JSOP_GETPROP) |
| breakTypeBarriersSSA(cx, poppedValue(offset, 0)); |
| |
| breakTypeBarriers(cx, offset, true); |
| } |
| |
| /* |
| * Subset constraint for property reads and argument passing which can add type |
| * barriers on the read instead of passing types along. |
| */ |
| class TypeConstraintSubsetBarrier : public TypeConstraint |
| { |
| public: |
| JSScript *script; |
| jsbytecode *pc; |
| TypeSet *target; |
| |
| TypeConstraintSubsetBarrier(JSScript *script, jsbytecode *pc, TypeSet *target) |
| : script(script), pc(pc), target(target) |
| {} |
| |
| const char *kind() { return "subsetBarrier"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type) |
| { |
| if (!target->hasType(type)) { |
| if (!script->ensureRanAnalysis(cx)) |
| return; |
| script->analysis()->addTypeBarrier(cx, pc, target, type); |
| } |
| } |
| }; |
| |
| void |
| StackTypeSet::addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target) |
| { |
| add(cx, cx->analysisLifoAlloc().new_<TypeConstraintSubsetBarrier>(script, pc, target)); |
| } |
| |
| void |
| HeapTypeSet::addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target) |
| { |
| JS_ASSERT(!target->purged()); |
| add(cx, cx->typeLifoAlloc().new_<TypeConstraintSubsetBarrier>(script, pc, target)); |
| } |
| |
| ///////////////////////////////////////////////////////////////////// |
| // TypeConstraint |
| ///////////////////////////////////////////////////////////////////// |
| |
| /* Get the object to use for a property access on type. */ |
| static inline TypeObject * |
| GetPropertyObject(JSContext *cx, HandleScript script, Type type) |
| { |
| if (type.isTypeObject()) |
| return type.typeObject(); |
| |
| /* Force instantiation of lazy types for singleton objects. */ |
| if (type.isSingleObject()) |
| return type.singleObject()->getType(cx); |
| |
| /* |
| * Handle properties attached to primitive types, treating this access as a |
| * read on the primitive's new object. |
| */ |
| TypeObject *object = NULL; |
| switch (type.primitive()) { |
| |
| case JSVAL_TYPE_INT32: |
| case JSVAL_TYPE_DOUBLE: |
| object = TypeScript::StandardType(cx, JSProto_Number); |
| break; |
| |
| case JSVAL_TYPE_BOOLEAN: |
| object = TypeScript::StandardType(cx, JSProto_Boolean); |
| break; |
| |
| case JSVAL_TYPE_STRING: |
| object = TypeScript::StandardType(cx, JSProto_String); |
| break; |
| |
| default: |
| /* undefined, null and lazy arguments do not have properties. */ |
| return NULL; |
| } |
| |
| if (!object) |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| return object; |
| } |
| |
| static inline bool |
| UsePropertyTypeBarrier(jsbytecode *pc) |
| { |
| /* |
| * At call opcodes, type barriers can only be added for the call bindings, |
| * which TypeConstraintCall will add barrier constraints for directly. |
| */ |
| uint32_t format = js_CodeSpec[*pc].format; |
| return (format & JOF_TYPESET) && !(format & JOF_INVOKE); |
| } |
| |
| static inline void |
| MarkPropertyAccessUnknown(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target) |
| { |
| if (UsePropertyTypeBarrier(pc)) |
| script->analysis()->addTypeBarrier(cx, pc, target, Type::UnknownType()); |
| else |
| target->addType(cx, Type::UnknownType()); |
| } |
| |
| /* |
| * Get a value for reading id from obj or its prototypes according to the |
| * current VM state, returning the unknown type on failure or an undefined |
| * property. |
| */ |
| static inline Type |
| GetSingletonPropertyType(JSContext *cx, JSObject *rawObjArg, HandleId id) |
| { |
| RootedObject obj(cx, rawObjArg); // Root this locally because it's assigned to. |
| |
| JS_ASSERT(id == IdToTypeId(id)); |
| |
| if (JSID_IS_VOID(id)) |
| return Type::UnknownType(); |
| |
| if (obj->isTypedArray()) { |
| if (id == id_length(cx)) |
| return Type::Int32Type(); |
| obj = obj->getProto(); |
| } |
| |
| while (obj) { |
| if (!obj->isNative()) |
| return Type::UnknownType(); |
| |
| RootedValue v(cx); |
| if (HasDataProperty(cx, obj, id, v.address())) { |
| if (v.isUndefined()) |
| return Type::UnknownType(); |
| return GetValueType(cx, v); |
| } |
| |
| obj = obj->getProto(); |
| } |
| |
| return Type::UnknownType(); |
| } |
| |
| /* |
| * Handle a property access on a specific object. All property accesses go through |
| * here, whether via x.f, x[f], or global name accesses. |
| */ |
| template <PropertyAccessKind access> |
| static inline void |
| PropertyAccess(JSContext *cx, JSScript *script, jsbytecode *pc, TypeObject *object, |
| StackTypeSet *target, jsid idArg) |
| { |
| RootedId id(cx, idArg); |
| |
| /* Reads from objects with unknown properties are unknown, writes to such objects are ignored. */ |
| if (object->unknownProperties()) { |
| if (access != PROPERTY_WRITE) |
| MarkPropertyAccessUnknown(cx, script, pc, target); |
| return; |
| } |
| |
| /* |
| * Get the possible types of the property. For assignments, we do not |
| * automatically update the 'own' bit on accessed properties, except for |
| * indexed elements. This exception allows for JIT fast paths to avoid |
| * testing the array's type when assigning to dense array elements. |
| */ |
| bool markOwn = access == PROPERTY_WRITE && JSID_IS_VOID(id); |
| HeapTypeSet *types = object->getProperty(cx, id, markOwn); |
| if (!types) |
| return; |
| |
| /* Capture the effects of a standard property access. */ |
| if (access == PROPERTY_WRITE) { |
| target->addSubset(cx, types); |
| } else { |
| JS_ASSERT_IF(script->hasAnalysis(), |
| target == TypeScript::BytecodeTypes(script, pc)); |
| if (!types->hasPropagatedProperty()) |
| object->getFromPrototypes(cx, id, types); |
| if (UsePropertyTypeBarrier(pc)) { |
| if (access == PROPERTY_READ) { |
| types->addSubsetBarrier(cx, script, pc, target); |
| } else { |
| TypeConstraintSubsetBarrier constraint(script, pc, target); |
| types->addTypesToConstraint(cx, &constraint); |
| } |
| if (object->singleton && !JSID_IS_VOID(id)) { |
| /* |
| * Add a singleton type barrier on the object if it has an |
| * 'own' property which is currently undefined. We'll be able |
| * to remove the barrier after the property becomes defined, |
| * even if no undefined value is ever observed at pc. |
| */ |
| RootedObject singleton(cx, object->singleton); |
| RootedShape shape(cx, GetSingletonShape(cx, singleton, id)); |
| if (shape && singleton->nativeGetSlot(shape->slot()).isUndefined()) |
| script->analysis()->addSingletonTypeBarrier(cx, pc, target, singleton, id); |
| } |
| } else { |
| JS_ASSERT(access == PROPERTY_READ); |
| types->addSubset(cx, target); |
| } |
| } |
| } |
| |
| /* Whether the JSObject/TypeObject referent of an access on type cannot be determined. */ |
| static inline bool |
| UnknownPropertyAccess(HandleScript script, Type type) |
| { |
| return type.isUnknown() |
| || type.isAnyObject() |
| || (!type.isObject() && !script->compileAndGo); |
| } |
| |
| template <PropertyAccessKind access> |
| void |
| TypeConstraintProp<access>::newType(JSContext *cx, TypeSet *source, Type type) |
| { |
| RootedScript script(cx, script_); |
| |
| if (UnknownPropertyAccess(script, type)) { |
| /* |
| * Access on an unknown object. Reads produce an unknown result, writes |
| * need to be monitored. |
| */ |
| if (access == PROPERTY_WRITE) |
| cx->compartment()->types.monitorBytecode(cx, script, pc - script->code); |
| else |
| MarkPropertyAccessUnknown(cx, script, pc, target); |
| return; |
| } |
| |
| if (type.isPrimitive(JSVAL_TYPE_MAGIC)) { |
| /* Ignore cases which will be accounted for by the followEscapingArguments analysis. */ |
| if (access == PROPERTY_WRITE || (id != JSID_VOID && id != id_length(cx))) |
| return; |
| |
| if (id == JSID_VOID) |
| MarkPropertyAccessUnknown(cx, script, pc, target); |
| return; |
| } |
| |
| TypeObject *object = GetPropertyObject(cx, script, type); |
| if (object) |
| PropertyAccess<access>(cx, script, pc, object, target, id); |
| } |
| |
| template <PropertyAccessKind access> |
| void |
| TypeConstraintCallProp<access>::newType(JSContext *cx, TypeSet *source, Type type) |
| { |
| RootedScript script(cx, script_); |
| |
| /* |
| * For CALLPROP, we need to update not just the pushed types but also the |
| * 'this' types of possible callees. If we can't figure out that set of |
| * callees, monitor the call to make sure discovered callees get their |
| * 'this' types updated. |
| */ |
| |
| if (UnknownPropertyAccess(script, type)) { |
| cx->compartment()->types.monitorBytecode(cx, script, callpc - script->code); |
| return; |
| } |
| |
| TypeObject *object = GetPropertyObject(cx, script, type); |
| if (object) { |
| if (object->unknownProperties()) { |
| cx->compartment()->types.monitorBytecode(cx, script, callpc - script->code); |
| } else { |
| TypeSet *types = object->getProperty(cx, id, false); |
| if (!types) |
| return; |
| if (!types->hasPropagatedProperty()) |
| object->getFromPrototypes(cx, id, types); |
| if (access == PROPERTY_READ) { |
| types->add(cx, cx->typeLifoAlloc().new_<TypeConstraintPropagateThis>( |
| script_, callpc, type, (StackTypeSet *) NULL)); |
| } else { |
| TypeConstraintPropagateThis constraint(script, callpc, type, NULL); |
| types->addTypesToConstraint(cx, &constraint); |
| } |
| } |
| } |
| } |
| |
| void |
| TypeConstraintSetElement::newType(JSContext *cx, TypeSet *source, Type type) |
| { |
| RootedScript script(cx, script_); |
| if (type.isUnknown() || |
| type.isPrimitive(JSVAL_TYPE_INT32) || |
| type.isPrimitive(JSVAL_TYPE_DOUBLE)) { |
| objectTypes->addSetProperty(cx, script, pc, valueTypes, JSID_VOID); |
| } |
| } |
| |
| static inline JSFunction * |
| CloneCallee(JSContext *cx, HandleFunction fun, HandleScript script, jsbytecode *pc) |
| { |
| /* |
| * Clone called functions at appropriate callsites to match interpreter |
| * behavior. |
| */ |
| JSFunction *callee = CloneFunctionAtCallsite(cx, fun, script, pc); |
| if (!callee) |
| return NULL; |
| |
| InferSpew(ISpewOps, "callsiteCloneType: #%u:%05u: %s", |
| script->id(), pc - script->code, TypeString(Type::ObjectType(callee))); |
| |
| return callee; |
| } |
| |
| void |
| TypeConstraintCall::newType(JSContext *cx, TypeSet *source, Type type) |
| { |
| RootedScript script(cx, callsite->script); |
| jsbytecode *pc = callsite->pc; |
| |
| JS_ASSERT_IF(script->hasAnalysis(), |
| callsite->returnTypes == TypeScript::BytecodeTypes(script, pc)); |
| |
| if (type.isUnknown() || type.isAnyObject()) { |
| /* Monitor calls on unknown functions. */ |
| cx->compartment()->types.monitorBytecode(cx, script, pc - script->code); |
| return; |
| } |
| |
| RootedFunction callee(cx); |
| |
| if (type.isSingleObject()) { |
| RootedObject obj(cx, type.singleObject()); |
| |
| if (!obj->is<JSFunction>()) { |
| /* Calls on non-functions are dynamically monitored. */ |
| return; |
| } |
| |
| if (obj->as<JSFunction>().isNative()) { |
| /* |
| * The return value and all side effects within native calls should |
| * be dynamically monitored, except when the compiler is generating |
| * specialized inline code or stub calls for a specific natives and |
| * knows about the behavior of that native. |
| */ |
| cx->compartment()->types.monitorBytecode(cx, script, pc - script->code, true); |
| |
| /* |
| * Add type constraints capturing the possible behavior of |
| * specialized natives which operate on properties. :XXX: use |
| * better factoring for both this and the compiler code itself |
| * which specializes particular natives. |
| */ |
| |
| Native native = obj->as<JSFunction>().native(); |
| |
| if (native == js::array_push) { |
| for (size_t i = 0; i < callsite->argumentCount; i++) { |
| callsite->thisTypes->addSetProperty(cx, script, pc, |
| callsite->argumentTypes[i], JSID_VOID); |
| } |
| } |
| |
| if (native == intrinsic_UnsafeSetElement) { |
| // UnsafeSetElement(arr0, idx0, elem0, ..., arrN, idxN, elemN) |
| // is (basically) equivalent to arri[idxi] = elemi for i = 0...N |
| JS_ASSERT((callsite->argumentCount % 3) == 0); |
| for (size_t i = 0; i < callsite->argumentCount; i += 3) { |
| StackTypeSet *arr = callsite->argumentTypes[i]; |
| StackTypeSet *elem = callsite->argumentTypes[i+2]; |
| arr->addSetProperty(cx, script, pc, elem, JSID_VOID); |
| } |
| } |
| |
| if (native == js::array_pop || native == js::array_shift) |
| callsite->thisTypes->addGetProperty(cx, script, pc, callsite->returnTypes, JSID_VOID); |
| |
| if (native == js_Array) { |
| TypeObject *res = TypeScript::InitObject(cx, script, pc, JSProto_Array); |
| if (!res) |
| return; |
| |
| callsite->returnTypes->addType(cx, Type::ObjectType(res)); |
| |
| if (callsite->argumentCount >= 2) { |
| for (unsigned i = 0; i < callsite->argumentCount; i++) { |
| PropertyAccess<PROPERTY_WRITE>(cx, script, pc, res, |
| callsite->argumentTypes[i], JSID_VOID); |
| } |
| } |
| } |
| |
| if (native == js_String && callsite->isNew) { |
| // Note that "new String()" returns a String object and "String()" |
| // returns a primitive string. |
| TypeObject *res = TypeScript::StandardType(cx, JSProto_String); |
| if (!res) |
| return; |
| |
| callsite->returnTypes->addType(cx, Type::ObjectType(res)); |
| } |
| |
| return; |
| } |
| |
| callee = &obj->as<JSFunction>(); |
| } else if (type.isTypeObject()) { |
| callee = type.typeObject()->interpretedFunction; |
| if (!callee) |
| return; |
| } else { |
| /* Calls on non-objects are dynamically monitored. */ |
| return; |
| } |
| |
| if (callee->isInterpretedLazy() && !callee->getOrCreateScript(cx)) |
| return; |
| |
| /* |
| * As callsite cloning is a hint, we must propagate to both the original |
| * and the clone. |
| */ |
| if (callee->nonLazyScript()->shouldCloneAtCallsite) { |
| callee = CloneCallee(cx, callee, script, pc); |
| if (!callee) |
| return; |
| } |
| |
| RootedScript calleeScript(cx, callee->nonLazyScript()); |
| if (!calleeScript->ensureHasTypes(cx)) |
| return; |
| |
| unsigned nargs = callee->nargs; |
| |
| /* Add bindings for the arguments of the call. */ |
| for (unsigned i = 0; i < callsite->argumentCount && i < nargs; i++) { |
| StackTypeSet *argTypes = callsite->argumentTypes[i]; |
| StackTypeSet *types = TypeScript::ArgTypes(calleeScript, i); |
| argTypes->addSubsetBarrier(cx, script, callsite->pc, types); |
| } |
| |
| /* Add void type for any formals in the callee not supplied at the call site. */ |
| for (unsigned i = callsite->argumentCount; i < nargs; i++) { |
| TypeSet *types = TypeScript::ArgTypes(calleeScript, i); |
| types->addType(cx, Type::UndefinedType()); |
| } |
| |
| StackTypeSet *thisTypes = TypeScript::ThisTypes(calleeScript); |
| HeapTypeSet *returnTypes = TypeScript::ReturnTypes(calleeScript); |
| |
| if (callsite->isNew) { |
| /* |
| * If the script does not return a value then the pushed value is the |
| * new object (typical case). Note that we don't model construction of |
| * the new value, which is done dynamically; we don't keep track of the |
| * possible 'new' types for a given prototype type object. |
| */ |
| thisTypes->addSubset(cx, returnTypes); |
| returnTypes->addFilterPrimitives(cx, callsite->returnTypes); |
| } else { |
| /* |
| * Add a binding for the return value of the call. We don't add a |
| * binding for the receiver object, as this is done with PropagateThis |
| * constraints added by the original JSOP_CALL* op. The type sets we |
| * manipulate here have lost any correlations between particular types |
| * in the 'this' and 'callee' sets, which we want to maintain for |
| * polymorphic JSOP_CALLPROP invocations. |
| */ |
| returnTypes->addSubset(cx, callsite->returnTypes); |
| } |
| } |
| |
| void |
| TypeConstraintPropagateThis::newType(JSContext *cx, TypeSet *source, Type type) |
| { |
| RootedScript script(cx, script_); |
| if (type.isUnknown() || type.isAnyObject()) { |
| /* |
| * The callee is unknown, make sure the call is monitored so we pick up |
| * possible this/callee correlations. This only comes into play for |
| * CALLPROP, for other calls we are past the type barrier and a |
| * TypeConstraintCall will also monitor the call. |
| */ |
| cx->compartment()->types.monitorBytecode(cx, script, callpc - script->code); |
| return; |
| } |
| |
| /* Ignore calls to natives, these will be handled by TypeConstraintCall. */ |
| RootedFunction callee(cx); |
| |
| if (type.isSingleObject()) { |
| RootedObject object(cx, type.singleObject()); |
| if (!object->is<JSFunction>() || !object->as<JSFunction>().isInterpreted()) |
| return; |
| callee = &object->as<JSFunction>(); |
| } else if (type.isTypeObject()) { |
| TypeObject *object = type.typeObject(); |
| if (!object->interpretedFunction) |
| return; |
| callee = object->interpretedFunction; |
| } else { |
| /* Ignore calls to primitives, these will go through a stub. */ |
| return; |
| } |
| |
| if (callee->isInterpretedLazy() && !callee->getOrCreateScript(cx)) |
| return; |
| |
| /* |
| * As callsite cloning is a hint, we must propagate to both the original |
| * and the clone. |
| */ |
| if (callee->nonLazyScript()->shouldCloneAtCallsite) { |
| callee = CloneCallee(cx, callee, script, callpc); |
| if (!callee) |
| return; |
| } |
| |
| if (!callee->nonLazyScript()->ensureHasTypes(cx)) |
| return; |
| |
| TypeSet *thisTypes = TypeScript::ThisTypes(callee->nonLazyScript()); |
| if (this->types) |
| this->types->addSubset(cx, thisTypes); |
| else |
| thisTypes->addType(cx, this->type); |
| } |
| |
| void |
| TypeConstraintArith::newType(JSContext *cx, TypeSet *source, Type type) |
| { |
| /* |
| * We only model a subset of the arithmetic behavior that is actually |
| * possible. The following need to be watched for at runtime: |
| * |
| * 1. Operations producing a double where no operand was a double. |
| * 2. Operations producing a string where no operand was a string (addition only). |
| * 3. Operations producing a value other than int/double/string. |
| */ |
| RootedScript script(cx, script_); |
| if (other) { |
| /* |
| * Addition operation, consider these cases: |
| * {int,bool} x {int,bool} -> int |
| * double x {int,bool,double} -> double |
| * string x any -> string |
| */ |
| if (type.isUnknown() || other->unknown()) { |
| target->addType(cx, Type::UnknownType()); |
| } else if (type.isPrimitive(JSVAL_TYPE_DOUBLE)) { |
| if (other->hasAnyFlag(TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL | |
| TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_BOOLEAN | |
| TYPE_FLAG_ANYOBJECT)) { |
| target->addType(cx, Type::DoubleType()); |
| } else if (other->getObjectCount() != 0) { |
| TypeDynamicResult(cx, script, pc, Type::DoubleType()); |
| } |
| } else if (type.isPrimitive(JSVAL_TYPE_STRING)) { |
| target->addType(cx, Type::StringType()); |
| } else if (other->hasAnyFlag(TYPE_FLAG_DOUBLE)) { |
| target->addType(cx, Type::DoubleType()); |
| } else if (other->hasAnyFlag(TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL | |
| TYPE_FLAG_INT32 | TYPE_FLAG_BOOLEAN | |
| TYPE_FLAG_ANYOBJECT)) { |
| target->addType(cx, Type::Int32Type()); |
| } else if (other->getObjectCount() != 0) { |
| TypeDynamicResult(cx, script, pc, Type::Int32Type()); |
| } |
| } else { |
| if (type.isUnknown()) |
| target->addType(cx, Type::UnknownType()); |
| else if (type.isPrimitive(JSVAL_TYPE_DOUBLE)) |
| target->addType(cx, Type::DoubleType()); |
| else if (!type.isAnyObject() && type.isObject()) |
| TypeDynamicResult(cx, script, pc, Type::Int32Type()); |
| else |
| target->addType(cx, Type::Int32Type()); |
| } |
| } |
| |
| void |
| TypeConstraintTransformThis::newType(JSContext *cx, TypeSet *source, Type type) |
| { |
| if (type.isUnknown() || type.isAnyObject() || type.isObject() || script_->strict) { |
| target->addType(cx, type); |
| return; |
| } |
| |
| RootedScript script(cx, script_); |
| |
| /* |
| * Builtin scripts do not adhere to normal assumptions about transforming |
| * 'this'. |
| */ |
| if (script->function() && script->function()->isSelfHostedBuiltin()) { |
| target->addType(cx, type); |
| return; |
| } |
| |
| /* |
| * Note: if |this| is null or undefined, the pushed value is the outer window. We |
| * can't use script->getGlobalType() here because it refers to the inner window. |
| */ |
| if (!script->compileAndGo || |
| type.isPrimitive(JSVAL_TYPE_NULL) || |
| type.isPrimitive(JSVAL_TYPE_UNDEFINED)) { |
| target->addType(cx, Type::UnknownType()); |
| return; |
| } |
| |
| TypeObject *object = NULL; |
| switch (type.primitive()) { |
| case JSVAL_TYPE_INT32: |
| case JSVAL_TYPE_DOUBLE: |
| object = TypeScript::StandardType(cx, JSProto_Number); |
| break; |
| case JSVAL_TYPE_BOOLEAN: |
| object = TypeScript::StandardType(cx, JSProto_Boolean); |
| break; |
| case JSVAL_TYPE_STRING: |
| object = TypeScript::StandardType(cx, JSProto_String); |
| break; |
| default: |
| return; |
| } |
| |
| if (!object) { |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| return; |
| } |
| |
| target->addType(cx, Type::ObjectType(object)); |
| } |
| |
| ///////////////////////////////////////////////////////////////////// |
| // Freeze constraints |
| ///////////////////////////////////////////////////////////////////// |
| |
| /* Constraint which triggers recompilation of a script if any type is added to a type set. */ |
| class TypeConstraintFreeze : public TypeConstraint |
| { |
| public: |
| RecompileInfo info; |
| |
| /* Whether a new type has already been added, triggering recompilation. */ |
| bool typeAdded; |
| |
| TypeConstraintFreeze(RecompileInfo info) |
| : info(info), typeAdded(false) |
| {} |
| |
| const char *kind() { return "freeze"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type) |
| { |
| if (typeAdded) |
| return; |
| |
| typeAdded = true; |
| cx->compartment()->types.addPendingRecompile(cx, info); |
| } |
| }; |
| |
| void |
| HeapTypeSet::addFreeze(JSContext *cx) |
| { |
| add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreeze>( |
| cx->compartment()->types.compiledInfo), false); |
| } |
| |
| static inline JSValueType |
| GetValueTypeFromTypeFlags(TypeFlags flags) |
| { |
| switch (flags) { |
| case TYPE_FLAG_UNDEFINED: |
| return JSVAL_TYPE_UNDEFINED; |
| case TYPE_FLAG_NULL: |
| return JSVAL_TYPE_NULL; |
| case TYPE_FLAG_BOOLEAN: |
| return JSVAL_TYPE_BOOLEAN; |
| case TYPE_FLAG_INT32: |
| return JSVAL_TYPE_INT32; |
| case (TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE): |
| return JSVAL_TYPE_DOUBLE; |
| case TYPE_FLAG_STRING: |
| return JSVAL_TYPE_STRING; |
| case TYPE_FLAG_LAZYARGS: |
| return JSVAL_TYPE_MAGIC; |
| case TYPE_FLAG_ANYOBJECT: |
| return JSVAL_TYPE_OBJECT; |
| default: |
| return JSVAL_TYPE_UNKNOWN; |
| } |
| } |
| |
| JSValueType |
| StackTypeSet::getKnownTypeTag() |
| { |
| TypeFlags flags = baseFlags(); |
| JSValueType type; |
| |
| if (baseObjectCount()) |
| type = flags ? JSVAL_TYPE_UNKNOWN : JSVAL_TYPE_OBJECT; |
| else |
| type = GetValueTypeFromTypeFlags(flags); |
| |
| /* |
| * If the type set is totally empty then it will be treated as unknown, |
| * but we still need to record the dependency as adding a new type can give |
| * it a definite type tag. This is not needed if there are enough types |
| * that the exact tag is unknown, as it will stay unknown as more types are |
| * added to the set. |
| */ |
| DebugOnly<bool> empty = flags == 0 && baseObjectCount() == 0; |
| JS_ASSERT_IF(empty, type == JSVAL_TYPE_UNKNOWN); |
| |
| return type; |
| } |
| |
| JSValueType |
| HeapTypeSet::getKnownTypeTag(JSContext *cx) |
| { |
| TypeFlags flags = baseFlags(); |
| JSValueType type; |
| |
| if (baseObjectCount()) |
| type = flags ? JSVAL_TYPE_UNKNOWN : JSVAL_TYPE_OBJECT; |
| else |
| type = GetValueTypeFromTypeFlags(flags); |
| |
| if (type != JSVAL_TYPE_UNKNOWN) |
| addFreeze(cx); |
| |
| /* |
| * If the type set is totally empty then it will be treated as unknown, |
| * but we still need to record the dependency as adding a new type can give |
| * it a definite type tag. This is not needed if there are enough types |
| * that the exact tag is unknown, as it will stay unknown as more types are |
| * added to the set. |
| */ |
| DebugOnly<bool> empty = flags == 0 && baseObjectCount() == 0; |
| JS_ASSERT_IF(empty, type == JSVAL_TYPE_UNKNOWN); |
| |
| return type; |
| } |
| |
| bool |
| StackTypeSet::mightBeType(JSValueType type) |
| { |
| if (unknown()) |
| return true; |
| |
| if (type == JSVAL_TYPE_OBJECT) |
| return unknownObject() || baseObjectCount() != 0; |
| |
| return baseFlags() & PrimitiveTypeFlag(type); |
| } |
| |
| /* Constraint which triggers recompilation if an object acquires particular flags. */ |
| class TypeConstraintFreezeObjectFlags : public TypeConstraint |
| { |
| public: |
| RecompileInfo info; |
| |
| /* Flags we are watching for on this object. */ |
| TypeObjectFlags flags; |
| |
| /* Whether the object has already been marked as having one of the flags. */ |
| bool marked; |
| |
| TypeConstraintFreezeObjectFlags(RecompileInfo info, TypeObjectFlags flags) |
| : info(info), flags(flags), |
| marked(false) |
| {} |
| |
| const char *kind() { return "freezeObjectFlags"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type) {} |
| |
| void newObjectState(JSContext *cx, TypeObject *object, bool force) |
| { |
| if (!marked && (object->hasAnyFlags(flags) || (!flags && force))) { |
| marked = true; |
| cx->compartment()->types.addPendingRecompile(cx, info); |
| } |
| } |
| }; |
| |
| bool |
| StackTypeSet::hasObjectFlags(JSContext *cx, TypeObjectFlags flags) |
| { |
| if (unknownObject()) |
| return true; |
| |
| /* |
| * Treat type sets containing no objects as having all object flags, |
| * to spare callers from having to check this. |
| */ |
| if (baseObjectCount() == 0) |
| return true; |
| |
| RootedObject obj(cx); |
| unsigned count = getObjectCount(); |
| for (unsigned i = 0; i < count; i++) { |
| TypeObject *object = getTypeObject(i); |
| if (!object) { |
| if (!(obj = getSingleObject(i))) |
| continue; |
| if (!(object = obj->getType(cx))) |
| return true; |
| } |
| if (object->hasAnyFlags(flags)) |
| return true; |
| |
| /* |
| * Add a constraint on the the object to pick up changes in the |
| * object's properties. |
| */ |
| TypeSet *types = object->getProperty(cx, JSID_EMPTY, false); |
| if (!types) |
| return true; |
| types->add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeObjectFlags>( |
| cx->compartment()->types.compiledInfo, flags), false); |
| } |
| |
| return false; |
| } |
| |
| bool |
| HeapTypeSet::HasObjectFlags(JSContext *cx, TypeObject *object, TypeObjectFlags flags) |
| { |
| if (object->hasAnyFlags(flags)) |
| return true; |
| |
| HeapTypeSet *types = object->getProperty(cx, JSID_EMPTY, false); |
| if (!types) |
| return true; |
| types->add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeObjectFlags>( |
| cx->compartment()->types.compiledInfo, flags), false); |
| return false; |
| } |
| |
| static inline void |
| ObjectStateChange(JSContext *cx, TypeObject *object, bool markingUnknown, bool force) |
| { |
| if (object->unknownProperties()) |
| return; |
| |
| /* All constraints listening to state changes are on the empty id. */ |
| TypeSet *types = object->maybeGetProperty(JSID_EMPTY, cx); |
| |
| /* Mark as unknown after getting the types, to avoid assertion. */ |
| if (markingUnknown) |
| object->flags |= OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES; |
| |
| if (types) { |
| TypeConstraint *constraint = types->constraintList; |
| while (constraint) { |
| constraint->newObjectState(cx, object, force); |
| constraint = constraint->next; |
| } |
| } |
| } |
| |
| void |
| HeapTypeSet::WatchObjectStateChange(JSContext *cx, TypeObject *obj) |
| { |
| JS_ASSERT(!obj->unknownProperties()); |
| HeapTypeSet *types = obj->getProperty(cx, JSID_EMPTY, false); |
| if (!types) |
| return; |
| |
| /* |
| * Use a constraint which triggers recompilation when markStateChange is |
| * called, which will set 'force' to true. |
| */ |
| types->add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeObjectFlags>( |
| cx->compartment()->types.compiledInfo, |
| 0)); |
| } |
| |
| class TypeConstraintFreezeOwnProperty : public TypeConstraint |
| { |
| public: |
| RecompileInfo info; |
| |
| bool updated; |
| bool configurable; |
| |
| TypeConstraintFreezeOwnProperty(RecompileInfo info, bool configurable) |
| : info(info), updated(false), configurable(configurable) |
| {} |
| |
| const char *kind() { return "freezeOwnProperty"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type) {} |
| |
| void newPropertyState(JSContext *cx, TypeSet *source) |
| { |
| if (updated) |
| return; |
| if (source->ownProperty(configurable)) { |
| updated = true; |
| cx->compartment()->types.addPendingRecompile(cx, info); |
| } |
| } |
| }; |
| |
| static void |
| CheckNewScriptProperties(JSContext *cx, HandleTypeObject type, HandleFunction fun); |
| |
| bool |
| HeapTypeSet::isOwnProperty(JSContext *cx, TypeObject *object, bool configurable) |
| { |
| /* |
| * Everywhere compiled code depends on definite properties associated with |
| * a type object's newScript, we need to make sure there are constraints |
| * in place which will mark those properties as configured should the |
| * definite properties be invalidated. |
| */ |
| if (object->flags & OBJECT_FLAG_NEW_SCRIPT_REGENERATE) { |
| if (object->newScript) { |
| Rooted<TypeObject*> typeObj(cx, object); |
| RootedFunction fun(cx, object->newScript->fun); |
| CheckNewScriptProperties(cx, typeObj, fun); |
| } else { |
| JS_ASSERT(object->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED); |
| object->flags &= ~OBJECT_FLAG_NEW_SCRIPT_REGENERATE; |
| } |
| } |
| |
| if (ownProperty(configurable)) |
| return true; |
| |
| add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeOwnProperty>( |
| cx->compartment()->types.compiledInfo, |
| configurable), false); |
| return false; |
| } |
| |
| bool |
| HeapTypeSet::knownNonEmpty(JSContext *cx) |
| { |
| if (baseFlags() != 0 || baseObjectCount() != 0) |
| return true; |
| |
| addFreeze(cx); |
| |
| return false; |
| } |
| |
| bool |
| StackTypeSet::knownNonStringPrimitive() |
| { |
| TypeFlags flags = baseFlags(); |
| |
| if (baseObjectCount() > 0) |
| return false; |
| |
| if (flags >= TYPE_FLAG_STRING) |
| return false; |
| |
| if (baseFlags() == 0) |
| return false; |
| return true; |
| } |
| |
| bool |
| StackTypeSet::filtersType(const StackTypeSet *other, Type filteredType) const |
| { |
| if (other->unknown()) |
| return unknown(); |
| |
| for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) { |
| Type type = Type::PrimitiveType(TypeFlagPrimitive(flag)); |
| if (type != filteredType && other->hasType(type) && !hasType(type)) |
| return false; |
| } |
| |
| if (other->unknownObject()) |
| return unknownObject(); |
| |
| for (size_t i = 0; i < other->getObjectCount(); i++) { |
| TypeObjectKey *key = other->getObject(i); |
| if (key) { |
| Type type = Type::ObjectType(key); |
| if (type != filteredType && !hasType(type)) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| StackTypeSet::DoubleConversion |
| StackTypeSet::convertDoubleElements(JSContext *cx) |
| { |
| if (unknownObject() || !getObjectCount()) |
| return AmbiguousDoubleConversion; |
| |
| bool alwaysConvert = true; |
| bool maybeConvert = false; |
| bool dontConvert = false; |
| |
| for (unsigned i = 0; i < getObjectCount(); i++) { |
| TypeObject *type = getTypeObject(i); |
| if (!type) { |
| if (JSObject *obj = getSingleObject(i)) { |
| type = obj->getType(cx); |
| if (!type) |
| return AmbiguousDoubleConversion; |
| } else { |
| continue; |
| } |
| } |
| |
| if (type->unknownProperties()) { |
| alwaysConvert = false; |
| continue; |
| } |
| |
| HeapTypeSet *types = type->getProperty(cx, JSID_VOID, false); |
| if (!types) |
| return AmbiguousDoubleConversion; |
| |
| types->addFreeze(cx); |
| |
| // We can't convert to double elements for objects which do not have |
| // double in their element types (as the conversion may render the type |
| // information incorrect), nor for non-array objects (as their elements |
| // may point to emptyObjectElements, which cannot be converted). |
| if (!types->hasType(Type::DoubleType()) || type->clasp != &ArrayClass) { |
| dontConvert = true; |
| alwaysConvert = false; |
| continue; |
| } |
| |
| // Only bother with converting known packed arrays whose possible |
| // element types are int or double. Other arrays require type tests |
| // when elements are accessed regardless of the conversion. |
| if (types->getKnownTypeTag(cx) == JSVAL_TYPE_DOUBLE && |
| !HeapTypeSet::HasObjectFlags(cx, type, OBJECT_FLAG_NON_PACKED)) |
| { |
| maybeConvert = true; |
| } else { |
| alwaysConvert = false; |
| } |
| } |
| |
| JS_ASSERT_IF(alwaysConvert, maybeConvert); |
| |
| if (maybeConvert && dontConvert) |
| return AmbiguousDoubleConversion; |
| if (alwaysConvert) |
| return AlwaysConvertToDoubles; |
| if (maybeConvert) |
| return MaybeConvertToDoubles; |
| return DontConvertToDoubles; |
| } |
| |
| bool |
| HeapTypeSet::knownSubset(JSContext *cx, TypeSet *other) |
| { |
| JS_ASSERT(!other->constraintsPurged()); |
| |
| if (!isSubset(other)) |
| return false; |
| |
| addFreeze(cx); |
| |
| return true; |
| } |
| |
| Class * |
| StackTypeSet::getKnownClass() |
| { |
| if (unknownObject()) |
| return NULL; |
| |
| Class *clasp = NULL; |
| unsigned count = getObjectCount(); |
| |
| for (unsigned i = 0; i < count; i++) { |
| Class *nclasp; |
| if (JSObject *object = getSingleObject(i)) |
| nclasp = object->getClass(); |
| else if (TypeObject *object = getTypeObject(i)) |
| nclasp = object->clasp; |
| else |
| continue; |
| |
| if (clasp && clasp != nclasp) |
| return NULL; |
| clasp = nclasp; |
| } |
| |
| return clasp; |
| } |
| |
| int |
| StackTypeSet::getTypedArrayType() |
| { |
| Class *clasp = getKnownClass(); |
| |
| if (clasp && IsTypedArrayClass(clasp)) |
| return clasp - &TypedArray::classes[0]; |
| return TypedArray::TYPE_MAX; |
| } |
| |
| bool |
| StackTypeSet::isDOMClass() |
| { |
| if (unknownObject()) |
| return false; |
| |
| unsigned count = getObjectCount(); |
| for (unsigned i = 0; i < count; i++) { |
| Class *clasp; |
| if (JSObject *object = getSingleObject(i)) |
| clasp = object->getClass(); |
| else if (TypeObject *object = getTypeObject(i)) |
| clasp = object->clasp; |
| else |
| continue; |
| |
| if (!(clasp->flags & JSCLASS_IS_DOMJSCLASS)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| JSObject * |
| StackTypeSet::getCommonPrototype() |
| { |
| if (unknownObject()) |
| return NULL; |
| |
| JSObject *proto = NULL; |
| unsigned count = getObjectCount(); |
| |
| for (unsigned i = 0; i < count; i++) { |
| TaggedProto nproto; |
| if (JSObject *object = getSingleObject(i)) |
| nproto = object->getProto(); |
| else if (TypeObject *object = getTypeObject(i)) |
| nproto = object->proto.get(); |
| else |
| continue; |
| |
| if (proto) { |
| if (nproto != proto) |
| return NULL; |
| } else { |
| if (!nproto.isObject()) |
| return NULL; |
| proto = nproto.toObject(); |
| } |
| } |
| |
| return proto; |
| } |
| |
| JSObject * |
| StackTypeSet::getSingleton() |
| { |
| if (baseFlags() != 0 || baseObjectCount() != 1) |
| return NULL; |
| |
| return getSingleObject(0); |
| } |
| |
| JSObject * |
| HeapTypeSet::getSingleton(JSContext *cx) |
| { |
| if (baseFlags() != 0 || baseObjectCount() != 1) |
| return NULL; |
| |
| RootedObject obj(cx, getSingleObject(0)); |
| |
| if (obj) |
| addFreeze(cx); |
| |
| return obj; |
| } |
| |
| bool |
| HeapTypeSet::needsBarrier(JSContext *cx) |
| { |
| bool result = unknownObject() |
| || getObjectCount() > 0 |
| || hasAnyFlag(TYPE_FLAG_STRING); |
| if (!result) |
| addFreeze(cx); |
| return result; |
| } |
| |
| bool |
| StackTypeSet::propertyNeedsBarrier(JSContext *cx, jsid id) |
| { |
| RootedId typeId(cx, IdToTypeId(id)); |
| |
| if (unknownObject()) |
| return true; |
| |
| for (unsigned i = 0; i < getObjectCount(); i++) { |
| if (getSingleObject(i)) |
| return true; |
| |
| if (types::TypeObject *otype = getTypeObject(i)) { |
| if (otype->unknownProperties()) |
| return true; |
| |
| if (types::HeapTypeSet *propTypes = otype->maybeGetProperty(typeId, cx)) { |
| if (propTypes->needsBarrier(cx)) |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| /* |
| * Force recompilation of any jitcode for the script, or of any other script |
| * which this script was inlined into. |
| */ |
| static inline void |
| AddPendingRecompile(JSContext *cx, JSScript *script) |
| { |
| cx->compartment()->types.addPendingRecompile(cx, script); |
| } |
| |
| /* |
| * As for TypeConstraintFreeze, but describes an implicit freeze constraint |
| * added for stack types within a script. Applies to all compilations of the |
| * script, not just a single one. |
| */ |
| class TypeConstraintFreezeStack : public TypeConstraint |
| { |
| JSScript *script_; |
| |
| public: |
| TypeConstraintFreezeStack(JSScript *script) |
| : script_(script) |
| {} |
| |
| const char *kind() { return "freezeStack"; } |
| |
| void newType(JSContext *cx, TypeSet *source, Type type) |
| { |
| /* |
| * Unlike TypeConstraintFreeze, triggering this constraint once does |
| * not disable it on future changes to the type set. |
| */ |
| AddPendingRecompile(cx, script_); |
| } |
| }; |
| |
| ///////////////////////////////////////////////////////////////////// |
| // TypeCompartment |
| ///////////////////////////////////////////////////////////////////// |
| |
| TypeCompartment::TypeCompartment() |
| { |
| PodZero(this); |
| compiledInfo.outputIndex = RecompileInfo::NoCompilerRunning; |
| } |
| |
| void |
| TypeZone::init(JSContext *cx) |
| { |
| if (!cx || |
| !cx->hasOption(JSOPTION_TYPE_INFERENCE) || |
| !cx->runtime()->jitSupportsFloatingPoint) |
| { |
| return; |
| } |
| |
| inferenceEnabled = true; |
| } |
| |
| TypeObject * |
| TypeCompartment::newTypeObject(JSContext *cx, Class *clasp, Handle<TaggedProto> proto, bool unknown) |
| { |
| JS_ASSERT_IF(proto.isObject(), cx->compartment() == proto.toObject()->compartment()); |
| |
| TypeObject *object = gc::NewGCThing<TypeObject, CanGC>(cx, gc::FINALIZE_TYPE_OBJECT, |
| sizeof(TypeObject), gc::TenuredHeap); |
| if (!object) |
| return NULL; |
| new(object) TypeObject(clasp, proto, clasp == &JSFunction::class_, unknown); |
| |
| if (!cx->typeInferenceEnabled()) |
| object->flags |= OBJECT_FLAG_UNKNOWN_MASK; |
| |
| return object; |
| } |
| |
| static inline jsbytecode * |
| PreviousOpcode(HandleScript script, jsbytecode *pc) |
| { |
| ScriptAnalysis *analysis = script->analysis(); |
| JS_ASSERT(analysis->maybeCode(pc)); |
| |
| if (pc == script->code) |
| return NULL; |
| |
| for (pc--;; pc--) { |
| if (analysis->maybeCode(pc)) |
| break; |
| } |
| |
| return pc; |
| } |
| |
| /* |
| * If pc is an array initializer within an outer multidimensional array |
| * initializer, find the opcode of the previous newarray. NULL otherwise. |
| */ |
| static inline jsbytecode * |
| FindPreviousInnerInitializer(HandleScript script, jsbytecode *initpc) |
| { |
| if (!script->hasAnalysis()) |
| return NULL; |
| |
| if (!script->analysis()->maybeCode(initpc)) |
| return NULL; |
| |
| /* |
| * Pattern match the following bytecode, which will appear between |
| * adjacent initializer elements: |
| * |
| * endinit (for previous initializer) |
| * initelem_array (for previous initializer) |
| * newarray |
| */ |
| |
| if (*initpc != JSOP_NEWARRAY) |
| return NULL; |
| |
| jsbytecode *last = PreviousOpcode(script, initpc); |
| if (!last || *last != JSOP_INITELEM_ARRAY) |
| return NULL; |
| |
| last = PreviousOpcode(script, last); |
| if (!last || *last != JSOP_ENDINIT) |
| return NULL; |
| |
| /* |
| * Find the start of the previous initializer. Keep track of initializer |
| * depth to skip over inner initializers within the previous one (e.g. for |
| * arrays with three or more dimensions). |
| */ |
| size_t initDepth = 0; |
| jsbytecode *previnit; |
| for (previnit = last; previnit; previnit = PreviousOpcode(script, previnit)) { |
| if (*previnit == JSOP_ENDINIT) |
| initDepth++; |
| if (*previnit == JSOP_NEWINIT || |
| *previnit == JSOP_NEWARRAY || |
| *previnit == JSOP_NEWOBJECT) |
| { |
| if (--initDepth == 0) |
| break; |
| } |
| } |
| |
| if (!previnit || *previnit != JSOP_NEWARRAY) |
| return NULL; |
| |
| return previnit; |
| } |
| |
| TypeObject * |
| TypeCompartment::addAllocationSiteTypeObject(JSContext *cx, AllocationSiteKey key) |
| { |
| AutoEnterAnalysis enter(cx); |
| |
| if (!allocationSiteTable) { |
| allocationSiteTable = cx->new_<AllocationSiteTable>(); |
| if (!allocationSiteTable || !allocationSiteTable->init()) { |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| return NULL; |
| } |
| } |
| |
| AllocationSiteTable::AddPtr p = allocationSiteTable->lookupForAdd(key); |
| JS_ASSERT(!p); |
| |
| TypeObject *res = NULL; |
| |
| /* |
| * If this is an array initializer nested in another array initializer, |
| * try to reuse the type objects from earlier elements to avoid |
| * distinguishing elements of the outer array unnecessarily. |
| */ |
| jsbytecode *pc = key.script->code + key.offset; |
| RootedScript keyScript(cx, key.script); |
| jsbytecode *prev = FindPreviousInnerInitializer(keyScript, pc); |
| if (prev) { |
| AllocationSiteKey nkey; |
| nkey.script = key.script; |
| nkey.offset = prev - key.script->code; |
| nkey.kind = JSProto_Array; |
| |
| AllocationSiteTable::Ptr p = cx->compartment()->types.allocationSiteTable->lookup(nkey); |
| if (p) |
| res = p->value; |
| } |
| |
| if (!res) { |
| RootedObject proto(cx); |
| if (!js_GetClassPrototype(cx, key.kind, &proto, NULL)) |
| return NULL; |
| |
| Rooted<TaggedProto> tagged(cx, TaggedProto(proto)); |
| res = newTypeObject(cx, GetClassForProtoKey(key.kind), tagged); |
| if (!res) { |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| return NULL; |
| } |
| key.script = keyScript; |
| } |
| |
| if (JSOp(*pc) == JSOP_NEWOBJECT) { |
| /* |
| * This object is always constructed the same way and will not be |
| * observed by other code before all properties have been added. Mark |
| * all the properties as definite properties of the object. |
| */ |
| RootedObject baseobj(cx, key.script->getObject(GET_UINT32_INDEX(pc))); |
| |
| if (!res->addDefiniteProperties(cx, baseobj)) |
| return NULL; |
| } |
| |
| if (!allocationSiteTable->add(p, key, res)) { |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| return NULL; |
| } |
| |
| return res; |
| } |
| |
| static inline jsid |
| GetAtomId(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset) |
| { |
| PropertyName *name = script->getName(GET_UINT32_INDEX(pc + offset)); |
| return IdToTypeId(NameToId(name)); |
| } |
| |
| bool |
| types::UseNewType(JSContext *cx, JSScript *script, jsbytecode *pc) |
| { |
| JS_ASSERT(cx->typeInferenceEnabled()); |
| |
| /* |
| * Make a heuristic guess at a use of JSOP_NEW that the constructed object |
| * should have a fresh type object. We do this when the NEW is immediately |
| * followed by a simple assignment to an object's .prototype field. |
| * This is designed to catch common patterns for subclassing in JS: |
| * |
| * function Super() { ... } |
| * function Sub1() { ... } |
| * function Sub2() { ... } |
| * |
| * Sub1.prototype = new Super(); |
| * Sub2.prototype = new Super(); |
| * |
| * Using distinct type objects for the particular prototypes of Sub1 and |
| * Sub2 lets us continue to distinguish the two subclasses and any extra |
| * properties added to those prototype objects. |
| */ |
| if (JSOp(*pc) != JSOP_NEW) |
| return false; |
| pc += JSOP_NEW_LENGTH; |
| if (JSOp(*pc) == JSOP_SETPROP) { |
| jsid id = GetAtomId(cx, script, pc, 0); |
| if (id == id_prototype(cx)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| NewObjectKind |
| types::UseNewTypeForInitializer(JSContext *cx, JSScript *script, jsbytecode *pc, JSProtoKey key) |
| { |
| /* |
| * Objects created outside loops in global and eval scripts should have |
| * singleton types. For now this is only done for plain objects and typed |
| * arrays, but not normal arrays. |
| */ |
| |
| if (!cx->typeInferenceEnabled() || (script->function() && !script->treatAsRunOnce)) |
| return GenericObject; |
| |
| if (key != JSProto_Object && !(key >= JSProto_Int8Array && key <= JSProto_Uint8ClampedArray)) |
| return GenericObject; |
| |
| /* |
| * All loops in the script will have a JSTRY_ITER or JSTRY_LOOP try note |
| * indicating their boundary. |
| */ |
| |
| if (!script->hasTrynotes()) |
| return SingletonObject; |
| |
| unsigned offset = pc - script->code; |
| |
| JSTryNote *tn = script->trynotes()->vector; |
| JSTryNote *tnlimit = tn + script->trynotes()->length; |
| for (; tn < tnlimit; tn++) { |
| if (tn->kind != JSTRY_ITER && tn->kind != JSTRY_LOOP) |
| continue; |
| |
| unsigned startOffset = script->mainOffset + tn->start; |
| unsigned endOffset = startOffset + tn->length; |
| |
| if (offset >= startOffset && offset < endOffset) |
| return GenericObject; |
| } |
| |
| return SingletonObject; |
| } |
| |
| NewObjectKind |
| types::UseNewTypeForInitializer(JSContext *cx, JSScript *script, jsbytecode *pc, Class *clasp) |
| { |
| return UseNewTypeForInitializer(cx, script, pc, JSCLASS_CACHED_PROTO_KEY(clasp)); |
| } |
| |
| static inline bool |
| ClassCanHaveExtraProperties(Class *clasp) |
| { |
| JS_ASSERT(clasp->resolve); |
| return clasp->resolve != JS_ResolveStub || clasp->ops.lookupGeneric || clasp->ops.getGeneric; |
| } |
| |
| static inline bool |
| PrototypeHasIndexedProperty(JSContext *cx, JSObject *obj) |
| { |
| do { |
| TypeObject *type = obj->getType(cx); |
| if (!type) |
| return true; |
| if (ClassCanHaveExtraProperties(type->clasp)) |
| return true; |
| if (type->unknownProperties()) |
| return true; |
| HeapTypeSet *indexTypes = type->getProperty(cx, JSID_VOID, false); |
| if (!indexTypes || indexTypes->isOwnProperty(cx, type, true) || indexTypes->knownNonEmpty(cx)) |
| return true; |
| obj = obj->getProto(); |
| } while (obj); |
| |
| return false; |
| } |
| |
| bool |
| types::ArrayPrototypeHasIndexedProperty(JSContext *cx, HandleScript script) |
| { |
| if (!cx->typeInferenceEnabled() || !script->compileAndGo) |
| return true; |
| |
| JSObject *proto = script->global().getOrCreateArrayPrototype(cx); |
| if (!proto) |
| return true; |
| |
| return PrototypeHasIndexedProperty(cx, proto); |
| } |
| |
| bool |
| types::TypeCanHaveExtraIndexedProperties(JSContext *cx, StackTypeSet *types) |
| { |
| Class *clasp = types->getKnownClass(); |
| |
| // Note: typed arrays have indexed properties not accounted for by type |
| // information, though these are all in bounds and will be accounted for |
| // by JIT paths. |
| if (!clasp || (ClassCanHaveExtraProperties(clasp) && !IsTypedArrayClass(clasp))) |
| return true; |
| |
| if (types->hasObjectFlags(cx, types::OBJECT_FLAG_SPARSE_INDEXES)) |
| return true; |
| |
| JSObject *proto = types->getCommonPrototype(); |
| if (!proto) |
| return true; |
| |
| return PrototypeHasIndexedProperty(cx, proto); |
| } |
| |
| bool |
| TypeCompartment::growPendingArray(JSContext *cx) |
| { |
| unsigned newCapacity = js::Max(unsigned(100), pendingCapacity * 2); |
| PendingWork *newArray = js_pod_calloc<PendingWork>(newCapacity); |
| if (!newArray) { |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| return false; |
| } |
| |
| PodCopy(newArray, pendingArray, pendingCount); |
| js_free(pendingArray); |
| |
| pendingArray = newArray; |
| pendingCapacity = newCapacity; |
| |
| return true; |
| } |
| |
| void |
| TypeCompartment::processPendingRecompiles(FreeOp *fop) |
| { |
| if (!pendingRecompiles) |
| return; |
| |
| /* Steal the list of scripts to recompile, else we will try to recursively recompile them. */ |
| Vector<RecompileInfo> *pending = pendingRecompiles; |
| pendingRecompiles = NULL; |
| |
| JS_ASSERT(!pending->empty()); |
| |
| #ifdef JS_ION |
| jit::Invalidate(*this, fop, *pending); |
| #endif |
| |
| fop->delete_(pending); |
| } |
| |
| void |
| TypeCompartment::setPendingNukeTypes(JSContext *cx) |
| { |
| TypeZone *zone = &compartment()->zone()->types; |
| if (!zone->pendingNukeTypes) { |
| if (cx->compartment()) |
| js_ReportOutOfMemory(cx); |
| zone->pendingNukeTypes = true; |
| } |
| } |
| |
| void |
| TypeZone::setPendingNukeTypes() |
| { |
| pendingNukeTypes = true; |
| } |
| |
| void |
| TypeZone::nukeTypes(FreeOp *fop) |
| { |
| /* |
| * This is the usual response if we encounter an OOM while adding a type |
| * or resolving type constraints. Reset the compartment to not use type |
| * inference, and recompile all scripts. |
| * |
| * Because of the nature of constraint-based analysis (add constraints, and |
| * iterate them until reaching a fixpoint), we can't undo an add of a type set, |
| * and merely aborting the operation which triggered the add will not be |
| * sufficient for correct behavior as we will be leaving the types in an |
| * inconsistent state. |
| */ |
| JS_ASSERT(pendingNukeTypes); |
| |
| for (CompartmentsInZoneIter comp(zone()); !comp.done(); comp.next()) { |
| if (comp->types.pendingRecompiles) { |
| fop->free_(comp->types.pendingRecompiles); |
| comp->types.pendingRecompiles = NULL; |
| } |
| } |
| |
| inferenceEnabled = false; |
| |
| #ifdef JS_ION |
| jit::InvalidateAll(fop, zone()); |
| |
| /* Throw away all JIT code in the compartment, but leave everything else alone. */ |
| |
| for (gc::CellIter i(zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) { |
| JSScript *script = i.get<JSScript>(); |
| jit::FinishInvalidation(fop, script); |
| } |
| #endif /* JS_ION */ |
| |
| pendingNukeTypes = false; |
| } |
| |
| void |
| TypeCompartment::addPendingRecompile(JSContext *cx, const RecompileInfo &info) |
| { |
| CompilerOutput *co = info.compilerOutput(cx); |
| if (!co) |
| return; |
| |
| if (co->pendingRecompilation) |
| return; |
| |
| if (co->isValid()) |
| CancelOffThreadIonCompile(cx->compartment(), co->script); |
| |
| if (compiledInfo.outputIndex == info.outputIndex) { |
| /* Tell Ion to discard generated code when it's done. */ |
| JS_ASSERT(compiledInfo.outputIndex != RecompileInfo::NoCompilerRunning); |
| JS_ASSERT(co->kind() == CompilerOutput::Ion || co->kind() == CompilerOutput::ParallelIon); |
| co->invalidate(); |
| return; |
| } |
| |
| if (!co->isValid()) { |
| JS_ASSERT(co->script == NULL); |
| return; |
| } |
| |
| #if defined(JS_ION) |
| if (!co->script->hasAnyIonScript()) { |
| /* Scripts which haven't been compiled yet don't need to be recompiled. */ |
| return; |
| } |
| #endif |
| |
| if (!pendingRecompiles) { |
| pendingRecompiles = cx->new_< Vector<RecompileInfo> >(cx); |
| if (!pendingRecompiles) { |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| return; |
| } |
| } |
| |
| #if DEBUG |
| for (size_t i = 0; i < pendingRecompiles->length(); i++) { |
| RecompileInfo pr = (*pendingRecompiles)[i]; |
| JS_ASSERT(info.outputIndex != pr.outputIndex); |
| } |
| #endif |
| |
| if (!pendingRecompiles->append(info)) { |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| return; |
| } |
| |
| InferSpew(ISpewOps, "addPendingRecompile: %p:%s:%d", co->script, co->script->filename(), co->script->lineno); |
| |
| co->setPendingRecompilation(); |
| } |
| |
| void |
| TypeCompartment::addPendingRecompile(JSContext *cx, JSScript *script) |
| { |
| JS_ASSERT(script); |
| if (!constrainedOutputs) |
| return; |
| |
| #ifdef JS_ION |
| CancelOffThreadIonCompile(cx->compartment(), script); |
| |
| // Let the script warm up again before attempting another compile. |
| if (jit::IsBaselineEnabled(cx)) |
| script->resetUseCount(); |
| |
| if (script->hasIonScript()) |
| addPendingRecompile(cx, script->ionScript()->recompileInfo()); |
| |
| if (script->hasParallelIonScript()) |
| addPendingRecompile(cx, script->parallelIonScript()->recompileInfo()); |
| #endif |
| |
| /* |
| * Remind Ion not to save the compile code if generating type |
| * inference information mid-compilation causes an invalidation of the |
| * script being compiled. |
| */ |
| if (compiledInfo.outputIndex != RecompileInfo::NoCompilerRunning) { |
| CompilerOutput *co = compiledInfo.compilerOutput(cx); |
| if (!co) { |
| if (script->compartment() != cx->compartment()) |
| MOZ_CRASH(); |
| return; |
| } |
| |
| JS_ASSERT(co->kind() == CompilerOutput::Ion || co->kind() == CompilerOutput::ParallelIon); |
| |
| if (co->script == script) |
| co->invalidate(); |
| } |
| |
| /* |
| * When one script is inlined into another the caller listens to state |
| * changes on the callee's script, so trigger these to force recompilation |
| * of any such callers. |
| */ |
| if (script->function() && !script->function()->hasLazyType()) |
| ObjectStateChange(cx, script->function()->type(), false, true); |
| } |
| |
| void |
| TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32_t offset, |
| bool returnOnly) |
| { |
| if (!script->ensureRanInference(cx)) |
| return; |
| |
| ScriptAnalysis *analysis = script->analysis(); |
| jsbytecode *pc = script->code + offset; |
| |
| JS_ASSERT_IF(returnOnly, js_CodeSpec[*pc].format & JOF_INVOKE); |
| |
| Bytecode &code = analysis->getCode(pc); |
| |
| if (returnOnly ? code.monitoredTypesReturn : code.monitoredTypes) |
| return; |
| |
| InferSpew(ISpewOps, "addMonitorNeeded:%s #%u:%05u", |
| returnOnly ? " returnOnly" : "", script->id(), offset); |
| |
| /* Dynamically monitor this call to keep track of its result types. */ |
| if (js_CodeSpec[*pc].format & JOF_INVOKE) |
| code.monitoredTypesReturn = true; |
| |
| if (returnOnly) |
| return; |
| |
| code.monitoredTypes = true; |
| |
| AddPendingRecompile(cx, script); |
| } |
| |
| void |
| TypeCompartment::markSetsUnknown(JSContext *cx, TypeObject *target) |
| { |
| JS_ASSERT(this == &cx->compartment()->types); |
| JS_ASSERT(!(target->flags & OBJECT_FLAG_SETS_MARKED_UNKNOWN)); |
| JS_ASSERT(!target->singleton); |
| JS_ASSERT(target->unknownProperties()); |
| target->flags |= OBJECT_FLAG_SETS_MARKED_UNKNOWN; |
| |
| AutoEnterAnalysis enter(cx); |
| |
| /* |
| * Mark both persistent and transient type sets which contain obj as having |
| * a generic object type. It is not sufficient to mark just the persistent |
| * sets, as analysis of individual opcodes can pull type objects from |
| * static information (like initializer objects at various offsets). |
| * |
| * We make a list of properties to update and fix them afterwards, as adding |
| * types can't be done while iterating over cells as it can potentially make |
| * new type objects as well or trigger GC. |
| */ |
| Vector<TypeSet *> pending(cx); |
| for (gc::CellIter i(cx->zone(), gc::FINALIZE_TYPE_OBJECT); !i.done(); i.next()) { |
| TypeObject *object = i.get<TypeObject>(); |
| unsigned count = object->getPropertyCount(); |
| for (unsigned i = 0; i < count; i++) { |
| Property *prop = object->getProperty(i); |
| if (prop && prop->types.hasType(Type::ObjectType(target))) { |
| if (!pending.append(&prop->types)) |
| cx->compartment()->types.setPendingNukeTypes(cx); |
| } |
| } |
| } |
| |
| for (unsigned i = 0; i < pending.length(); i++) |
| pending[i]->addType(cx, Type::AnyObjectType()); |
| |
| for (gc::CellIter i(cx->zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) { |
| RootedScript script(cx, i.get<JSScript>()); |
| if (script->types) { |
| unsigned count = TypeScript::NumTypeSets(script); |
| TypeSet *typeArray = script->types->typeArray(); |
| for (unsigned i = 0; i < count; i++) { |
| if (typeArray[i].hasType(Type::ObjectType(target))) |
| typeArray[i].addType(cx, Type::AnyObjectType()); |
| } |
| } |
| if (script->hasAnalysis() && script->analysis()->ranInference()) { |
| for (unsigned i = 0; i < script->length; i++) { |
| if (!script->analysis()->maybeCode(i)) |
| continue; |
| jsbytecode *pc = script->code + i; |
| unsigned defCount = GetDefCount(script, i); |
| if (ExtendedDef(pc)) |
| defCount++; |
| for (unsigned j = 0; j < defCount; j++) { |
| TypeSet *types = script->analysis()->pushedTypes(pc, j); |
| if (types->hasType(Type::ObjectType(target))) |
| types->addType(cx, Type::AnyObjectType()); |
| } |
| } |
| } |
| } |
| } |
| |
| void |
| ScriptAnalysis::addTypeBarrier(JSContext *cx, const jsbytecode *pc, TypeSet *target, Type type) |
| { |
| Bytecode &code = getCode(pc); |
| |
| if (!type.isUnknown() && !type.isAnyObject() && |
| type.isObject() && target->getObjectCount() >= BARRIER_OBJECT_LIMIT) { |
| /* Ignore this barrier, just add the type to the target. */ |
| target->addType(cx, type); |
| return; |
| } |
| |
| if (!code.typeBarriers) { |
| /* |
| * Adding type barriers at a bytecode which did not have them before |
| * will trigger recompilation. If there were already type barriers, |
| * however, do not trigger recompilation (the script will be recompiled |
| * if any of the barriers is ever violated). |
| */ |
| AddPendingRecompile(cx, script_); |
| } |
| |
| /* Ignore duplicate barriers. */ |
| size_t barrierCount = 0; |
| TypeBarrier *barrier = code.typeBarriers; |
| while (barrier) { |
| if (barrier->target == target && !barrier->singleton) { |
| if (barrier->type == type) |
|