blob: 3a26388be822b8da7b2ba6e694a94359cffaa8ae [file] [log] [blame]
/* -*- 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)
return;
if (barrier->type.isAnyObject() && !type.isUnknown() &&
/* type.isAnyObject() must be false, since type != barrier->type */
type.isObject())
{
return;
}
}
barrier = barrier->next;
barrierCount++;
}
/*
* Use a generic object barrier if the number of barriers on an opcode gets
* excessive: it is unlikely that we will be able to completely discharge
* the barrier anyways without the target being marked as a generic object.
*/
if (barrierCount >= BARRIER_OBJECT_LIMIT &&
!type.isUnknown() && !type.isAnyObject() && type.isObject())
{
type = Type::AnyObjectType();
}
InferSpew(ISpewOps, "typeBarrier: #%u:%05u: %sT%p%s %s",
script_->id(), pc - script_->code,
InferSpewColor(target), target, InferSpewColorReset(),
TypeString(type));
barrier = cx->analysisLifoAlloc().new_<TypeBarrier>(target, type, (JSObject *) NULL, JSID_VOID);
if (!barrier) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
barrier->next = code.typeBarriers;
code.typeBarriers = barrier;
}
void
ScriptAnalysis::addSingletonTypeBarrier(JSContext *cx, const jsbytecode *pc, TypeSet *target,
HandleObject singleton, HandleId singletonId)
{
JS_ASSERT(singletonId == IdToTypeId(singletonId) && !JSID_IS_VOID(singletonId));
Bytecode &code = getCode(pc);
if (!code.typeBarriers) {
/* Trigger recompilation as for normal type barriers. */
AddPendingRecompile(cx, script_);
}
InferSpew(ISpewOps, "singletonTypeBarrier: #%u:%05u: %sT%p%s %p %s",
script_->id(), pc - script_->code,
InferSpewColor(target), target, InferSpewColorReset(),
(void *) singleton.get(), TypeIdString(singletonId));
TypeBarrier *barrier = cx->analysisLifoAlloc().new_<TypeBarrier>(target, Type::UndefinedType(),
singleton, singletonId);
if (!barrier) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
barrier->next = code.typeBarriers;
code.typeBarriers = barrier;
}
void
TypeCompartment::print(JSContext *cx, bool force)
{
gc::AutoSuppressGC suppressGC(cx);
JSCompartment *compartment = this->compartment();
AutoEnterAnalysis enter(NULL, compartment);
if (!force && !InferSpewActive(ISpewResult))
return;
for (gc::CellIter i(compartment->zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
RootedScript script(cx, i.get<JSScript>());
if (script->hasAnalysis() && script->analysis()->ranInference())
script->analysis()->printTypes(cx);
}
#ifdef DEBUG
for (gc::CellIter i(compartment->zone(), gc::FINALIZE_TYPE_OBJECT); !i.done(); i.next()) {
TypeObject *object = i.get<TypeObject>();
object->print();
}
#endif
printf("Counts: ");
for (unsigned count = 0; count < TYPE_COUNT_LIMIT; count++) {
if (count)
printf("/");
printf("%u", typeCounts[count]);
}
printf(" (%u over)\n", typeCountOver);
}
/////////////////////////////////////////////////////////////////////
// TypeCompartment tables
/////////////////////////////////////////////////////////////////////
/*
* The arrayTypeTable and objectTypeTable are per-compartment tables for making
* common type objects to model the contents of large script singletons and
* JSON objects. These are vanilla Arrays and native Objects, so we distinguish
* the types of different ones by looking at the types of their properties.
*
* All singleton/JSON arrays which have the same prototype, are homogenous and
* of the same element type will share a type object. All singleton/JSON
* objects which have the same shape and property types will also share a type
* object. We don't try to collate arrays or objects that have type mismatches.
*/
static inline bool
NumberTypes(Type a, Type b)
{
return (a.isPrimitive(JSVAL_TYPE_INT32) || a.isPrimitive(JSVAL_TYPE_DOUBLE))
&& (b.isPrimitive(JSVAL_TYPE_INT32) || b.isPrimitive(JSVAL_TYPE_DOUBLE));
}
/*
* As for GetValueType, but requires object types to be non-singletons with
* their default prototype. These are the only values that should appear in
* arrays and objects whose type can be fixed.
*/
static inline Type
GetValueTypeForTable(JSContext *cx, const Value &v)
{
Type type = GetValueType(cx, v);
JS_ASSERT(!type.isSingleObject());
return type;
}
struct types::ArrayTableKey
{
Type type;
JSObject *proto;
ArrayTableKey()
: type(Type::UndefinedType()), proto(NULL)
{}
typedef ArrayTableKey Lookup;
static inline uint32_t hash(const ArrayTableKey &v) {
return (uint32_t) (v.type.raw() ^ ((uint32_t)(size_t)v.proto >> 2));
}
static inline bool match(const ArrayTableKey &v1, const ArrayTableKey &v2) {
return v1.type == v2.type && v1.proto == v2.proto;
}
};
void
TypeCompartment::fixArrayType(JSContext *cx, JSObject *obj)
{
AutoEnterAnalysis enter(cx);
if (!arrayTypeTable) {
arrayTypeTable = cx->new_<ArrayTypeTable>();
if (!arrayTypeTable || !arrayTypeTable->init()) {
arrayTypeTable = NULL;
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
}
/*
* If the array is of homogenous type, pick a type object which will be
* shared with all other singleton/JSON arrays of the same type.
* If the array is heterogenous, keep the existing type object, which has
* unknown properties.
*/
JS_ASSERT(obj->isArray());
unsigned len = obj->getDenseInitializedLength();
if (len == 0)
return;
Type type = GetValueTypeForTable(cx, obj->getDenseElement(0));
for (unsigned i = 1; i < len; i++) {
Type ntype = GetValueTypeForTable(cx, obj->getDenseElement(i));
if (ntype != type) {
if (NumberTypes(type, ntype))
type = Type::DoubleType();
else
return;
}
}
ArrayTableKey key;
key.type = type;
key.proto = obj->getProto();
ArrayTypeTable::AddPtr p = arrayTypeTable->lookupForAdd(key);
if (p) {
obj->setType(p->value);
} else {
Rooted<Type> origType(cx, type);
/* Make a new type to use for future arrays with the same elements. */
RootedObject objProto(cx, obj->getProto());
Rooted<TypeObject*> objType(cx, newTypeObject(cx, &ArrayClass, objProto));
if (!objType) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
obj->setType(objType);
if (!objType->unknownProperties())
objType->addPropertyType(cx, JSID_VOID, type);
// The key's fields may have been moved by moving GC and therefore the
// AddPtr is now invalid. ArrayTypeTable's equality and hashcodes
// operators use only the two fields (type and proto) directly, so we
// can just conditionally update them here.
if (type != origType || key.proto != obj->getProto()) {
key.type = origType;
key.proto = obj->getProto();
p = arrayTypeTable->lookupForAdd(key);
}
if (!arrayTypeTable->relookupOrAdd(p, key, objType)) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
}
}
/*
* N.B. We could also use the initial shape of the object (before its type is
* fixed) as the key in the object table, but since all references in the table
* are weak the hash entries would usually be collected on GC even if objects
* with the new type/shape are still live.
*/
struct types::ObjectTableKey
{
jsid *properties;
uint32_t nproperties;
uint32_t nfixed;
struct Lookup {
IdValuePair *properties;
uint32_t nproperties;
uint32_t nfixed;
Lookup(IdValuePair *properties, uint32_t nproperties, uint32_t nfixed)
: properties(properties), nproperties(nproperties), nfixed(nfixed)
{}
};
static inline HashNumber hash(const Lookup &lookup) {
return (HashNumber) (JSID_BITS(lookup.properties[lookup.nproperties - 1].id) ^
lookup.nproperties ^
lookup.nfixed);
}
static inline bool match(const ObjectTableKey &v, const Lookup &lookup) {
if (lookup.nproperties != v.nproperties || lookup.nfixed != v.nfixed)
return false;
for (size_t i = 0; i < lookup.nproperties; i++) {
if (lookup.properties[i].id != v.properties[i])
return false;
}
return true;
}
};
struct types::ObjectTableEntry
{
ReadBarriered<TypeObject> object;
ReadBarriered<Shape> shape;
Type *types;
};
static inline void
UpdateObjectTableEntryTypes(JSContext *cx, ObjectTableEntry &entry,
IdValuePair *properties, size_t nproperties)
{
if (entry.object->unknownProperties())
return;
for (size_t i = 0; i < nproperties; i++) {
Type type = entry.types[i];
Type ntype = GetValueTypeForTable(cx, properties[i].value);
if (ntype == type)
continue;
if (ntype.isPrimitive(JSVAL_TYPE_INT32) &&
type.isPrimitive(JSVAL_TYPE_DOUBLE))
{
/* The property types already reflect 'int32'. */
} else {
if (ntype.isPrimitive(JSVAL_TYPE_DOUBLE) &&
type.isPrimitive(JSVAL_TYPE_INT32))
{
/* Include 'double' in the property types to avoid the update below later. */
entry.types[i] = Type::DoubleType();
}
entry.object->addPropertyType(cx, IdToTypeId(properties[i].id), ntype);
}
}
}
void
TypeCompartment::fixObjectType(JSContext *cx, JSObject *obj)
{
AutoEnterAnalysis enter(cx);
if (!objectTypeTable) {
objectTypeTable = cx->new_<ObjectTypeTable>();
if (!objectTypeTable || !objectTypeTable->init()) {
objectTypeTable = NULL;
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
}
/*
* Use the same type object for all singleton/JSON objects with the same
* base shape, i.e. the same fields written in the same order.
*/
JS_ASSERT(obj->isObject());
if (obj->slotSpan() == 0 || obj->inDictionaryMode() || !obj->hasEmptyElements())
return;
Vector<IdValuePair> properties(cx);
if (!properties.resize(obj->slotSpan())) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
Shape *shape = obj->lastProperty();
while (!shape->isEmptyShape()) {
IdValuePair &entry = properties[shape->slot()];
entry.id = shape->propid();
entry.value = obj->getSlot(shape->slot());
shape = shape->previous();
}
ObjectTableKey::Lookup lookup(properties.begin(), properties.length(), obj->numFixedSlots());
ObjectTypeTable::AddPtr p = objectTypeTable->lookupForAdd(lookup);
if (p) {
JS_ASSERT(obj->getProto() == p->value.object->proto);
JS_ASSERT(obj->lastProperty() == p->value.shape);
UpdateObjectTableEntryTypes(cx, p->value, properties.begin(), properties.length());
obj->setType(p->value.object);
return;
}
/* Make a new type to use for the object and similar future ones. */
Rooted<TaggedProto> objProto(cx, obj->getTaggedProto());
TypeObject *objType = newTypeObject(cx, &ObjectClass, objProto);
if (!objType || !objType->addDefiniteProperties(cx, obj)) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
if (obj->isIndexed())
objType->setFlags(cx, OBJECT_FLAG_SPARSE_INDEXES);
jsid *ids = cx->pod_calloc<jsid>(properties.length());
if (!ids) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
Type *types = cx->pod_calloc<Type>(properties.length());
if (!types) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
for (size_t i = 0; i < properties.length(); i++) {
ids[i] = properties[i].id;
types[i] = GetValueTypeForTable(cx, obj->getSlot(i));
if (!objType->unknownProperties())
objType->addPropertyType(cx, IdToTypeId(ids[i]), types[i]);
}
ObjectTableKey key;
key.properties = ids;
key.nproperties = properties.length();
key.nfixed = obj->numFixedSlots();
JS_ASSERT(ObjectTableKey::match(key, lookup));
ObjectTableEntry entry;
entry.object = objType;
entry.shape = obj->lastProperty();
entry.types = types;
p = objectTypeTable->lookupForAdd(lookup);
if (!objectTypeTable->add(p, key, entry)) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
obj->setType(objType);
}
JSObject *
TypeCompartment::newTypedObject(JSContext *cx, IdValuePair *properties, size_t nproperties)
{
AutoEnterAnalysis enter(cx);
if (!objectTypeTable) {
objectTypeTable = cx->new_<ObjectTypeTable>();
if (!objectTypeTable || !objectTypeTable->init()) {
objectTypeTable = NULL;
cx->compartment()->types.setPendingNukeTypes(cx);
return NULL;
}
}
/*
* Use the object type table to allocate an object with the specified
* properties, filling in its final type and shape and failing if no cache
* entry could be found for the properties.
*/
/*
* Filter out a few cases where we don't want to use the object type table.
* Note that if the properties contain any duplicates or dense indexes,
* the lookup below will fail as such arrays of properties cannot be stored
* in the object type table --- fixObjectType populates the table with
* properties read off its input object, which cannot be duplicates, and
* ignores objects with dense indexes.
*/
if (!nproperties || nproperties >= PropertyTree::MAX_HEIGHT)
return NULL;
gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
size_t nfixed = gc::GetGCKindSlots(allocKind, &ObjectClass);
ObjectTableKey::Lookup lookup(properties, nproperties, nfixed);
ObjectTypeTable::AddPtr p = objectTypeTable->lookupForAdd(lookup);
if (!p)
return NULL;
RootedObject obj(cx, NewBuiltinClassInstance(cx, &ObjectClass, allocKind));
if (!obj) {
cx->clearPendingException();
return NULL;
}
JS_ASSERT(obj->getProto() == p->value.object->proto);
RootedShape shape(cx, p->value.shape);
if (!JSObject::setLastProperty(cx, obj, shape)) {
cx->clearPendingException();
return NULL;
}
UpdateObjectTableEntryTypes(cx, p->value, properties, nproperties);
for (size_t i = 0; i < nproperties; i++)
obj->setSlot(i, properties[i].value);
obj->setType(p->value.object);
return obj;
}
/////////////////////////////////////////////////////////////////////
// TypeObject
/////////////////////////////////////////////////////////////////////
void
TypeObject::getFromPrototypes(JSContext *cx, jsid id, TypeSet *types, bool force)
{
if (!force && types->hasPropagatedProperty())
return;
types->setPropagatedProperty();
if (!proto)
return;
if (proto == Proxy::LazyProto) {
JS_ASSERT(unknownProperties());
return;
}
types::TypeObject *protoType = proto->getType(cx);
if (!protoType || protoType->unknownProperties()) {
types->addType(cx, Type::UnknownType());
return;
}
HeapTypeSet *protoTypes = protoType->getProperty(cx, id, false);
if (!protoTypes)
return;
protoTypes->addSubset(cx, types);
protoType->getFromPrototypes(cx, id, protoTypes);
}
static inline void
UpdatePropertyType(JSContext *cx, TypeSet *types, JSObject *obj, Shape *shape,
bool force)
{
types->setOwnProperty(cx, false);
if (!shape->writable())
types->setOwnProperty(cx, true);
if (shape->hasGetterValue() || shape->hasSetterValue()) {
types->setOwnProperty(cx, true);
types->addType(cx, Type::UnknownType());
} else if (shape->hasDefaultGetter() && shape->hasSlot()) {
const Value &value = obj->nativeGetSlot(shape->slot());
/*
* Don't add initial undefined types for singleton properties that are
* not collated into the JSID_VOID property (see propertySet comment).
*/
if (force || !value.isUndefined()) {
Type type = GetValueType(cx, value);
types->addType(cx, type);
}
}
}
bool
TypeObject::addProperty(JSContext *cx, jsid id, Property **pprop)
{
JS_ASSERT(!*pprop);
Property *base = cx->typeLifoAlloc().new_<Property>(id);
if (!base) {
cx->compartment()->types.setPendingNukeTypes(cx);
return false;
}
if (singleton && singleton->isNative()) {
/*
* Fill the property in with any type the object already has in an own
* property. We are only interested in plain native properties and
* dense elements which don't go through a barrier when read by the VM
* or jitcode.
*/
RootedObject rSingleton(cx, singleton);
if (JSID_IS_VOID(id)) {
/* Go through all shapes on the object to get integer-valued properties. */
RootedShape shape(cx, singleton->lastProperty());
while (!shape->isEmptyShape()) {
if (JSID_IS_VOID(IdToTypeId(shape->propid())))
UpdatePropertyType(cx, &base->types, rSingleton, shape, true);
shape = shape->previous();
}
/* Also get values of any dense elements in the object. */
for (size_t i = 0; i < singleton->getDenseInitializedLength(); i++) {
const Value &value = singleton->getDenseElement(i);
if (!value.isMagic(JS_ELEMENTS_HOLE)) {
Type type = GetValueType(cx, value);
base->types.setOwnProperty(cx, false);
base->types.addType(cx, type);
}
}
} else if (!JSID_IS_EMPTY(id)) {
RootedId rootedId(cx, id);
Shape *shape = singleton->nativeLookup(cx, rootedId);
if (shape)
UpdatePropertyType(cx, &base->types, rSingleton, shape, false);
}
if (singleton->watched()) {
/*
* Mark the property as configured, to inhibit optimizations on it
* and avoid bypassing the watchpoint handler.
*/
base->types.setOwnProperty(cx, true);
}
}
*pprop = base;
InferSpew(ISpewOps, "typeSet: %sT%p%s property %s %s",
InferSpewColor(&base->types), &base->types, InferSpewColorReset(),
TypeObjectString(this), TypeIdString(id));
return true;
}
bool
TypeObject::addDefiniteProperties(JSContext *cx, JSObject *obj)
{
if (unknownProperties())
return true;
/* Mark all properties of obj as definite properties of this type. */
AutoEnterAnalysis enter(cx);
RootedShape shape(cx, obj->lastProperty());
while (!shape->isEmptyShape()) {
jsid id = IdToTypeId(shape->propid());
if (!JSID_IS_VOID(id) && obj->isFixedSlot(shape->slot()) &&
shape->slot() <= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) {
TypeSet *types = getProperty(cx, id, true);
if (!types)
return false;
types->setDefinite(shape->slot());
}
shape = shape->previous();
}
return true;
}
bool
TypeObject::matchDefiniteProperties(HandleObject obj)
{
unsigned count = getPropertyCount();
for (unsigned i = 0; i < count; i++) {
Property *prop = getProperty(i);
if (!prop)
continue;
if (prop->types.definiteProperty()) {
unsigned slot = prop->types.definiteSlot();
bool found = false;
Shape *shape = obj->lastProperty();
while (!shape->isEmptyShape()) {
if (shape->slot() == slot && shape->propid() == prop->id) {
found = true;
break;
}
shape = shape->previous();
}
if (!found)
return false;
}
}
return true;
}
inline void
InlineAddTypeProperty(JSContext *cx, TypeObject *obj, jsid id, Type type)
{
JS_ASSERT(id == IdToTypeId(id));
AutoEnterAnalysis enter(cx);
TypeSet *types = obj->getProperty(cx, id, true);
if (!types || types->hasType(type))
return;
InferSpew(ISpewOps, "externalType: property %s %s: %s",
TypeObjectString(obj), TypeIdString(id), TypeString(type));
types->addType(cx, type);
}
void
TypeObject::addPropertyType(JSContext *cx, jsid id, Type type)
{
InlineAddTypeProperty(cx, this, id, type);
}
void
TypeObject::addPropertyType(JSContext *cx, jsid id, const Value &value)
{
InlineAddTypeProperty(cx, this, id, GetValueType(cx, value));
}
void
TypeObject::addPropertyType(JSContext *cx, const char *name, Type type)
{
jsid id = JSID_VOID;
if (name) {
JSAtom *atom = Atomize(cx, name, strlen(name));
if (!atom) {
AutoEnterAnalysis enter(cx);
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
id = AtomToId(atom);
}
InlineAddTypeProperty(cx, this, id, type);
}
void
TypeObject::addPropertyType(JSContext *cx, const char *name, const Value &value)
{
addPropertyType(cx, name, GetValueType(cx, value));
}
void
TypeObject::markPropertyConfigured(JSContext *cx, jsid id)
{
AutoEnterAnalysis enter(cx);
id = IdToTypeId(id);
TypeSet *types = getProperty(cx, id, true);
if (types)
types->setOwnProperty(cx, true);
}
void
TypeObject::markStateChange(JSContext *cx)
{
if (unknownProperties())
return;
AutoEnterAnalysis enter(cx);
TypeSet *types = maybeGetProperty(JSID_EMPTY, cx);
if (types) {
TypeConstraint *constraint = types->constraintList;
while (constraint) {
constraint->newObjectState(cx, this, true);
constraint = constraint->next;
}
}
}
void
TypeObject::setFlags(JSContext *cx, TypeObjectFlags flags)
{
if ((this->flags & flags) == flags)
return;
AutoEnterAnalysis enter(cx);
if (singleton) {
/* Make sure flags are consistent with persistent object state. */
JS_ASSERT_IF(flags & OBJECT_FLAG_ITERATED,
singleton->lastProperty()->hasObjectFlag(BaseShape::ITERATED_SINGLETON));
}
this->flags |= flags;
InferSpew(ISpewOps, "%s: setFlags 0x%x", TypeObjectString(this), flags);
ObjectStateChange(cx, this, false, false);
}
void
TypeObject::markUnknown(JSContext *cx)
{
AutoEnterAnalysis enter(cx);
JS_ASSERT(cx->compartment()->activeAnalysis);
JS_ASSERT(!unknownProperties());
if (!(flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED))
clearNewScript(cx);
InferSpew(ISpewOps, "UnknownProperties: %s", TypeObjectString(this));
ObjectStateChange(cx, this, true, true);
/*
* Existing constraints may have already been added to this object, which we need
* to do the right thing for. We can't ensure that we will mark all unknown
* objects before they have been accessed, as the __proto__ of a known object
* could be dynamically set to an unknown object, and we can decide to ignore
* properties of an object during analysis (i.e. hashmaps). Adding unknown for
* any properties accessed already accounts for possible values read from them.
*/
unsigned count = getPropertyCount();
for (unsigned i = 0; i < count; i++) {
Property *prop = getProperty(i);
if (prop) {
prop->types.addType(cx, Type::UnknownType());
prop->types.setOwnProperty(cx, true);
}
}
}
void
TypeObject::clearNewScript(JSContext *cx)
{
JS_ASSERT(!(flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED));
flags |= OBJECT_FLAG_NEW_SCRIPT_CLEARED;
/*
* It is possible for the object to not have a new script yet but to have
* one added in the future. When analyzing properties of new scripts we mix
* in adding constraints to trigger clearNewScript with changes to the
* type sets themselves (from breakTypeBarriers). It is possible that we
* could trigger one of these constraints before AnalyzeNewScriptProperties
* has finished, in which case we want to make sure that call fails.
*/
if (!newScript)
return;
AutoEnterAnalysis enter(cx);
/*
* Any definite properties we added due to analysis of the new script when
* the type object was created are now invalid: objects with the same type
* can be created by using 'new' on a different script or through some
* other mechanism (e.g. Object.create). Rather than clear out the definite
* bits on the object's properties, just mark such properties as having
* been deleted/reconfigured, which will have the same effect on JITs
* wanting to use the definite bits to optimize property accesses.
*/
for (unsigned i = 0; i < getPropertyCount(); i++) {
Property *prop = getProperty(i);
if (!prop)
continue;
if (prop->types.definiteProperty())
prop->types.setOwnProperty(cx, true);
}
/*
* If we cleared the new script while in the middle of initializing an
* object, it will still have the new script's shape and reflect the no
* longer correct state of the object once its initialization is completed.
* We can't really detect the possibility of this statically, but the new
* script keeps track of where each property is initialized so we can walk
* the stack and fix up any such objects.
*/
Vector<uint32_t, 32> pcOffsets(cx);
for (ScriptFrameIter iter(cx); !iter.done(); ++iter) {
pcOffsets.append(uint32_t(iter.pc() - iter.script()->code));
if (iter.isConstructing() &&
iter.callee() == newScript->fun &&
iter.thisv().isObject() &&
!iter.thisv().toObject().hasLazyType() &&
iter.thisv().toObject().type() == this)
{
RootedObject obj(cx, &iter.thisv().toObject());
/* Whether all identified 'new' properties have been initialized. */
bool finished = false;
/* If not finished, number of properties that have been added. */
uint32_t numProperties = 0;
/*
* If non-zero, we are scanning initializers in a call which has
* already finished.
*/
size_t depth = 0;
size_t callDepth = pcOffsets.length() - 1;
uint32_t offset = pcOffsets[callDepth];
for (TypeNewScript::Initializer *init = newScript->initializerList;; init++) {
if (init->kind == TypeNewScript::Initializer::SETPROP) {
if (!depth && init->offset > offset) {
/* Advanced past all properties which have been initialized. */
break;
}
numProperties++;
} else if (init->kind == TypeNewScript::Initializer::FRAME_PUSH) {
if (depth) {
depth++;
} else if (init->offset > offset) {
/* Advanced past all properties which have been initialized. */
break;
} else if (init->offset == offset) {
if (!callDepth)
break;
offset = pcOffsets[--callDepth];
} else {
/* This call has already finished. */
depth = 1;
}
} else if (init->kind == TypeNewScript::Initializer::FRAME_POP) {
if (depth) {
depth--;
} else {
/* This call has not finished yet. */
break;
}
} else {
JS_ASSERT(init->kind == TypeNewScript::Initializer::DONE);
finished = true;
break;
}
}
if (!finished)
obj->rollbackProperties(cx, numProperties);
}
}
/* We NULL out newScript *before* freeing it so the write barrier works. */
TypeNewScript *savedNewScript = newScript;
newScript = NULL;
js_free(savedNewScript);
markStateChange(cx);
}
void
TypeObject::print()
{
TaggedProto tagged(proto);
printf("%s : %s",
TypeObjectString(this),
tagged.isObject() ? TypeString(Type::ObjectType(proto))
: (tagged.isLazy() ? "(lazy)" : "(null)"));
if (unknownProperties()) {
printf(" unknown");
} else {
if (!hasAnyFlags(OBJECT_FLAG_SPARSE_INDEXES))
printf(" dense");
if (!hasAnyFlags(OBJECT_FLAG_NON_PACKED))
printf(" packed");
if (!hasAnyFlags(OBJECT_FLAG_LENGTH_OVERFLOW))
printf(" noLengthOverflow");
if (hasAnyFlags(OBJECT_FLAG_EMULATES_UNDEFINED))
printf(" emulatesUndefined");
if (hasAnyFlags(OBJECT_FLAG_ITERATED))
printf(" iterated");
if (interpretedFunction)
printf(" ifun");
}
unsigned count = getPropertyCount();
if (count == 0) {
printf(" {}\n");
return;
}
printf(" {");
for (unsigned i = 0; i < count; i++) {
Property *prop = getProperty(i);
if (prop) {
printf("\n %s:", TypeIdString(prop->id));
prop->types.print();
}
}
printf("\n}\n");
}
/////////////////////////////////////////////////////////////////////
// Type Analysis
/////////////////////////////////////////////////////////////////////
static inline TypeObject *
GetInitializerType(JSContext *cx, JSScript *script, jsbytecode *pc)
{
if (!script->compileAndGo)
return NULL;
JSOp op = JSOp(*pc);
JS_ASSERT(op == JSOP_NEWARRAY || op == JSOP_NEWOBJECT || op == JSOP_NEWINIT);
bool isArray = (op == JSOP_NEWARRAY || (op == JSOP_NEWINIT && GET_UINT8(pc) == JSProto_Array));
JSProtoKey key = isArray ? JSProto_Array : JSProto_Object;
if (UseNewTypeForInitializer(cx, script, pc, key))
return NULL;
return TypeScript::InitObject(cx, script, pc, key);
}
/* Analyze type information for a single bytecode. */
bool
ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, TypeInferenceState &state)
{
JSScript *script = this->script_;
jsbytecode *pc = script->code + offset;
JSOp op = (JSOp)*pc;
Bytecode &code = getCode(offset);
JS_ASSERT(!code.pushedTypes);
InferSpew(ISpewOps, "analyze: #%u:%05u", script->id(), offset);
unsigned defCount = GetDefCount(script, offset);
if (ExtendedDef(pc))
defCount++;
StackTypeSet *pushed = cx->analysisLifoAlloc().newArrayUninitialized<StackTypeSet>(defCount);
if (!pushed)
return false;
PodZero(pushed, defCount);
code.pushedTypes = pushed;
/*
* Add phi nodes introduced at this point to the list of all phi nodes in
* the script. Types for these are not generated until after the script has
* been processed, as types can flow backwards into phi nodes and the
* source sets may not exist if we try to process these eagerly.
*/
if (code.newValues) {
SlotValue *newv = code.newValues;
while (newv->slot) {
if (newv->value.kind() != SSAValue::PHI || newv->value.phiOffset() != offset) {
newv++;
continue;
}
/*
* The phi nodes at join points should all be unique, and every phi
* node created should be in the phiValues list on some bytecode.
*/
if (!state.phiNodes.append(newv->value.phiNode()))
return false;
TypeSet &types = newv->value.phiNode()->types;
InferSpew(ISpewOps, "typeSet: %sT%p%s phi #%u:%05u:%u",
InferSpewColor(&types), &types, InferSpewColorReset(),
script->id(), offset, newv->slot);
types.setPurged();
newv++;
}
}
for (unsigned i = 0; i < defCount; i++) {
InferSpew(ISpewOps, "typeSet: %sT%p%s pushed%u #%u:%05u",
InferSpewColor(&pushed[i]), &pushed[i], InferSpewColorReset(),
i, script->id(), offset);
pushed[i].setPurged();
}
/* Add type constraints for the various opcodes. */
switch (op) {
/* Nop bytecodes. */
case JSOP_POP:
case JSOP_NOP:
case JSOP_NOTEARG:
case JSOP_LOOPHEAD:
case JSOP_LOOPENTRY:
case JSOP_GOTO:
case JSOP_IFEQ:
case JSOP_IFNE:
case JSOP_LINENO:
case JSOP_DEFCONST:
case JSOP_LEAVEWITH:
case JSOP_LEAVEBLOCK:
case JSOP_RETRVAL:
case JSOP_ENDITER:
case JSOP_THROWING:
case JSOP_GOSUB:
case JSOP_RETSUB:
case JSOP_CONDSWITCH:
case JSOP_DEFAULT:
case JSOP_POPN:
case JSOP_POPV:
case JSOP_DEBUGGER:
case JSOP_SETCALL:
case JSOP_TABLESWITCH:
case JSOP_TRY:
case JSOP_LABEL:
case JSOP_RUNONCE:
break;
/* Bytecodes pushing values of known type. */
case JSOP_VOID:
case JSOP_UNDEFINED:
pushed[0].addType(cx, Type::UndefinedType());
break;
case JSOP_ZERO:
case JSOP_ONE:
case JSOP_INT8:
case JSOP_INT32:
case JSOP_UINT16:
case JSOP_UINT24:
case JSOP_BITAND:
case JSOP_BITOR:
case JSOP_BITXOR:
case JSOP_BITNOT:
case JSOP_RSH:
case JSOP_LSH:
case JSOP_URSH:
pushed[0].addType(cx, Type::Int32Type());
break;
case JSOP_FALSE:
case JSOP_TRUE:
case JSOP_EQ:
case JSOP_NE:
case JSOP_LT:
case JSOP_LE:
case JSOP_GT:
case JSOP_GE:
case JSOP_NOT:
case JSOP_STRICTEQ:
case JSOP_STRICTNE:
case JSOP_IN:
case JSOP_INSTANCEOF:
pushed[0].addType(cx, Type::BooleanType());
break;
case JSOP_DOUBLE:
pushed[0].addType(cx, Type::DoubleType());
break;
case JSOP_STRING:
case JSOP_TYPEOF:
case JSOP_TYPEOFEXPR:
pushed[0].addType(cx, Type::StringType());
break;
case JSOP_NULL:
pushed[0].addType(cx, Type::NullType());
break;
case JSOP_REGEXP:
if (script->compileAndGo) {
TypeObject *object = TypeScript::StandardType(cx, JSProto_RegExp);
if (!object)
return false;
pushed[0].addType(cx, Type::ObjectType(object));
} else {
pushed[0].addType(cx, Type::UnknownType());
}
break;
case JSOP_OBJECT:
pushed[0].addType(cx, Type::ObjectType(script->getObject(GET_UINT32_INDEX(pc))));
break;
case JSOP_STOP:
/* If a stop is reachable then the return type may be void. */
if (script->function())
TypeScript::ReturnTypes(script)->addType(cx, Type::UndefinedType());
break;
case JSOP_OR:
case JSOP_AND:
/* OR/AND push whichever operand determined the result. */
poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
break;
case JSOP_DUP:
poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
poppedTypes(pc, 0)->addSubset(cx, &pushed[1]);
break;
case JSOP_DUP2:
poppedTypes(pc, 1)->addSubset(cx, &pushed[0]);
poppedTypes(pc, 0)->addSubset(cx, &pushed[1]);
poppedTypes(pc, 1)->addSubset(cx, &pushed[2]);
poppedTypes(pc, 0)->addSubset(cx, &pushed[3]);
break;
case JSOP_SWAP:
case JSOP_PICK: {
unsigned pickedDepth = (op == JSOP_SWAP ? 1 : GET_UINT8(pc));
/* The last popped value is the last pushed. */
poppedTypes(pc, pickedDepth)->addSubset(cx, &pushed[pickedDepth]);
for (unsigned i = 0; i < pickedDepth; i++)
poppedTypes(pc, i)->addSubset(cx, &pushed[pickedDepth - 1 - i]);
break;
}
case JSOP_GETGNAME:
case JSOP_CALLGNAME: {
jsid id = GetAtomId(cx, script, pc, 0);
StackTypeSet *seen = TypeScript::BytecodeTypes(script, pc);
seen->addSubset(cx, &pushed[0]);
/*
* Normally we rely on lazy standard class initialization to fill in
* the types of global properties the script can access. In a few cases
* the method JIT will bypass this, and we need to add the types
* directly.
*/
if (id == NameToId(cx->names().undefined))
seen->addType(cx, Type::UndefinedType());
if (id == NameToId(cx->names().NaN))
seen->addType(cx, Type::DoubleType());
if (id == NameToId(cx->names().Infinity))
seen->addType(cx, Type::DoubleType());
TypeObject *global = script->global().getType(cx);
if (!global)
return false;
/* Handle as a property access. */
if (state.hasPropertyReadTypes)
PropertyAccess<PROPERTY_READ_EXISTING>(cx, script, pc, global, seen, id);
else
PropertyAccess<PROPERTY_READ>(cx, script, pc, global, seen, id);
break;
}
case JSOP_NAME:
case JSOP_GETINTRINSIC:
case JSOP_CALLNAME:
case JSOP_CALLINTRINSIC: {
StackTypeSet *seen = TypeScript::BytecodeTypes(script, pc);
addTypeBarrier(cx, pc, seen, Type::UnknownType());
seen->addSubset(cx, &pushed[0]);
break;
}
case JSOP_BINDGNAME:
case JSOP_BINDNAME:
case JSOP_BINDINTRINSIC:
break;
case JSOP_SETGNAME: {
jsid id = GetAtomId(cx, script, pc, 0);
TypeObject *global = script->global().getType(cx);
if (!global)
return false;
PropertyAccess<PROPERTY_WRITE>(cx, script, pc, global, poppedTypes(pc, 0), id);
poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
break;
}
case JSOP_SETNAME:
case JSOP_SETINTRINSIC:
case JSOP_SETCONST:
cx->compartment()->types.monitorBytecode(cx, script, offset);
poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
break;
case JSOP_GETXPROP: {
StackTypeSet *seen = TypeScript::BytecodeTypes(script, pc);
addTypeBarrier(cx, pc, seen, Type::UnknownType());
seen->addSubset(cx, &pushed[0]);
break;
}
case JSOP_GETARG:
case JSOP_CALLARG:
case JSOP_GETLOCAL:
case JSOP_CALLLOCAL: {
uint32_t slot = GetBytecodeSlot(script, pc);
if (trackSlot(slot)) {
/*
* Normally these opcodes don't pop anything, but they are given
* an extended use holding the variable's SSA value before the
* access. Use the types from here.
*/
poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
} else {
/* Local 'let' variable. Punt on types for these, for now. */
pushed[0].addType(cx, Type::UnknownType());
}
break;
}
case JSOP_SETARG:
case JSOP_SETLOCAL:
/*
* For assignments to non-escaping locals/args, we don't need to update
* the possible types of the var, as for each read of the var SSA gives
* us the writes that could have produced that read.
*/
poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
break;
case JSOP_GETALIASEDVAR:
case JSOP_CALLALIASEDVAR:
/*
* Every aliased variable will contain 'undefined' in addition to the
* type of whatever value is written to it. Thus, a dynamic barrier is
* necessary. Since we don't expect the to observe more than 1 type,
* there is little benefit to maintaining a TypeSet for the aliased
* variable. Instead, we monitor/barrier all reads unconditionally.
*/
TypeScript::BytecodeTypes(script, pc)->addSubset(cx, &pushed[0]);
break;
case JSOP_SETALIASEDVAR:
poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
break;
case JSOP_ARGUMENTS:
/* Compute a precise type only when we know the arguments won't escape. */
if (script->needsArgsObj())
pushed[0].addType(cx, Type::UnknownType());
else
pushed[0].addType(cx, Type::MagicArgType());
break;
case JSOP_REST: {
StackTypeSet *types = TypeScript::BytecodeTypes(script, pc);
if (script->compileAndGo) {
TypeObject *rest = TypeScript::InitObject(cx, script, pc, JSProto_Array);
if (!rest)
return false;
// Simulate setting a element.
if (!rest->unknownProperties()) {
HeapTypeSet *propTypes = rest->getProperty(cx, JSID_VOID, true);
if (!propTypes)
return false;
propTypes->addType(cx, Type::UnknownType());
}
types->addType(cx, Type::ObjectType(rest));
} else {
types->addType(cx, Type::UnknownType());
}
types->addSubset(cx, &pushed[0]);
break;
}
case JSOP_SETPROP: {
jsid id = GetAtomId(cx, script, pc, 0);
poppedTypes(pc, 1)->addSetProperty(cx, script, pc, poppedTypes(pc, 0), id);
poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
break;
}
case JSOP_LENGTH:
case JSOP_GETPROP:
case JSOP_CALLPROP: {
jsid id = GetAtomId(cx, script, pc, 0);
StackTypeSet *seen = TypeScript::BytecodeTypes(script, pc);
HeapTypeSet *input = &script->types->propertyReadTypes[state.propertyReadIndex++];
poppedTypes(pc, 0)->addSubset(cx, input);
if (state.hasPropertyReadTypes) {
TypeConstraintGetPropertyExisting getProp(script, pc, seen, id);
input->addTypesToConstraint(cx, &getProp);
if (op == JSOP_CALLPROP) {
TypeConstraintCallPropertyExisting callProp(script, pc, id);
input->addTypesToConstraint(cx, &callProp);
}
} else {
input->addGetProperty(cx, script, pc, seen, id);
if (op == JSOP_CALLPROP)
input->addCallProperty(cx, script, pc, id);
}
seen->addSubset(cx, &pushed[0]);
break;
}
/*
* We only consider ELEM accesses on integers below. Any element access
* which is accessing a non-integer property must be monitored.
*/
case JSOP_GETELEM:
case JSOP_CALLELEM: {
StackTypeSet *seen = TypeScript::BytecodeTypes(script, pc);
/* Don't try to compute a precise callee for CALLELEM. */
if (op == JSOP_CALLELEM)
seen->addType(cx, Type::AnyObjectType());
HeapTypeSet *input = &script->types->propertyReadTypes[state.propertyReadIndex++];
poppedTypes(pc, 1)->addSubset(cx, input);
if (state.hasPropertyReadTypes) {
TypeConstraintGetPropertyExisting getProp(script, pc, seen, JSID_VOID);
input->addTypesToConstraint(cx, &getProp);
} else {
input->addGetProperty(cx, script, pc, seen, JSID_VOID);
}
seen->addSubset(cx, &pushed[0]);
break;
}
case JSOP_SETELEM:
poppedTypes(pc, 1)->addSetElement(cx, script, pc, poppedTypes(pc, 2), poppedTypes(pc, 0));
poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
break;
case JSOP_TOID:
/*
* This is only used for element inc/dec ops; any id produced which
* is not an integer must be monitored.
*/
pushed[0].addType(cx, Type::Int32Type());
break;
case JSOP_THIS:
TypeScript::ThisTypes(script)->addTransformThis(cx, script, &pushed[0]);
break;
case JSOP_RETURN:
case JSOP_SETRVAL:
if (script->function())
poppedTypes(pc, 0)->addSubset(cx, TypeScript::ReturnTypes(script));
break;
case JSOP_ADD:
poppedTypes(pc, 0)->addArith(cx, script, pc, &pushed[0], poppedTypes(pc, 1));
poppedTypes(pc, 1)->addArith(cx, script, pc, &pushed[0], poppedTypes(pc, 0));
break;
case JSOP_SUB:
case JSOP_MUL:
case JSOP_MOD:
case JSOP_DIV:
poppedTypes(pc, 0)->addArith(cx, script, pc, &pushed[0]);
poppedTypes(pc, 1)->addArith(cx, script, pc, &pushed[0]);
break;
case JSOP_NEG:
case JSOP_POS:
poppedTypes(pc, 0)->addArith(cx, script, pc, &pushed[0]);
break;
case JSOP_LAMBDA: {
RootedObject obj(cx, script->getObject(GET_UINT32_INDEX(pc)));
TypeSet *res = &pushed[0];
// If the lambda may produce values with different types than the
// original function, despecialize the type produced here. This includes
// functions that are deep cloned at each lambda, as well as inner
// functions to run-once lambdas which may actually execute multiple times.
if (script->compileAndGo && !script->treatAsRunOnce &&
!UseNewTypeForClone(&obj->as<JSFunction>()))
{
res->addType(cx, Type::ObjectType(obj));
} else {
res->addType(cx, Type::AnyObjectType());
}
break;
}
case JSOP_DEFFUN:
cx->compartment()->types.monitorBytecode(cx, script, offset);
break;
case JSOP_DEFVAR:
break;
case JSOP_CALL:
case JSOP_EVAL:
case JSOP_FUNCALL:
case JSOP_FUNAPPLY:
case JSOP_NEW: {
StackTypeSet *seen = TypeScript::BytecodeTypes(script, pc);
seen->addSubset(cx, &pushed[0]);
/* Construct the base call information about this site. */
unsigned argCount = GetUseCount(script, offset) - 2;
TypeCallsite *callsite = cx->analysisLifoAlloc().new_<TypeCallsite>(
cx, script, pc, op == JSOP_NEW, argCount);
if (!callsite || (argCount && !callsite->argumentTypes)) {
cx->compartment()->types.setPendingNukeTypes(cx);
break;
}
callsite->thisTypes = poppedTypes(pc, argCount);
callsite->returnTypes = seen;
for (unsigned i = 0; i < argCount; i++)
callsite->argumentTypes[i] = poppedTypes(pc, argCount - 1 - i);
/*
* Mark FUNCALL and FUNAPPLY sites as monitored. The method JIT may
* lower these into normal calls, and we need to make sure the
* callee's argument types are checked on entry.
*/
if (op == JSOP_FUNCALL || op == JSOP_FUNAPPLY)
cx->compartment()->types.monitorBytecode(cx, script, pc - script->code);
StackTypeSet *calleeTypes = poppedTypes(pc, argCount + 1);
/*
* Propagate possible 'this' types to the callee except when the call
* came through JSOP_CALLPROP (which uses TypeConstraintCallProperty)
* or for JSOP_NEW (where the callee will construct the 'this' object).
*/
SSAValue calleeValue = poppedValue(pc, argCount + 1);
if (*pc != JSOP_NEW &&
(calleeValue.kind() != SSAValue::PUSHED ||
script->code[calleeValue.pushedOffset()] != JSOP_CALLPROP))
{
calleeTypes->add(cx, cx->analysisLifoAlloc().new_<TypeConstraintPropagateThis>
(script, pc, Type::UndefinedType(), callsite->thisTypes));
}
calleeTypes->addCall(cx, callsite);
break;
}
case JSOP_NEWINIT:
case JSOP_NEWARRAY:
case JSOP_NEWOBJECT: {
StackTypeSet *types = TypeScript::BytecodeTypes(script, pc);
types->addSubset(cx, &pushed[0]);
bool isArray = (op == JSOP_NEWARRAY || (op == JSOP_NEWINIT && GET_UINT8(pc) == JSProto_Array));
JSProtoKey key = isArray ? JSProto_Array : JSProto_Object;
if (UseNewTypeForInitializer(cx, script, pc, key)) {
/* Defer types pushed by this bytecode until runtime. */
break;
}
TypeObject *initializer = GetInitializerType(cx, script, pc);
if (script->compileAndGo) {
if (!initializer)
return false;
types->addType(cx, Type::ObjectType(initializer));
} else {
JS_ASSERT(!initializer);
types->addType(cx, Type::UnknownType());
}
break;
}
case JSOP_ENDINIT:
break;
case JSOP_INITELEM:
case JSOP_INITELEM_INC:
case JSOP_INITELEM_ARRAY:
case JSOP_INITELEM_GETTER:
case JSOP_INITELEM_SETTER:
case JSOP_SPREAD: {
const SSAValue &objv = poppedValue(pc, (op == JSOP_INITELEM_ARRAY) ? 1 : 2);
jsbytecode *initpc = script->code + objv.pushedOffset();
TypeObject *initializer = GetInitializerType(cx, script, initpc);
if (initializer) {
pushed[0].addType(cx, Type::ObjectType(initializer));
if (!initializer->unknownProperties()) {
/*
* Assume the initialized element is an integer. INITELEM can be used
* for doubles which don't map to the JSID_VOID property, which must
* be caught with dynamic monitoring.
*/
TypeSet *types = initializer->getProperty(cx, JSID_VOID, true);
if (!types)
return false;
if (op == JSOP_INITELEM_GETTER || op == JSOP_INITELEM_SETTER) {
types->addType(cx, Type::UnknownType());
} else if (state.hasHole) {
if (!initializer->unknownProperties())
initializer->setFlags(cx, OBJECT_FLAG_NON_PACKED);
} else if (op == JSOP_SPREAD) {
// Iterator could put arbitrary things into the array.
types->addType(cx, Type::UnknownType());
} else {
poppedTypes(pc, 0)->addSubset(cx, types);
}
}
} else {
pushed[0].addType(cx, Type::UnknownType());
}
switch (op) {
case JSOP_SPREAD:
case JSOP_INITELEM_INC:
poppedTypes(pc, 1)->addSubset(cx, &pushed[1]);
break;
default:
break;
}
state.hasHole = false;
break;
}
case JSOP_HOLE:
state.hasHole = true;
break;
case JSOP_INITPROP:
case JSOP_INITPROP_GETTER:
case JSOP_INITPROP_SETTER: {
const SSAValue &objv = poppedValue(pc, 1);
jsbytecode *initpc = script->code + objv.pushedOffset();
TypeObject *initializer = GetInitializerType(cx, script, initpc);
if (initializer) {
pushed[0].addType(cx, Type::ObjectType(initializer));
if (!initializer->unknownProperties()) {
jsid id = GetAtomId(cx, script, pc, 0);
TypeSet *types = initializer->getProperty(cx, id, true);
if (!types)
return false;
if (id == id___proto__(cx) || id == id_prototype(cx))
cx->compartment()->types.monitorBytecode(cx, script, offset);
else if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER)
types->addType(cx, Type::UnknownType());
else
poppedTypes(pc, 0)->addSubset(cx, types);
}
} else {
pushed[0].addType(cx, Type::UnknownType());
}
JS_ASSERT(!state.hasHole);
break;
}
case JSOP_ENTERWITH:
case JSOP_ENTERBLOCK:
case JSOP_ENTERLET0:
/*
* Scope lookups can occur on the values being pushed here. We don't track
* the value or its properties, and just monitor all name opcodes in the
* script.
*/
break;
case JSOP_ENTERLET1:
/*
* JSOP_ENTERLET1 enters a let block with an unrelated value on top of
* the stack (such as the condition to a switch) whose constraints must
* be propagated. The other values are ignored for the same reason as
* JSOP_ENTERLET0.
*/
poppedTypes(pc, 0)->addSubset(cx, &pushed[defCount - 1]);
break;
case JSOP_ITER: {
/*
* Use a per-script type set to unify the possible target types of all
* 'for in' or 'for each' loops in the script. We need to mark the
* value pushed by the ITERNEXT appropriately, but don't track the SSA
* information to connect that ITERNEXT with the appropriate ITER.
* This loses some precision when a script mixes 'for in' and
* 'for each' loops together, oh well.
*/
if (!state.forTypes) {
state.forTypes = StackTypeSet::make(cx, "forTypes");
if (!state.forTypes)
return false;
}
if (GET_UINT8(pc) == JSITER_ENUMERATE)
state.forTypes->addType(cx, Type::StringType());
else
state.forTypes->addType(cx, Type::UnknownType());
break;
}
case JSOP_ITERNEXT:
state.forTypes->addSubset(cx, &pushed[0]);
break;
case JSOP_MOREITER:
pushed[1].addType(cx, Type::BooleanType());
break;
case JSOP_ENUMELEM:
case JSOP_ENUMCONSTELEM:
case JSOP_ARRAYPUSH:
cx->compartment()->types.monitorBytecode(cx, script, offset);
break;
case JSOP_THROW:
/* There will be a monitor on the bytecode catching the exception. */
break;
case JSOP_FINALLY:
/* Pushes information about whether an exception was thrown. */
break;
case JSOP_IMPLICITTHIS:
case JSOP_EXCEPTION:
pushed[0].addType(cx, Type::UnknownType());
break;
case JSOP_DELPROP:
case JSOP_DELELEM:
case JSOP_DELNAME:
pushed[0].addType(cx, Type::BooleanType());
break;
case JSOP_LEAVEBLOCKEXPR:
poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
break;
case JSOP_LEAVEFORLETIN:
break;
case JSOP_CASE:
poppedTypes(pc, 1)->addSubset(cx, &pushed[0]);
break;
case JSOP_GENERATOR:
TypeScript::ReturnTypes(script)->addType(cx, Type::UnknownType());
break;
case JSOP_YIELD:
pushed[0].addType(cx, Type::UnknownType());
break;
case JSOP_CALLEE:
pushed[0].addType(cx, Type::AnyObjectType());
break;
default:
/* Display fine-grained debug information first */
fprintf(stderr, "Unknown bytecode %02x at #%u:%05u\n", op, script->id(), offset);
TypeFailure(cx, "Unknown bytecode %02x", op);
}
return true;
}
void
ScriptAnalysis::analyzeTypes(JSContext *cx)
{
JS_ASSERT(!ranInference());
if (OOM()) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
if (!ranSSA()) {
analyzeSSA(cx);
if (failed())
return;
}
if (!script_->ensureHasBytecodeTypeMap(cx))
return;
/*
* Set this early to avoid reentrance. Any failures are OOMs, and will nuke
* all types in the compartment.
*/
ranInference_ = true;
TypeInferenceState state(cx);
/*
* Generate type sets for the inputs to property reads in the script,
* unless it already has them. If we purge analysis information and end up
* reanalyzing types in the script, we don't want to regenerate constraints
* on these property inputs as they will be duplicating information on the
* property type sets previously added.
*/
if (script_->types->propertyReadTypes) {
state.hasPropertyReadTypes = true;
} else {
HeapTypeSet *typeArray =
(HeapTypeSet*) cx->typeLifoAlloc().alloc(sizeof(HeapTypeSet) * numPropertyReads());
if (!typeArray) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
script_->types->propertyReadTypes = typeArray;
PodZero(typeArray, numPropertyReads());
#ifdef DEBUG
for (unsigned i = 0; i < numPropertyReads(); i++) {
InferSpew(ISpewOps, "typeSet: %sT%p%s propertyRead%u #%u",
InferSpewColor(&typeArray[i]), &typeArray[i], InferSpewColorReset(),
i, script_->id());
}
#endif
}
undefinedTypeSet = cx->analysisLifoAlloc().new_<StackTypeSet>();
if (!undefinedTypeSet) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
undefinedTypeSet->addType(cx, Type::UndefinedType());
unsigned offset = 0;
while (offset < script_->length) {
Bytecode *code = maybeCode(offset);
jsbytecode *pc = script_->code + offset;
if (code && !analyzeTypesBytecode(cx, offset, state)) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
offset += GetBytecodeLength(pc);
}
JS_ASSERT(state.propertyReadIndex == numPropertyReads());
for (unsigned i = 0; i < state.phiNodes.length(); i++) {
SSAPhiNode *node = state.phiNodes[i];
for (unsigned j = 0; j < node->length; j++) {
const SSAValue &v = node->options[j];
getValueTypes(v)->addSubset(cx, &node->types);
}
}
/*
* Replay any dynamic type results which have been generated for the script
* either because we ran the interpreter some before analyzing or because
* we are reanalyzing after a GC.
*/
TypeResult *result = script_->types->dynamicList;
while (result) {
if (result->offset != UINT32_MAX) {
pushedTypes(result->offset)->addType(cx, result->type);
} else {
/* Custom for-in loop iteration has happened in this script. */
state.forTypes->addType(cx, Type::UnknownType());
}
result = result->next;
}
TypeScript::AddFreezeConstraints(cx, script_);
}
bool
ScriptAnalysis::integerOperation(jsbytecode *pc)
{
JS_ASSERT(uint32_t(pc - script_->code) < script_->length);
switch (JSOp(*pc)) {
case JSOP_ADD:
case JSOP_SUB:
case JSOP_MUL:
case JSOP_DIV:
if (pushedTypes(pc, 0)->getKnownTypeTag() != JSVAL_TYPE_INT32)
return false;
if (poppedTypes(pc, 0)->getKnownTypeTag() != JSVAL_TYPE_INT32)
return false;
if (poppedTypes(pc, 1)->getKnownTypeTag() != JSVAL_TYPE_INT32)
return false;
return true;
default:
return true;
}
}
/*
* Persistent constraint clearing out newScript and definite properties from
* an object should a property on another object get a getter or setter.
*/
class TypeConstraintClearDefiniteGetterSetter : public TypeConstraint
{
public:
TypeObject *object;
TypeConstraintClearDefiniteGetterSetter(TypeObject *object)
: object(object)
{}
const char *kind() { return "clearDefiniteGetterSetter"; }
void newPropertyState(JSContext *cx, TypeSet *source)
{
if (!object->newScript)
return;
/*
* Clear out the newScript shape and definite property information from
* an object if the source type set could be a setter or could be
* non-writable, both of which are indicated by the source type set
* being marked as configured.
*/
if (!(object->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED) && source->ownProperty(true))
object->clearNewScript(cx);
}
void newType(JSContext *cx, TypeSet *source, Type type) {}
};
static bool
AddClearDefiniteGetterSetterForPrototypeChain(JSContext *cx, TypeObject *type, jsid id)
{
/*
* Ensure that if the properties named here could have a getter, setter or
* a permanent property in any transitive prototype, the definite
* properties get cleared from the shape.
*/
RootedObject parent(cx, type->proto);
while (parent) {
TypeObject *parentObject = parent->getType(cx);
if (!parentObject || parentObject->unknownProperties())
return false;
HeapTypeSet *parentTypes = parentObject->getProperty(cx, id, false);
if (!parentTypes || parentTypes->ownProperty(true))
return false;
parentTypes->add(cx, cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteGetterSetter>(type));
parent = parent->getProto();
}
return true;
}
/*
* Constraint which clears definite properties on an object should a type set
* contain any types other than a single object.
*/
class TypeConstraintClearDefiniteSingle : public TypeConstraint
{
public:
TypeObject *object;
TypeConstraintClearDefiniteSingle(TypeObject *object)
: object(object)
{}
const char *kind() { return "clearDefiniteSingle"; }
void newType(JSContext *cx, TypeSet *source, Type type) {
if (object->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED)
return;
if (source->baseFlags() || source->getObjectCount() > 1)
object->clearNewScript(cx);
}
};
struct NewScriptPropertiesState
{
RootedFunction thisFunction;
RootedObject baseobj;
Vector<TypeNewScript::Initializer> initializerList;
Vector<jsid> accessedProperties;
NewScriptPropertiesState(JSContext *cx)
: thisFunction(cx), baseobj(cx), initializerList(cx), accessedProperties(cx)
{}
};
static bool
AnalyzePoppedThis(JSContext *cx, SSAUseChain *use,
TypeObject *type, JSFunction *fun, NewScriptPropertiesState &state);
static bool
AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun,
NewScriptPropertiesState &state)
{
/*
* When invoking 'new' on the specified script, try to find some properties
* which will definitely be added to the created object before it has a
* chance to escape and be accessed elsewhere.
*
* Returns true if the entire script was analyzed (pbaseobj has been
* preserved), false if we had to bail out part way through (pbaseobj may
* have been cleared).
*/
if (state.initializerList.length() > 50) {
/*
* Bail out on really long initializer lists (far longer than maximum
* number of properties we can track), we may be recursing.
*/
return false;
}
RootedScript script(cx, fun->getOrCreateScript(cx));
if (!script)
return false;
if (!script->ensureRanAnalysis(cx) || !script->ensureRanInference(cx)) {
state.baseobj = NULL;
cx->compartment()->types.setPendingNukeTypes(cx);
return false;
}
ScriptAnalysis *analysis = script->analysis();
/*
* Offset of the last bytecode which popped 'this' and which we have
* processed. To support compound inline assignments to properties like
* 'this.f = (this.g = ...)' where multiple 'this' values are pushed
* and popped en masse, we keep a stack of 'this' values that have yet to
* be processed. If a 'this' is pushed before the previous 'this' value
* was popped, we defer processing it until we see a 'this' that is popped
* after the previous 'this' was popped, i.e. the end of the compound
* inline assignment, or we encounter a return from the script.
*/
Vector<SSAUseChain *> pendingPoppedThis(cx);
unsigned nextOffset = 0;
while (nextOffset < script->length) {
unsigned offset = nextOffset;
jsbytecode *pc = script->code + offset;
JSOp op = JSOp(*pc);
nextOffset += GetBytecodeLength(pc);
Bytecode *code = analysis->maybeCode(pc);
if (!code)
continue;
/*
* If offset >= the offset at the top of the pending stack, we either
* encountered the end of a compound inline assignment or a 'this' was
* immediately popped and used. In either case, handle the uses
* consumed before the current offset.
*/
while (!pendingPoppedThis.empty() && offset >= pendingPoppedThis.back()->offset) {
SSAUseChain *use = pendingPoppedThis.popCopy();
if (!AnalyzePoppedThis(cx, use, type, fun, state))
return false;
}
/*
* End analysis after the first return statement from the script,
* returning success if the return is unconditional.
*/
if (op == JSOP_RETURN || op == JSOP_STOP || op == JSOP_RETRVAL)
return code->unconditional;
/* 'this' can escape through a call to eval. */
if (op == JSOP_EVAL)
return false;
/*
* We are only interested in places where 'this' is popped. The new
* 'this' value cannot escape and be accessed except through such uses.
*/
if (op != JSOP_THIS)
continue;
SSAValue thisv = SSAValue::PushedValue(offset, 0);
SSAUseChain *uses = analysis->useChain(thisv);
JS_ASSERT(uses);
if (uses->next || !uses->popped) {
/* 'this' value popped in more than one place. */
return false;
}
/* Only handle 'this' values popped in unconditional code. */
Bytecode *poppedCode = analysis->maybeCode(uses->offset);
if (!poppedCode || !poppedCode->unconditional)
return false;
if (!pendingPoppedThis.append(uses))
return false;
}
/* Will have hit a STOP or similar, unless the script always throws. */
return true;
}
static bool
AnalyzePoppedThis(JSContext *cx, SSAUseChain *use,
TypeObject *type, JSFunction *fun, NewScriptPropertiesState &state)
{
RootedScript script(cx, fun->nonLazyScript());
ScriptAnalysis *analysis = script->analysis();
jsbytecode *pc = script->code + use->offset;
JSOp op = JSOp(*pc);
if (op == JSOP_SETPROP && use->u.which == 1) {
/*
* Don't use GetAtomId here, we need to watch for SETPROP on
* integer properties and bail out. We can't mark the aggregate
* JSID_VOID type property as being in a definite slot.
*/
RootedId id(cx, NameToId(script->getName(GET_UINT32_INDEX(pc))));
if (IdToTypeId(id) != id)
return false;
if (id_prototype(cx) == id || id___proto__(cx) == id || id_constructor(cx) == id)
return false;
/*
* Don't add definite properties for properties that were already
* read in the constructor.
*/
for (size_t i = 0; i < state.accessedProperties.length(); i++) {
if (state.accessedProperties[i] == id)
return false;
}
if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, type, id))
return false;
unsigned slotSpan = state.baseobj->slotSpan();
RootedValue value(cx, UndefinedValue());
if (!DefineNativeProperty(cx, state.baseobj, id, value, NULL, NULL,
JSPROP_ENUMERATE, 0, 0, DNP_SKIP_TYPE))
{
cx->compartment()->types.setPendingNukeTypes(cx);
state.baseobj = NULL;
return false;
}
if (state.baseobj->inDictionaryMode()) {
state.baseobj = NULL;
return false;
}
if (state.baseobj->slotSpan() == slotSpan) {
/* Set a duplicate property. */
return false;
}
TypeNewScript::Initializer setprop(TypeNewScript::Initializer::SETPROP, use->offset);
if (!state.initializerList.append(setprop)) {
cx->compartment()->types.setPendingNukeTypes(cx);
state.baseobj = NULL;
return false;
}
if (state.baseobj->slotSpan() >= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) {
/* Maximum number of definite properties added. */
return false;
}
return true;
}
if (op == JSOP_GETPROP && use->u.which == 0) {
/*
* Properties can be read from the 'this' object if the following hold:
*
* - The read is not on a getter along the prototype chain, which
* could cause 'this' to escape.
*
* - The accessed property is either already a definite property or
* is not later added as one. Since the definite properties are
* added to the object at the point of its creation, reading a
* definite property before it is assigned could incorrectly hit.
*/
RootedId id(cx, NameToId(script->getName(GET_UINT32_INDEX(pc))));
if (IdToTypeId(id) != id)
return false;
if (!state.baseobj->nativeLookup(cx, id) && !state.accessedProperties.append(id.get())) {
cx->compartment()->types.setPendingNukeTypes(cx);
state.baseobj = NULL;
return false;
}
if (!AddClearDefiniteGetterSetterForPrototypeChain(cx, type, id))
return false;
/*
* Populate the read with any value from the type's proto, if
* this is being used in a function call and we need to analyze the
* callee's behavior.
*/
Shape *shape = (type->proto && type->proto->isNative())
? type->proto->nativeLookup(cx, id)
: NULL;
if (shape && shape->hasSlot()) {
Value protov = type->proto->getSlot(shape->slot());
TypeSet *types = TypeScript::BytecodeTypes(script, pc);
types->addType(cx, GetValueType(cx, protov));
}
return true;
}
if ((op == JSOP_FUNCALL || op == JSOP_FUNAPPLY) && use->u.which == GET_ARGC(pc) - 1) {
/*
* Passed as the first parameter to Function.call. Follow control
* into the callee, and add any definite properties it assigns to
* the object as well. :TODO: This is narrow pattern matching on
* the inheritance patterns seen in the v8-deltablue benchmark, and
* needs robustness against other ways initialization can cross
* script boundaries.
*
* Add constraints ensuring we are calling Function.call on a
* particular script, removing definite properties from the result
*/
/* Callee/this must have been pushed by a CALLPROP. */
SSAValue calleev = analysis->poppedValue(pc, GET_ARGC(pc) + 1);
if (calleev.kind() != SSAValue::PUSHED)
return false;
jsbytecode *calleepc = script->code + calleev.pushedOffset();
if (JSOp(*calleepc) != JSOP_CALLPROP)
return false;
/*
* This code may not have run yet, break any type barriers involved
* in performing the call (for the greater good!).
*/
if (cx->compartment()->types.compiledInfo.outputIndex == RecompileInfo::NoCompilerRunning) {
analysis->breakTypeBarriersSSA(cx, analysis->poppedValue(calleepc, 0));
analysis->breakTypeBarriers(cx, calleepc - script->code, true);
}
StackTypeSet *funcallTypes = analysis->poppedTypes(pc, GET_ARGC(pc) + 1);
StackTypeSet *scriptTypes = analysis->poppedTypes(pc, GET_ARGC(pc));
/* Need to definitely be calling Function.call/apply on a specific script. */
RootedFunction function(cx);
{
JSObject *funcallObj = funcallTypes->getSingleton();
JSObject *scriptObj = scriptTypes->getSingleton();
if (!funcallObj || !funcallObj->is<JSFunction>() ||
funcallObj->as<JSFunction>().isInterpreted() ||
!scriptObj || !scriptObj->is<JSFunction>() ||
!scriptObj->as<JSFunction>().isInterpreted())
{
return false;
}
Native native = funcallObj->as<JSFunction>().native();
if (native != js_fun_call && native != js_fun_apply)
return false;
function = &scriptObj->as<JSFunction>();
}
/*
* Generate constraints to clear definite properties from the type
* should the Function.call or callee itself change in the future.
*/
funcallTypes->add(cx,
cx->analysisLifoAlloc().new_<TypeConstraintClearDefiniteSingle>(type));
scriptTypes->add(cx,
cx->analysisLifoAlloc().new_<TypeConstraintClearDefiniteSingle>(type));
TypeNewScript::Initializer pushframe(TypeNewScript::Initializer::FRAME_PUSH, use->offset);
if (!state.initializerList.append(pushframe)) {
cx->compartment()->types.setPendingNukeTypes(cx);
state.baseobj = NULL;
return false;
}
if (!AnalyzeNewScriptProperties(cx, type, function, state))
return false;
TypeNewScript::Initializer popframe(TypeNewScript::Initializer::FRAME_POP, 0);
if (!state.initializerList.append(popframe)) {
cx->compartment()->types.setPendingNukeTypes(cx);
state.baseobj = NULL;
return false;
}
/*
* The callee never lets the 'this' value escape, continue looking
* for definite properties in the remainder of this script.
*/
return true;
}
/* Unhandled use of 'this'. */
return false;
}
/*
* Either make the newScript information for type when it is constructed
* by the specified script, or regenerate the constraints for an existing
* newScript on the type after they were cleared by a GC.
*/
static void
CheckNewScriptProperties(JSContext *cx, HandleTypeObject type, HandleFunction fun)
{
if (type->unknownProperties())
return;
NewScriptPropertiesState state(cx);
state.thisFunction = fun;
/* Strawman object to add properties to and watch for duplicates. */
state.baseobj = NewBuiltinClassInstance(cx, &ObjectClass, gc::FINALIZE_OBJECT16);
if (!state.baseobj) {
if (type->newScript)
type->clearNewScript(cx);
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
AnalyzeNewScriptProperties(cx, type, fun, state);
if (!state.baseobj ||
state.baseobj->slotSpan() == 0 ||
!!(type->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED))
{
if (type->newScript)
type->clearNewScript(cx);
return;
}
/*
* If the type already has a new script, we are just regenerating the type
* constraints and don't need to make another TypeNewScript. Make sure that
* the properties added to baseobj match the type's definite properties.
*/
if (type->newScript) {
if (!type->matchDefiniteProperties(state.baseobj))
type->clearNewScript(cx);
return;
}
gc::AllocKind kind = gc::GetGCObjectKind(state.baseobj->slotSpan());
/* We should not have overflowed the maximum number of fixed slots for an object. */
JS_ASSERT(gc::GetGCKindSlots(kind) >= state.baseobj->slotSpan());
TypeNewScript::Initializer done(TypeNewScript::Initializer::DONE, 0);
/*
* The base object may have been created with a different finalize kind
* than we will use for subsequent new objects. Generate an object with the
* appropriate final shape.
*/
RootedShape shape(cx, state.baseobj->lastProperty());
state.baseobj = NewReshapedObject(cx, type, state.baseobj->getParent(), kind, shape);
if (!state.baseobj ||
!type->addDefiniteProperties(cx, state.baseobj) ||
!state.initializerList.append(done)) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
size_t numBytes = sizeof(TypeNewScript)
+ (state.initializerList.length() * sizeof(TypeNewScript::Initializer));
#ifdef JSGC_ROOT_ANALYSIS
// calloc can legitimately return a pointer that appears to be poisoned.
void *p;
do {
p = cx->calloc_(numBytes);
} while (IsPoisonedPtr(p));
type->newScript = (TypeNewScript *) p;
#else
type->newScript = (TypeNewScript *) cx->calloc_(numBytes);
#endif
if (!type->newScript) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
type->newScript->fun = fun;
type->newScript->allocKind = kind;
type->newScript->shape = state.baseobj->lastProperty();
type->newScript->initializerList = (TypeNewScript::Initializer *)
((char *) type->newScript.get() + sizeof(TypeNewScript));
PodCopy(type->newScript->initializerList,
state.initializerList.begin(),
state.initializerList.length());
}
/////////////////////////////////////////////////////////////////////
// Printing
/////////////////////////////////////////////////////////////////////
void
ScriptAnalysis::printTypes(JSContext *cx)
{
AutoEnterAnalysis enter(NULL, script_->compartment());
TypeCompartment *compartment = &script_->compartment()->types;
/*
* Check if there are warnings for used values with unknown types, and build
* statistics about the size of type sets found for stack values.
*/
for (unsigned offset = 0; offset < script_->length; offset++) {
if (!maybeCode(offset))
continue;
unsigned defCount = GetDefCount(script_, offset);
if (!defCount)
continue;
for (unsigned i = 0; i < defCount; i++) {
TypeSet *types = pushedTypes(offset, i);
if (types->unknown()) {
compartment->typeCountOver++;
continue;
}
unsigned typeCount = 0;
if (types->hasAnyFlag(TYPE_FLAG_ANYOBJECT) || types->getObjectCount() != 0)
typeCount++;
for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) {
if (types->hasAnyFlag(flag))
typeCount++;
}
/*
* Adjust the type counts for floats: values marked as floats
* are also marked as ints by the inference, but for counting
* we don't consider these to be separate types.
*/
if (types->hasAnyFlag(TYPE_FLAG_DOUBLE)) {
JS_ASSERT(types->hasAnyFlag(TYPE_FLAG_INT32));
typeCount--;
}
if (typeCount > TypeCompartment::TYPE_COUNT_LIMIT) {
compartment->typeCountOver++;
} else if (typeCount == 0) {
/* Ignore values without types, this may be unreached code. */
} else {
compartment->typeCounts[typeCount-1]++;
}
}
}
#ifdef DEBUG
if (script_->function())
printf("Function");
else if (script_->isCachedEval)
printf("Eval");
else
printf("Main");
printf(" #%u %s (line %d):\n", script_->id(), script_->filename(), script_->lineno);
printf("locals:");
printf("\n return:");
TypeScript::ReturnTypes(script_)->print();
printf("\n this:");
TypeScript::ThisTypes(script_)->print();
for (unsigned i = 0; script_->function() && i < script_->function()->nargs; i++) {
printf("\n arg%u:", i);
TypeScript::ArgTypes(script_, i)->print();
}
printf("\n");
RootedScript script(cx, script_);
for (unsigned offset = 0; offset < script_->length; offset++) {
if (!maybeCode(offset))
continue;
jsbytecode *pc = script_->code + offset;
PrintBytecode(cx, script, pc);
if (js_CodeSpec[*pc].format & JOF_TYPESET) {
TypeSet *types = TypeScript::BytecodeTypes(script_, pc);
printf(" typeset %d:", (int) (types - script_->types->typeArray()));
types->print();
printf("\n");
}
unsigned defCount = GetDefCount(script_, offset);
for (unsigned i = 0; i < defCount; i++) {
printf(" type %d:", i);
pushedTypes(offset, i)->print();
printf("\n");
}
if (getCode(offset).monitoredTypes)
printf(" monitored\n");
TypeBarrier *barrier = getCode(offset).typeBarriers;
if (barrier != NULL) {
printf(" barrier:");
while (barrier) {
printf(" %s", TypeString(barrier->type));
barrier = barrier->next;
}
printf("\n");
}
}
printf("\n");
#endif /* DEBUG */
}
/////////////////////////////////////////////////////////////////////
// Interface functions
/////////////////////////////////////////////////////////////////////
void
types::MarkIteratorUnknownSlow(JSContext *cx)
{
/* Check whether we are actually at an ITER opcode. */
jsbytecode *pc;
RootedScript script(cx, cx->currentScript(&pc));
if (!script || !pc)
return;
if (JSOp(*pc) != JSOP_ITER)
return;
AutoEnterAnalysis enter(cx);
if (!script->ensureHasTypes(cx))
return;
/*
* This script is iterating over an actual Iterator or Generator object, or
* an object with a custom __iterator__ hook. In such cases 'for in' loops
* can produce values other than strings, and the types of the ITER opcodes
* in the script need to be updated. During analysis this is done with the
* forTypes in the analysis state, but we don't keep a pointer to this type
* set and need to scan the script to fix affected opcodes.
*/
TypeResult *result = script->types->dynamicList;
while (result) {
if (result->offset == UINT32_MAX) {
/* Already know about custom iterators used in this script. */
JS_ASSERT(result->type.isUnknown());
return;
}
result = result->next;
}
InferSpew(ISpewOps, "externalType: customIterator #%u", script->id());
result = cx->new_<TypeResult>(UINT32_MAX, Type::UnknownType());
if (!result) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
result->next = script->types->dynamicList;
script->types->dynamicList = result;
AddPendingRecompile(cx, script);
if (!script->hasAnalysis() || !script->analysis()->ranInference())
return;
ScriptAnalysis *analysis = script->analysis();
for (unsigned i = 0; i < script->length; i++) {
jsbytecode *pc = script->code + i;
if (!analysis->maybeCode(pc))
continue;
if (JSOp(*pc) == JSOP_ITERNEXT)
analysis->pushedTypes(pc, 0)->addType(cx, Type::UnknownType());
}
}
void
types::TypeMonitorCallSlow(JSContext *cx, JSObject *callee, const CallArgs &args,
bool constructing)
{
unsigned nargs = callee->as<JSFunction>().nargs;
JSScript *script = callee->as<JSFunction>().nonLazyScript();
if (!constructing)
TypeScript::SetThis(cx, script, args.thisv());
/*
* Add constraints going up to the minimum of the actual and formal count.
* If there are more actuals than formals the later values can only be
* accessed through the arguments object, which is monitored.
*/
unsigned arg = 0;
for (; arg < args.length() && arg < nargs; arg++)
TypeScript::SetArgument(cx, script, arg, args[arg]);
/* Watch for fewer actuals than formals to the call. */
for (; arg < nargs; arg++)
TypeScript::SetArgument(cx, script, arg, UndefinedValue());
}
static inline bool
IsAboutToBeFinalized(TypeObjectKey *key)
{
/* Mask out the low bit indicating whether this is a type or JS object. */
gc::Cell *tmp = reinterpret_cast<gc::Cell *>(uintptr_t(key) & ~1);
bool isAboutToBeFinalized = IsCellAboutToBeFinalized(&tmp);
JS_ASSERT(tmp == reinterpret_cast<gc::Cell *>(uintptr_t(key) & ~1));
return isAboutToBeFinalized;
}
void
types::TypeDynamicResult(JSContext *cx, JSScript *script, jsbytecode *pc, Type type)
{
JS_ASSERT(cx->typeInferenceEnabled());
if (!script->types)
return;
AutoEnterAnalysis enter(cx);
/* Directly update associated type sets for applicable bytecodes. */
if (js_CodeSpec[*pc].format & JOF_TYPESET) {
if (!script->ensureHasBytecodeTypeMap(cx)) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
TypeSet *types = TypeScript::BytecodeTypes(script, pc);
if (!types->hasType(type)) {
InferSpew(ISpewOps, "externalType: monitorResult #%u:%05u: %s",
script->id(), pc - script->code, TypeString(type));
types->addType(cx, type);
}
return;
}
if (script->hasAnalysis() && script->analysis()->ranInference()) {
/*
* If the pushed set already has this type, we don't need to ensure
* there is a TypeIntermediate. Either there already is one, or the
* type could be determined from the script's other input type sets.
*/
TypeSet *pushed = script->analysis()->pushedTypes(pc, 0);
if (pushed->hasType(type))
return;
} else {
/* Scan all intermediate types on the script to check for a dupe. */
TypeResult *result, **pstart = &script->types->dynamicList, **presult = pstart;
while (*presult) {
result = *presult;
if (result->offset == unsigned(pc - script->code) && result->type == type) {
if (presult != pstart) {
/* Move to the head of the list, maintain LRU order. */
*presult = result->next;
result->next = *pstart;
*pstart = result;
}
return;
}
presult = &result->next;
}
}
InferSpew(ISpewOps, "externalType: monitorResult #%u:%05u: %s",
script->id(), pc - script->code, TypeString(type));
TypeResult *result = cx->new_<TypeResult>(pc - script->code, type);
if (!result) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
result->next = script->types->dynamicList;
script->types->dynamicList = result;
AddPendingRecompile(cx, script);
if (script->hasAnalysis() && script->analysis()->ranInference()) {
TypeSet *pushed = script->analysis()->pushedTypes(pc, 0);
pushed->addType(cx, type);
}
}
void
types::TypeMonitorResult(JSContext *cx, JSScript *script, jsbytecode *pc, const js::Value &rval)
{
/* Allow the non-TYPESET scenario to simplify stubs used in compound opcodes. */
if (!(js_CodeSpec[*pc].format & JOF_TYPESET))
return;
if (!script->types)
return;
AutoEnterAnalysis enter(cx);
if (!script->ensureHasBytecodeTypeMap(cx)) {
cx->compartment()->types.setPendingNukeTypes(cx);
return;
}
Type type = GetValueType(cx, rval);
TypeSet *types = TypeScript::BytecodeTypes(script, pc);
if (types->hasType(type))
return;
InferSpew(ISpewOps, "bytecodeType: #%u:%05u: %s",
script->id(), pc - script->code, TypeString(type));
types->addType(cx, type);
}
/////////////////////////////////////////////////////////////////////
// TypeScript
/////////////////////////////////////////////////////////////////////
/*
* Returns true if we don't expect to compute the correct types for some value
* pushed by the specified bytecode.
*/
static inline bool
IgnorePushed(const jsbytecode *pc, unsigned index)
{
switch (JSOp(*pc)) {
/* We keep track of the scopes pushed by BINDNAME separately. */
case JSOP_BINDNAME:
case JSOP_BINDGNAME:
case JSOP_BINDINTRINSIC:
return true;
/* Stack not consistent in TRY_BRANCH_AFTER_COND. */
case JSOP_IN:
case JSOP_EQ:
case JSOP_NE:
case JSOP_LT:
case JSOP_LE:
case JSOP_GT:
case JSOP_GE:
return (index == 0);
/* Value not determining result is not pushed by OR/AND. */
case JSOP_OR:
case JSOP_AND:
return (index == 0);
/* Holes tracked separately. */
case JSOP_HOLE:
return (index == 0);
/* Storage for 'with' and 'let' blocks not monitored. */
case JSOP_ENTERWITH:
case JSOP_ENTERBLOCK:
case JSOP_ENTERLET0:
case JSOP_ENTERLET1:
return true;
/* We don't keep track of the iteration state for 'for in' or 'for each in' loops. */
case JSOP_ITER:
case JSOP_ITERNEXT:
case JSOP_MOREITER:
case JSOP_ENDITER:
return true;
/* Ops which can manipulate values pushed by opcodes we don't model. */
case JSOP_DUP:
case JSOP_DUP2:
case JSOP_SWAP:
case JSOP_PICK:
return true;
/* We don't keep track of state indicating whether there is a pending exception. */
case JSOP_FINALLY:
return true;
/*
* We don't treat GETLOCAL immediately followed by a pop as a use-before-def,
* and while the type will have been inferred correctly the method JIT
* may not have written the local's initial undefined value to the stack,
* leaving a stale value.
*/
case JSOP_GETLOCAL:
return JSOp(pc[JSOP_GETLOCAL_LENGTH]) == JSOP_POP;
default:
return false;
}
}
bool
JSScript::makeTypes(JSContext *cx)
{
JS_ASSERT(!types);
if (!cx->typeInferenceEnabled()) {
types = cx->pod_calloc<TypeScript>();
if (!types) {
js_ReportOutOfMemory(cx);
return false;
}
new(types) TypeScript();
return analyzedArgsUsage() || ensureRanAnalysis(cx);
}
AutoEnterAnalysis enter(cx);
unsigned count = TypeScript::NumTypeSets(this);
types = (TypeScript *) cx->calloc_(sizeof(TypeScript) + (sizeof(TypeSet) * count));
if (!types) {
cx->compartment()->types.setPendingNukeTypes(cx);
return false;
}
new(types) TypeScript();
TypeSet *typeArray = types->typeArray();
TypeSet *returnTypes = TypeScript::ReturnTypes(this);
for (unsigned i = 0; i < count; i++) {
TypeSet *types = &typeArray[i];
if (types != returnTypes)
types->setConstraintsPurged();
}
if (isCallsiteClone) {
/*
* For callsite clones, flow the types from the specific clone back to
* the original function.
*/
JS_ASSERT(function());
JS_ASSERT(originalFunction());
JS_ASSERT(function()->nargs == originalFunction()->nargs);
JSScript *original = originalFunction()->nonLazyScript();
if (!original->ensureHasTypes(cx))
return false;
TypeScript::ReturnTypes(this)->addSubset(cx, TypeScript::ReturnTypes(original));
TypeScript::ThisTypes(this)->addSubset(cx, TypeScript::ThisTypes(original));
for (unsigned i = 0; i < function()->nargs; i++)
TypeScript::ArgTypes(this, i)->addSubset(cx, TypeScript::ArgTypes(original, i));
}
#ifdef DEBUG
for (unsigned i = 0; i < nTypeSets; i++)
InferSpew(ISpewOps, "typeSet: %sT%p%s bytecode%u #%u",
InferSpewColor(&typeArray[i]), &typeArray[i], InferSpewColorReset(),
i, id());
InferSpew(ISpewOps, "typeSet: %sT%p%s return #%u",
InferSpewColor(returnTypes), returnTypes, InferSpewColorReset(),
id());
TypeSet *thisTypes = TypeScript::ThisTypes(this);
InferSpew(ISpewOps, "typeSet: %sT%p%s this #%u",
InferSpewColor(thisTypes), thisTypes, InferSpewColorReset(),
id());
unsigned nargs = function() ? function()->nargs : 0;
for (unsigned i = 0; i < nargs; i++) {
TypeSet *types = TypeScript::ArgTypes(this, i);
InferSpew(ISpewOps, "typeSet: %sT%p%s arg%u #%u",
InferSpewColor(types), types, InferSpewColorReset(),
i, id());
}
#endif
return analyzedArgsUsage() || ensureRanAnalysis(cx);
}
bool
JSScript::makeBytecodeTypeMap(JSContext *cx)
{
JS_ASSERT(cx->typeInferenceEnabled());
JS_ASSERT(types && !types->bytecodeMap);
types->bytecodeMap = cx->analysisLifoAlloc().newArrayUninitialized<uint32_t>(nTypeSets + 1);
if (!types->bytecodeMap)
return false;
uint32_t added = 0;
for (jsbytecode *pc = code; pc < code + length; pc += GetBytecodeLength(pc)) {
JSOp op = JSOp(*pc);
if (js_CodeSpec[op].format & JOF_TYPESET) {
types->bytecodeMap[added++] = pc - code;
if (added == nTypeSets)
break;
}
}
JS_ASSERT(added == nTypeSets);
// The last entry in the last index found, and is used to avoid binary
// searches for the sought entry when queries are in linear order.
types->bytecodeMap[nTypeSets] = 0;
return true;
}
bool
JSScript::makeAnalysis(JSContext *cx)
{
JS_ASSERT(types && !types->analysis);
AutoEnterAnalysis enter(cx);
types->analysis = cx->analysisLifoAlloc().new_<ScriptAnalysis>(this);
if (!types->analysis)
return false;
RootedScript self(cx, this);
self->types->analysis->analyzeBytecode(cx);
if (self->types->analysis->OOM()) {
self->types->analysis = NULL;
return false;
}
return true;
}
/* static */ bool
JSFunction::setTypeForScriptedFunction(JSContext *cx, HandleFunction fun, bool singleton /* = false */)
{
if (!cx->typeInferenceEnabled())
return true;
if (singleton) {
if (!setSingletonType(cx, fun))
return false;
} else {
RootedObject funProto(cx, fun->getProto());
TypeObject *type =
cx->compartment()->types.newTypeObject(cx, &JSFunction::class_, funProto);
if (!type)
return false;
fun->setType(type);
type->interpretedFunction = fun;
}
return true;
}
/////////////////////////////////////////////////////////////////////
// JSObject
/////////////////////////////////////////////////////////////////////
bool
JSObject::shouldSplicePrototype(JSContext *cx)
{
/*
* During bootstrapping, if inference is enabled we need to make sure not
* to splice a new prototype in for Function.prototype or the global
* object if their __proto__ had previously been set to null, as this
* will change the prototype for all other objects with the same type.
* If inference is disabled we cannot determine from the object whether it
* has had its __proto__ set after creation.
*/
if (getProto() != NULL)
return false;
return !cx->typeInferenceEnabled() || hasSingletonType();
}
bool
JSObject::splicePrototype(JSContext *cx, Class *clasp, Handle<TaggedProto> proto)
{
JS_ASSERT(cx->compartment() == compartment());
RootedObject self(cx, this);
/*
* For singleton types representing only a single JSObject, the proto
* can be rearranged as needed without destroying type information for
* the old or new types. Note that type constraints propagating properties
* from the old prototype are not removed.
*/
JS_ASSERT_IF(cx->typeInferenceEnabled(), self->hasSingletonType());
/* Inner objects may not appear on prototype chains. */
JS_ASSERT_IF(proto.isObject(), !proto.toObject()->getClass()->ext.outerObject);
/*
* Force type instantiation when splicing lazy types. This may fail,
* in which case inference will be disabled for the compartment.
*/
Rooted<TypeObject*> type(cx, self->getType(cx));
if (!type)
return false;
Rooted<TypeObject*> protoType(cx, NULL);
if (proto.isObject()) {
protoType = proto.toObject()->getType(cx);
if (!protoType)
return false;
}
if (!cx->typeInferenceEnabled()) {
TypeObject *type = cx->compartment()->getNewType(cx, clasp, proto);
if (!type)
return false;
self->type_ = type;
return true;
}
type->clasp = clasp;
type->proto = proto.raw();
AutoEnterAnalysis enter(cx);
if (protoType && protoType->unknownProperties() && !type->unknownProperties()) {
type->markUnknown(cx);
return true;
}
if (!type->unknownProperties()) {
/* Update properties on this type with any shared with the prototype. */
unsigned count = type->getPropertyCount();
for (unsigned i = 0; i < count; i++) {
Property *prop = type->getProperty(i);
if (prop && prop->types.hasPropagatedProperty())
type->getFromPrototypes(cx, prop->id, &prop->types, true);
}
}
return true;
}
/* static */ TypeObject *
JSObject::makeLazyType(JSContext *cx, HandleObject obj)
{
JS_ASSERT(obj->hasLazyType());
JS_ASSERT(cx->compartment() == obj->compartment());
/* De-lazification of functions can GC, so we need to do it up here. */
if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpretedLazy()) {
RootedFunction fun(cx, &obj->as<JSFunction>());
if (!fun->getOrCreateScript(cx))
return NULL;
}
Rooted<TaggedProto> proto(cx, obj->getTaggedProto());
TypeObject *type = cx->compartment()->types.newTypeObject(cx, obj->getClass(), proto);
if (!type) {
if (cx->typeInferenceEnabled())
cx->compartment()->types.setPendingNukeTypes(cx);
return obj->type_;
}
if (!cx->typeInferenceEnabled()) {
/* This can only happen if types were previously nuked. */
obj->type_ = type;
return type;
}
AutoEnterAnalysis enter(cx);
/* Fill in the type according to the state of this object. */
type->singleton = obj;
if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted())
type->interpretedFunction = &obj->as<JSFunction>();
if (obj->lastProperty()->hasObjectFlag(BaseShape::ITERATED_SINGLETON))
type->flags |= OBJECT_FLAG_ITERATED;
if (obj->getClass()->emulatesUndefined())
type->flags |= OBJECT_FLAG_EMULATES_UNDEFINED;
/*
* Adjust flags for objects which will have the wrong flags set by just
* looking at the class prototype key.
*/
/* Don't track whether singletons are packed. */
type->flags |= OBJECT_FLAG_NON_PACKED;
if (obj->isIndexed())
type->flags |= OBJECT_FLAG_SPARSE_INDEXES;
if (obj->isArray() && obj->getArrayLength() > INT32_MAX)
type->flags |= OBJECT_FLAG_LENGTH_OVERFLOW;
obj->type_ = type;
return type;
}
/* static */ inline HashNumber
TypeObjectEntry::hash(const Lookup &lookup)
{
return PointerHasher<JSObject *, 3>::hash(lookup.proto.raw()) ^
PointerHasher<Class *, 3>::hash(lookup.clasp);
}
/* static */ inline bool
TypeObjectEntry::match(TypeObject *key, const Lookup &lookup)
{
return key->proto == lookup.proto.raw() && key->clasp == lookup.clasp;
}
#ifdef DEBUG
bool
JSObject::hasNewType(Class *clasp, TypeObject *type)
{
TypeObjectSet &table = compartment()->newTypeObjects;
if (!table.initialized())
return false;
TypeObjectSet::Ptr p = table.lookup(TypeObjectSet::Lookup(clasp, this));
return p && *p == type;
}
#endif /* DEBUG */
/* static */ bool
JSObject::setNewTypeUnknown(JSContext *cx, Class *clasp, HandleObject obj)
{
if (!obj->setFlag(cx, js::BaseShape::NEW_TYPE_UNKNOWN))
return false;
/*
* If the object already has a new type, mark that type as unknown. It will
* not have the SETS_MARKED_UNKNOWN bit set, so may require a type set
* crawl if prototypes of the object change dynamically in the future.
*/
TypeObjectSet &table = cx->compartment()->newTypeObjects;
if (table.initialized()) {
if (TypeObjectSet::Ptr p = table.lookup(TypeObjectSet::Lookup(clasp, obj.get())))
MarkTypeObjectUnknownProperties(cx, *p);
}
return true;
}
TypeObject *
JSCompartment::getNewType(JSContext *cx, Class *clasp, TaggedProto proto_, JSFunction *fun_)
{
JS_ASSERT_IF(fun_, proto_.isObject());
JS_ASSERT_IF(proto_.isObject(), cx->compartment() == proto_.toObject()->compartment());
if (!newTypeObjects.initialized() && !newTypeObjects.init())
return NULL;
TypeObjectSet::AddPtr p = newTypeObjects.lookupForAdd(TypeObjectSet::Lookup(clasp, proto_));
if (p) {
TypeObject *type = *p;
/*
* If set, the type's newScript indicates the script used to create
* all objects in existence which have this type. If there are objects
* in existence which are not created by calling 'new' on newScript,
* we must clear the new script information from the type and will not
* be able to assume any definite properties for instances of the type.
* This case is rare, but can happen if, for example, two scripted
* functions have the same value for their 'prototype' property, or if
* Object.create is called with a prototype object that is also the
* 'prototype' property of some scripted function.
*/
if (type->newScript && type->newScript->fun != fun_)
type->clearNewScript(cx);
return type;
}
Rooted<TaggedProto> proto(cx, proto_);
RootedFunction fun(cx, fun_);
if (proto.isObject() && !proto.toObject()->setDelegate(cx))
return NULL;
bool markUnknown =
proto.isObject()
? proto.toObject()->lastProperty()->hasObjectFlag(BaseShape::NEW_TYPE_UNKNOWN)
: true;
RootedTypeObject type(cx, types.newTypeObject(cx, clasp, proto, markUnknown));
if (!type)
return NULL;
if (!newTypeObjects.relookupOrAdd(p, TypeObjectSet::Lookup(clasp, proto), type.get()))
return NULL;
if (!cx->typeInferenceEnabled())
return type;
AutoEnterAnalysis enter(cx);
/*
* Set the special equality flag for types whose prototype also has the
* flag set. This is a hack, :XXX: need a real correspondence between
* types and the possible js::Class of objects with that type.
*/
if (proto.isObject()) {
RootedObject obj(cx, proto.toObject());
if (fun)
CheckNewScriptProperties(cx, type, fun);
if (obj->is<RegExpObject>()) {
AddTypeProperty(cx, type, "source", types::Type::StringType());
AddTypeProperty(cx, type, "global", types::Type::BooleanType());
AddTypeProperty(cx, type, "ignoreCase", types::Type::BooleanType());
AddTypeProperty(cx, type, "multiline", types::Type::BooleanType());
AddTypeProperty(cx, type, "sticky", types::Type::BooleanType());
AddTypeProperty(cx, type, "lastIndex", types::Type::Int32Type());
}
if (obj->is<StringObject>())
AddTypeProperty(cx, type, "length", Type::Int32Type());
}
/*
* The new type is not present in any type sets, so mark the object as
* unknown in all type sets it appears in. This allows the prototype of
* such objects to mutate freely without triggering an expensive walk of
* the compartment's type sets. (While scripts normally don't mutate
* __proto__, the browser will for proxies and such, and we need to
* accommodate this behavior).
*/
if (type->unknownProperties())
type->flags |= OBJECT_FLAG_SETS_MARKED_UNKNOWN;
return type;
}
TypeObject *
JSObject::getNewType(JSContext *cx, Class *clasp, JSFunction *fun)
{
return cx->compartment()->getNewType(cx, clasp, this, fun);
}
TypeObject *
JSCompartment::getLazyType(JSContext *cx, Class *clasp, TaggedProto proto)
{
JS_ASSERT(cx->compartment() == this);
JS_ASSERT_IF(proto.isObject(), cx->compartment() == proto.toObject()->compartment());
AutoEnterAnalysis enter(cx);
TypeObjectSet &table = cx->compartment()->lazyTypeObjects;
if (!table.initialized() && !table.init())
return NULL;
TypeObjectSet::AddPtr p = table.lookupForAdd(TypeObjectSet::Lookup(clasp, proto));
if (p) {
TypeObject *type = *p;
JS_ASSERT(type->lazy());
return type;
}
Rooted<TaggedProto> protoRoot(cx, proto);
TypeObject *type = cx->compartment()->types.newTypeObject(cx, clasp, protoRoot, false);
if (!type)
return NULL;
if (!table.relookupOrAdd(p, TypeObjectSet::Lookup(clasp, protoRoot), type))
return NULL;
type->singleton = (JSObject *) TypeObject::LAZY_SINGLETON;
return type;
}
/////////////////////////////////////////////////////////////////////
// Tracing
/////////////////////////////////////////////////////////////////////
static void
CrashAtUnhandlableOOM(const char *reason)
{
char msgbuf[1024];
JS_snprintf(msgbuf, sizeof(msgbuf), "[unhandlable oom] %s", reason);
MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__);
MOZ_CRASH();
}
void
TypeSet::sweep(Zone *zone)
{
JS_ASSERT(!purged());
/*
* Purge references to type objects that are no longer live. Type sets hold
* only weak references. For type sets containing more than one object,
* live entries in the object hash need to be copied to the zone's
* new arena.
*/
unsigned objectCount = baseObjectCount();
if (objectCount >= 2) {
unsigned oldCapacity = HashSetCapacity(objectCount);
TypeObjectKey **oldArray = objectSet;
clearObjects();
objectCount = 0;
for (unsigned i = 0; i < oldCapacity; i++) {
TypeObjectKey *object = oldArray[i];
if (object && !IsAboutToBeFinalized(object)) {
TypeObjectKey **pentry =
HashSetInsert<TypeObjectKey *,TypeObjectKey,TypeObjectKey>
(zone->types.typeLifoAlloc, objectSet, objectCount, object);
if (!pentry)
CrashAtUnhandlableOOM("OOM in ConstraintTypeSet::sweep");
*pentry = object;
}
}
setBaseObjectCount(objectCount);
} else if (objectCount == 1) {
TypeObjectKey *object = (TypeObjectKey *) objectSet;
if (IsAboutToBeFinalized(object)) {
objectSet = NULL;
setBaseObjectCount(0);
}
}
/*
* All constraints are wiped out on each GC, including those propagating
* into this type set from prototype properties.
*/
constraintList = NULL;
flags &= ~TYPE_FLAG_PROPAGATED_PROPERTY;
}
inline void
TypeObject::clearProperties()
{
setBasePropertyCount(0);
propertySet = NULL;
}
/*
* Before sweeping the arenas themselves, scan all type objects in a
* compartment to fixup weak references: property type sets referencing dead
* JS and type objects, and singleton JS objects whose type is not referenced
* elsewhere. This also releases memory associated with dead type objects,
* so that type objects do not need later finalization.
*/
inline void
TypeObject::sweep(FreeOp *fop)
{
/*
* We may be regenerating existing type sets containing this object,
* so reset contributions on each GC to avoid tripping the limit.
*/
contribution = 0;
if (singleton) {
JS_ASSERT(!newScript);
/*
* All properties can be discarded. We will regenerate them as needed
* as code gets reanalyzed.
*/
clearProperties();
return;
}
if (!isMarked()) {
if (newScript)
fop->free_(newScript);
return;
}
js::LifoAlloc &typeLifoAlloc = zone()->types.typeLifoAlloc;
/*
* Properties were allocated from the old arena, and need to be copied over
* to the new one. Don't hang onto properties without the OWN_PROPERTY
* flag; these were never directly assigned, and get any possible values
* from the object's prototype.
*/
unsigned propertyCount = basePropertyCount();
if (propertyCount >= 2) {
unsigned oldCapacity = HashSetCapacity(propertyCount);
Property **oldArray = propertySet;
clearProperties();
propertyCount = 0;
for (unsigned i = 0; i < oldCapacity; i++) {
Property *prop = oldArray[i];
if (prop && prop->types.ownProperty(false)) {
Property *newProp = typeLifoAlloc.new_<Property>(*prop);
if (!newProp)
CrashAtUnhandlableOOM("OOM in TypeObject::sweep");
Property **pentry =
HashSetInsert<jsid,Property,Property>
(typeLifoAlloc, propertySet, propertyCount, prop->id);
if (!pentry)
CrashAtUnhandlableOOM("OOM in TypeObject::sweep");
*pentry = newProp;
newProp->types.sweep(zone());
}
}
setBasePropertyCount(propertyCount);
} else if (propertyCount == 1) {
Property *prop = (Property *) propertySet;
if (prop->types.ownProperty(false)) {
Property *newProp = typeLifoAlloc.new_<Property>(*prop);
if (!newProp)
CrashAtUnhandlableOOM("OOM in TypeObject::sweep");
propertySet = (Property **) newProp;
newProp->types.sweep(zone());
} else {
propertySet = NULL;
setBasePropertyCount(0);
}
}
if (basePropertyCount() <= SET_ARRAY_SIZE) {
for (unsigned i = 0; i < basePropertyCount(); i++)
JS_ASSERT(propertySet[i]);
}
/*
* The GC will clear out the constraints ensuring the correctness of the
* newScript information, these constraints will need to be regenerated
* the next time we compile code which depends on this info.
*/
if (newScript)
flags |= OBJECT_FLAG_NEW_SCRIPT_REGENERATE;
}
void
TypeCompartment::sweep(FreeOp *fop)
{
/*
* Iterate through the array/object type tables and remove all entries
* referencing collected data. These tables only hold weak references.
*/
if (arrayTypeTable) {
for (ArrayTypeTable::Enum e(*arrayTypeTable); !e.empty(); e.popFront()) {
const ArrayTableKey &key = e.front().key;
JS_ASSERT(e.front().value->proto == key.proto);
JS_ASSERT(!key.type.isSingleObject());
bool remove = false;
TypeObject *typeObject = NULL;
if (key.type.isTypeObject()) {
typeObject = key.type.typeObject();
if (IsTypeObjectAboutToBeFinalized(&typeObject))
remove = true;
}
if (IsTypeObjectAboutToBeFinalized(e.front().value.unsafeGet()))
remove = true;
if (remove) {
e.removeFront();
} else if (typeObject && typeObject != key.type.typeObject()) {
ArrayTableKey newKey;
newKey.type = Type::ObjectType(typeObject);
newKey.proto = key.proto;
e.rekeyFront(newKey);
}
}
}
if (objectTypeTable) {
for (ObjectTypeTable::Enum e(*objectTypeTable); !e.empty(); e.popFront()) {
const ObjectTableKey &key = e.front().key;
ObjectTableEntry &entry = e.front().value;
bool remove = false;
if (IsTypeObjectAboutToBeFinalized(entry.object.unsafeGet()))
remove = true;
if (IsShapeAboutToBeFinalized(entry.shape.unsafeGet()))
remove = true;
for (unsigned i = 0; !remove && i < key.nproperties; i++) {
if (JSID_IS_STRING(key.properties[i])) {
JSString *str = JSID_TO_STRING(key.properties[i]);
if (IsStringAboutToBeFinalized(&str))
remove = true;
JS_ASSERT(AtomToId((JSAtom *)str) == key.properties[i]);
}
JS_ASSERT(!entry.types[i].isSingleObject());
TypeObject *typeObject = NULL;
if (entry.types[i].isTypeObject()) {
typeObject = entry.types[i].typeObject();
if (IsTypeObjectAboutToBeFinalized(&typeObject))
remove = true;
else if (typeObject != entry.types[i].typeObject())
entry.types[i] = Type::ObjectType(typeObject);
}
}
if (remove) {
js_free(key.properties);
js_free(entry.types);
e.removeFront();
}
}
}
if (allocationSiteTable) {
for (AllocationSiteTable::Enum e(*allocationSiteTable); !e.empty(); e.popFront()) {
AllocationSiteKey key = e.front().key;
bool keyDying = IsScriptAboutToBeFinalized(&key.script);
bool valDying = IsTypeObjectAboutToBeFinalized(e.front().value.unsafeGet());
if (keyDying || valDying)
e.removeFront();
else if (key.script != e.front().key.script)
e.rekeyFront(key);
}
}
/*
* The pending array is reset on GC, it can grow large (75+ KB) and is easy
* to reallocate if the compartment becomes active again.
*/
if (pendingArray)
fop->free_(pendingArray);
pendingArray = NULL;
pendingCapacity = 0;
sweepCompilerOutputs(fop, true);
}
void
TypeCompartment::sweepShapes(FreeOp *fop)
{
/*
* Sweep any weak shape references that may be finalized even if a GC is
* preserving type information.
*/
if (objectTypeTable) {
for (ObjectTypeTable::Enum e(*objectTypeTable); !e.empty(); e.popFront()) {
const ObjectTableKey &key = e.front().key;
ObjectTableEntry &entry = e.front().value;
if (IsShapeAboutToBeFinalized(entry.shape.unsafeGet())) {
fop->free_(key.properties);
fop->free_(entry.types);
e.removeFront();
}
}
}
}
void
TypeCompartment::sweepCompilerOutputs(FreeOp *fop, bool discardConstraints)
{
if (constrainedOutputs) {
if (discardConstraints) {
JS_ASSERT(compiledInfo.outputIndex == RecompileInfo::NoCompilerRunning);
#if DEBUG
for (unsigned i = 0; i < constrainedOutputs->length(); i++) {
CompilerOutput &co = (*constrainedOutputs)[i];
JS_ASSERT(!co.isValid());
}
#endif
fop->delete_(constrainedOutputs);
constrainedOutputs = NULL;
} else {
// Constraints have captured an index to the constrained outputs
// vector. Thus, we invalidate all compilations except the one
// which is potentially running now.
size_t len = constrainedOutputs->length();
for (unsigned i = 0; i < len; i++) {
if (i != compiledInfo.outputIndex) {
CompilerOutput &co = (*constrainedOutputs)[i];
JS_ASSERT(!co.isValid());
co.invalidate();
}
}
}
}
if (pendingRecompiles) {
fop->delete_(pendingRecompiles);
pendingRecompiles = NULL;
}
}
void
JSCompartment::sweepNewTypeObjectTable(TypeObjectSet &table)
{
gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP_TABLES_TYPE_OBJECT);
JS_ASSERT(zone()->isGCSweeping());
if (table.initialized()) {
for (TypeObjectSet::Enum e(table); !e.empty(); e.popFront()) {
TypeObject *type = e.front();
if (IsTypeObjectAboutToBeFinalized(&type))
e.removeFront();
else if (type != e.front())
e.rekeyFront(TypeObjectSet::Lookup(type->clasp, type->proto.get()), type);
}
}
}
TypeCompartment::~TypeCompartment()
{
if (pendingArray)
js_free(pendingArray);
if (arrayTypeTable)
js_delete(arrayTypeTable);
if (objectTypeTable)
js_delete(objectTypeTable);
if (allocationSiteTable)
js_delete(allocationSiteTable);
}
/* static */ void
TypeScript::Sweep(FreeOp *fop, JSScript *script)
{
JSCompartment *compartment = script->compartment();
JS_ASSERT(compartment->zone()->isGCSweeping());
JS_ASSERT(compartment->zone()->types.inferenceEnabled);
unsigned num = NumTypeSets(script);
TypeSet *typeArray = script->types->typeArray();
/* Remove constraints and references to dead objects from the persistent type sets. */
for (unsigned i = 0; i < num; i++)
typeArray[i].sweep(compartment->zone());
TypeResult **presult = &script->types->dynamicList;
while (*presult) {
TypeResult *result = *presult;
Type type = result->type;
if (!type.isUnknown() && !type.isAnyObject() && type.isObject() &&
IsAboutToBeFinalized(type.objectKey()))
{
*presult = result->next;
fop->delete_(result);
} else {
presult = &result->next;
}
}
/*
* Freeze constraints on stack type sets need to be regenerated the next
* time the script is analyzed.
*/
script->hasFreezeConstraints = false;
}
void
TypeScript::destroy()
{
while (dynamicList) {
TypeResult *next = dynamicList->next;
js_delete(dynamicList);
dynamicList = next;
}
js_free(this);
}
/* static */ void
TypeScript::AddFreezeConstraints(JSContext *cx, JSScript *script)
{
if (script->hasFreezeConstraints)
return;
script->hasFreezeConstraints = true;
/*
* Adding freeze constraints to a script ensures that code for the script
* will be recompiled any time any type set for stack values in the script
* change: these type sets are implicitly frozen during compilation.
*
* To ensure this occurs, we don't need to add freeze constraints to the
* type sets for every stack value, but rather only the input type sets
* to analysis of the stack in a script. The contents of the stack sets
* are completely determined by these input sets and by any dynamic types
* in the script (for which TypeDynamicResult will trigger recompilation).
*
* Add freeze constraints to each input type set, which includes sets for
* all arguments, locals, and monitored type sets in the script. This
* includes all type sets in the TypeScript except the script's return
* value types.
*/
size_t count = TypeScript::NumTypeSets(script);
TypeSet *returnTypes = TypeScript::ReturnTypes(script);
TypeSet *array = script->types->typeArray();
for (size_t i = 0; i < count; i++) {
TypeSet *types = &array[i];
if (types == returnTypes)
continue;
JS_ASSERT(types->constraintsPurged());
types->add(cx, cx->analysisLifoAlloc().new_<TypeConstraintFreezeStack>(script), false);
}
}
/* static */ void
TypeScript::Purge(JSContext *cx, HandleScript script)
{
if (!script->types)
return;
unsigned num = NumTypeSets(script);
TypeSet *typeArray = script->types->typeArray();
TypeSet *returnTypes = ReturnTypes(script);
bool ranInference = script->hasAnalysis() && script->analysis()->ranInference();
script->clearAnalysis();
if (!ranInference && !script->hasFreezeConstraints) {
/*
* Even if the script hasn't been analyzed by TI, TypeConstraintCall
* can still add constraints on 'this' for 'new' calls.
*/
ThisTypes(script)->constraintList = NULL;
#ifdef DEBUG
for (size_t i = 0; i < num; i++) {
TypeSet *types = &typeArray[i];
JS_ASSERT_IF(types != returnTypes, !types->constraintList);
}
#endif
return;
}
for (size_t i = 0; i < num; i++) {
TypeSet *types = &typeArray[i];
if (types != returnTypes)
types->constraintList = NULL;
}
if (script->hasFreezeConstraints)
TypeScript::AddFreezeConstraints(cx, script);
}
void
TypeCompartment::maybePurgeAnalysis(JSContext *cx, bool force)
{
// FIXME bug 781657
return;
JS_ASSERT(this == &cx->compartment()->types);
JS_ASSERT(!cx->compartment()->activeAnalysis);
if (!cx->typeInferenceEnabled())
return;
size_t triggerBytes = cx->runtime()->analysisPurgeTriggerBytes;
size_t beforeUsed = cx->compartment()->analysisLifoAlloc.used();
if (!force) {
if (!triggerBytes || triggerBytes >= beforeUsed)
return;
}
AutoEnterAnalysis enter(cx);
/* Reset the analysis pool, making its memory available for reuse. */
cx->compartment()->analysisLifoAlloc.releaseAll();
uint64_t start = PRMJ_Now();
for (gc::CellIter i(cx->zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
RootedScript script(cx, i.get<JSScript>());
if (script->compartment() == cx->compartment())
TypeScript::Purge(cx, script);
}
uint64_t done = PRMJ_Now();
if (cx->runtime()->analysisPurgeCallback) {
size_t afterUsed = cx->compartment()->analysisLifoAlloc.used();
size_t typeUsed = cx->typeLifoAlloc().used();
char buf[1000];
JS_snprintf(buf, sizeof(buf),
"Total Time %.2f ms, %d bytes before, %d bytes after\n",
(done - start) / double(PRMJ_USEC_PER_MSEC),
(int) (beforeUsed + typeUsed),
(int) (afterUsed + typeUsed));
JSString *desc = JS_NewStringCopyZ(cx, buf);
if (!desc) {
cx->clearPendingException();
return;
}
JS::Rooted<JSFlatString*> flat(cx, &desc->asFlat());
cx->runtime()->analysisPurgeCallback(cx->runtime(), flat);
}
}
static void
SizeOfScriptTypeInferenceData(JSScript *script, JS::TypeInferenceSizes *sizes,
JSMallocSizeOfFun mallocSizeOf)
{
TypeScript *typeScript = script->types;
if (!typeScript)
return;
/* If TI is disabled, a single TypeScript is still present. */
if (!script->compartment()->zone()->types.inferenceEnabled) {
sizes->typeScripts += mallocSizeOf(typeScript);
return;
}
sizes->typeScripts += mallocSizeOf(typeScript);
TypeResult *result = typeScript->dynamicList;
while (result) {
sizes->typeResults += mallocSizeOf(result);
result = result->next;
}
}
void
Zone::sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf, size_t *typePool)
{
*typePool += types.typeLifoAlloc.sizeOfExcludingThis(mallocSizeOf);
}
void
JSCompartment::sizeOfTypeInferenceData(JS::TypeInferenceSizes *sizes, JSMallocSizeOfFun mallocSizeOf)
{
sizes->analysisPool += analysisLifoAlloc.sizeOfExcludingThis(mallocSizeOf);
/* Pending arrays are cleared on GC along with the analysis pool. */
sizes->pendingArrays += mallocSizeOf(types.pendingArray);
/* TypeCompartment::pendingRecompiles is non-NULL only while inference code is running. */
JS_ASSERT(!types.pendingRecompiles);
for (gc::CellIter i(zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
JSScript *script = i.get<JSScript>();
if (script->compartment() == this)
SizeOfScriptTypeInferenceData(script, sizes, mallocSizeOf);
}
if (types.allocationSiteTable)
sizes->allocationSiteTables += types.allocationSiteTable->sizeOfIncludingThis(mallocSizeOf);
if (types.arrayTypeTable)
sizes->arrayTypeTables += types.arrayTypeTable->sizeOfIncludingThis(mallocSizeOf);
if (types.objectTypeTable) {
sizes->objectTypeTables += types.objectTypeTable->sizeOfIncludingThis(mallocSizeOf);
for (ObjectTypeTable::Enum e(*types.objectTypeTable);
!e.empty();
e.popFront())
{
const ObjectTableKey &key = e.front().key;
const ObjectTableEntry &value = e.front().value;
/* key.ids and values.types have the same length. */
sizes->objectTypeTables += mallocSizeOf(key.properties) + mallocSizeOf(value.types);
}
}
}
size_t
TypeObject::sizeOfExcludingThis(JSMallocSizeOfFun mallocSizeOf)
{
if (singleton) {
/*
* Properties and associated type sets for singletons are cleared on
* every GC. The type object is normally destroyed too, but we don't
* charge this to 'temporary' as this is not for GC heap values.
*/
JS_ASSERT(!newScript);
return 0;
}
return mallocSizeOf(newScript);
}
TypeZone::TypeZone(Zone *zone)
: zone_(zone),
typeLifoAlloc(TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
pendingNukeTypes(false),
inferenceEnabled(false)
{
}
TypeZone::~TypeZone()
{
}
void
TypeZone::sweep(FreeOp *fop, bool releaseTypes)
{
JS_ASSERT(zone()->isGCSweeping());
JSRuntime *rt = zone()->rt;
/*
* Clear the analysis pool, but don't release its data yet. While
* sweeping types any live data will be allocated into the pool.
*/
LifoAlloc oldAlloc(typeLifoAlloc.defaultChunkSize());
oldAlloc.steal(&typeLifoAlloc);
/*
* Sweep analysis information and everything depending on it from the
* compartment, including all remaining mjit code if inference is
* enabled in the compartment.
*/
if (inferenceEnabled) {
gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_DISCARD_TI);
for (CellIterUnderGC i(zone(), FINALIZE_SCRIPT); !i.done(); i.next()) {
JSScript *script = i.get<JSScript>();
if (script->types) {
types::TypeScript::Sweep(fop, script);
if (releaseTypes) {
script->types->destroy();
script->types = NULL;
}
}
}
}
{
gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_SWEEP_TYPES);
for (gc::CellIterUnderGC iter(zone(), gc::FINALIZE_TYPE_OBJECT);
!iter.done(); iter.next())
{
TypeObject *object = iter.get<TypeObject>();
object->sweep(fop);
}
for (CompartmentsInZoneIter comp(zone()); !comp.done(); comp.next())
comp->types.sweep(fop);
}
{
gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_CLEAR_SCRIPT_ANALYSIS);
for (CellIterUnderGC i(zone(), FINALIZE_SCRIPT); !i.done(); i.next()) {
JSScript *script = i.get<JSScript>();
script->clearAnalysis();
script->clearPropertyReadTypes();
}
}
{
gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_FREE_TI_ARENA);
rt->freeLifoAlloc.transferFrom(&oldAlloc);
}
}