gecko/js/src/jsinfer.cpp
2010-11-30 09:04:25 -08:00

3834 lines
117 KiB
C++

/* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
/* vim: set ts=40 sw=4 et tw=99: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SpiderMonkey bytecode type inference
*
* The Initial Developer of the Original Code is
* Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Brian Hackett <bhackett@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "jsapi.h"
#include "jsautooplen.h"
#include "jsbit.h"
#include "jsbool.h"
#include "jsdate.h"
#include "jsexn.h"
#include "jsgc.h"
#include "jsinfer.h"
#include "jsmath.h"
#include "jsnum.h"
#include "jsobj.h"
#include "jsscript.h"
#include "jscntxt.h"
#include "jsscan.h"
#include "jsscope.h"
#include "jsstr.h"
#include "jstl.h"
#include "jsiter.h"
#include "methodjit/MethodJIT.h"
#include "methodjit/Retcon.h"
#include "jsatominlines.h"
#include "jsinferinlines.h"
#include "jsobjinlines.h"
#include "jsscriptinlines.h"
#include <zlib.h>
#ifdef JS_HAS_XML_SUPPORT
#include "jsxml.h"
#endif
static inline jsid
id_prototype(JSContext *cx) {
return ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
}
static inline jsid
id_arguments(JSContext *cx) {
return ATOM_TO_JSID(cx->runtime->atomState.argumentsAtom);
}
static inline jsid
id_length(JSContext *cx) {
return ATOM_TO_JSID(cx->runtime->atomState.lengthAtom);
}
static inline jsid
id___proto__(JSContext *cx) {
return ATOM_TO_JSID(cx->runtime->atomState.protoAtom);
}
static inline jsid
id_constructor(JSContext *cx) {
return ATOM_TO_JSID(cx->runtime->atomState.constructorAtom);
}
static inline jsid
id_caller(JSContext *cx) {
return ATOM_TO_JSID(cx->runtime->atomState.callerAtom);
}
static inline jsid
id_toString(JSContext *cx)
{
return ATOM_TO_JSID(cx->runtime->atomState.toStringAtom);
}
static inline jsid
id_toSource(JSContext *cx)
{
return ATOM_TO_JSID(cx->runtime->atomState.toSourceAtom);
}
namespace js {
namespace types {
#ifdef DEBUG
unsigned TypeSet::typesetCount = 0;
unsigned TypeConstraint::constraintCount = 0;
#endif
static const char *js_CodeNameTwo[] = {
#define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
name,
#include "jsopcode.tbl"
#undef OPDEF
};
/////////////////////////////////////////////////////////////////////
// Logging
/////////////////////////////////////////////////////////////////////
static bool InferSpewActive(SpewChannel channel)
{
static bool active[SPEW_COUNT];
static bool checked = false;
if (!checked) {
checked = true;
PodArrayZero(active);
const char *env = getenv("INFERFLAGS");
if (!env)
return false;
if (strstr(env, "dynamic"))
active[ISpewDynamic] = true;
if (strstr(env, "ops"))
active[ISpewOps] = true;
if (strstr(env, "result"))
active[ISpewResult] = true;
if (strstr(env, "full")) {
for (unsigned i = 0; i < SPEW_COUNT; i++)
active[i] = true;
}
}
return active[channel];
}
#ifdef DEBUG
const char *
TypeObjectString(TypeObject *object)
{
return TypeIdString(object->name);
}
const char *
TypeString(jstype type)
{
switch (type) {
case TYPE_UNDEFINED:
return "void";
case TYPE_NULL:
return "null";
case TYPE_BOOLEAN:
return "bool";
case TYPE_INT32:
return "int";
case TYPE_DOUBLE:
return "float";
case TYPE_STRING:
return "string";
case TYPE_UNKNOWN:
return "unknown";
default: {
JS_ASSERT(TypeIsObject(type));
TypeObject *object = (TypeObject *) type;
return TypeIdString(object->name);
}
}
}
void 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);
}
#endif
void TypeFailure(JSContext *cx, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stdout, "[infer failure] ");
vfprintf(stdout, fmt, ap);
fprintf(stdout, "\n");
va_end(ap);
cx->compartment->types.finish(cx, cx->compartment);
fflush(stdout);
*((int*)NULL) = 0; /* Type warnings */
}
/////////////////////////////////////////////////////////////////////
// TypeSet
/////////////////////////////////////////////////////////////////////
inline void
TypeSet::add(JSContext *cx, TypeConstraint *constraint, bool callExisting)
{
InferSpew(ISpewOps, "addConstraint: T%u C%u %s",
id(), constraint->id(), constraint->kind());
JS_ASSERT(constraint->next == NULL);
constraint->next = constraintList;
constraintList = constraint;
if (!callExisting)
return;
if (typeFlags & TYPE_FLAG_UNKNOWN) {
cx->compartment->types.addPending(cx, constraint, this, TYPE_UNKNOWN);
cx->compartment->types.resolvePending(cx);
return;
}
for (jstype type = TYPE_UNDEFINED; type <= TYPE_STRING; type++) {
if (typeFlags & (1 << type))
cx->compartment->types.addPending(cx, constraint, this, type);
}
if (objectCount >= 2) {
unsigned objectCapacity = HashSetCapacity(objectCount);
for (unsigned i = 0; i < objectCapacity; i++) {
TypeObject *object = objectSet[i];
if (object)
cx->compartment->types.addPending(cx, constraint, this, (jstype) object);
}
} else if (objectCount == 1) {
TypeObject *object = (TypeObject*) objectSet;
cx->compartment->types.addPending(cx, constraint, this, (jstype) object);
}
cx->compartment->types.resolvePending(cx);
}
void
TypeSet::print(JSContext *cx)
{
if (typeFlags == 0) {
printf(" missing");
return;
}
if (typeFlags & TYPE_FLAG_UNKNOWN)
printf(" unknown");
if (typeFlags & TYPE_FLAG_UNDEFINED)
printf(" void");
if (typeFlags & TYPE_FLAG_NULL)
printf(" null");
if (typeFlags & TYPE_FLAG_BOOLEAN)
printf(" bool");
if (typeFlags & TYPE_FLAG_INT32)
printf(" int");
if (typeFlags & TYPE_FLAG_DOUBLE)
printf(" float");
if (typeFlags & TYPE_FLAG_STRING)
printf(" string");
if (typeFlags & TYPE_FLAG_OBJECT) {
printf(" object[%u]", objectCount);
if (objectCount >= 2) {
unsigned objectCapacity = HashSetCapacity(objectCount);
for (unsigned i = 0; i < objectCapacity; i++) {
TypeObject *object = objectSet[i];
if (object)
printf(" %s", TypeIdString(object->name));
}
} else if (objectCount == 1) {
TypeObject *object = (TypeObject*) objectSet;
printf(" %s", TypeIdString(object->name));
}
}
}
/* Standard subset constraint, propagate all types from one set to another. */
class TypeConstraintSubset : public TypeConstraint
{
public:
TypeSet *target;
TypeConstraintSubset(TypeSet *target)
: TypeConstraint("subset"), target(target)
{
JS_ASSERT(target);
}
void newType(JSContext *cx, TypeSet *source, jstype type);
};
void
TypeSet::addSubset(JSContext *cx, JSArenaPool &pool, TypeSet *target)
{
JS_ASSERT(this->pool == &pool);
add(cx, ArenaNew<TypeConstraintSubset>(pool, target));
}
/* Constraints for reads/writes on object properties. */
class TypeConstraintProp : public TypeConstraint
{
public:
/* Bytecode this access occurs at. */
analyze::Bytecode *code;
/*
* 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.
*/
bool assign;
TypeSet *target;
/* Property being accessed. */
jsid id;
TypeConstraintProp(analyze::Bytecode *code, TypeSet *target, jsid id, bool assign)
: TypeConstraint("prop"), code(code), assign(assign), target(target), id(id)
{
JS_ASSERT(code);
JS_ASSERT(id == MakeTypeId(id));
/* If the target is NULL, this is as an inc/dec on the property. */
JS_ASSERT_IF(!target, assign);
}
void newType(JSContext *cx, TypeSet *source, jstype type);
};
void
TypeSet::addGetProperty(JSContext *cx, analyze::Bytecode *code, TypeSet *target, jsid id)
{
JS_ASSERT(this->pool == &code->pool());
add(cx, ArenaNew<TypeConstraintProp>(code->pool(), code, target, id, false));
}
void
TypeSet::addSetProperty(JSContext *cx, analyze::Bytecode *code, TypeSet *target, jsid id)
{
JS_ASSERT(this->pool == &code->pool());
add(cx, ArenaNew<TypeConstraintProp>(code->pool(), code, target, id, true));
}
/*
* Constraints for reads/writes on object elements, which may either be integer
* element accesses or arbitrary accesses depending on the index type.
*/
class TypeConstraintElem : public TypeConstraint
{
public:
/* Bytecode performing the element access. */
analyze::Bytecode *code;
/* Types of the object being accessed. */
TypeSet *object;
/* Target to use for the ConstraintProp. */
TypeSet *target;
/* As for ConstraintProp. */
bool assign;
TypeConstraintElem(analyze::Bytecode *code, TypeSet *object, TypeSet *target, bool assign)
: TypeConstraint("elem"), code(code), object(object), target(target), assign(assign)
{}
void newType(JSContext *cx, TypeSet *source, jstype type);
};
void
TypeSet::addGetElem(JSContext *cx, analyze::Bytecode *code, TypeSet *object, TypeSet *target)
{
JS_ASSERT(this->pool == &code->pool());
add(cx, ArenaNew<TypeConstraintElem>(code->pool(), code, object, target, false));
}
void
TypeSet::addSetElem(JSContext *cx, analyze::Bytecode *code, TypeSet *object, TypeSet *target)
{
JS_ASSERT(this->pool == &code->pool());
add(cx, ArenaNew<TypeConstraintElem>(code->pool(), code, object, target, true));
}
/*
* 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)
: TypeConstraint("call"), callsite(callsite)
{}
void newType(JSContext *cx, TypeSet *source, jstype type);
};
void
TypeSet::addCall(JSContext *cx, TypeCallsite *site)
{
JS_ASSERT(this->pool == &site->pool());
add(cx, ArenaNew<TypeConstraintCall>(site->pool(), site));
}
/* Constraints for arithmetic operations. */
class TypeConstraintArith : public TypeConstraint
{
public:
/* Bytecode the operation occurs at. */
analyze::Bytecode *code;
/* Type set receiving the result of the arithmetic. */
TypeSet *target;
/* For addition operations, the other operand. */
TypeSet *other;
TypeConstraintArith(analyze::Bytecode *code, TypeSet *target, TypeSet *other)
: TypeConstraint("arith"), code(code), target(target), other(other)
{
JS_ASSERT(code);
JS_ASSERT(target);
}
void newType(JSContext *cx, TypeSet *source, jstype type);
};
void
TypeSet::addArith(JSContext *cx, JSArenaPool &pool, analyze::Bytecode *code,
TypeSet *target, TypeSet *other)
{
JS_ASSERT(this->pool == &pool);
add(cx, ArenaNew<TypeConstraintArith>(pool, code, target, other));
}
/* Subset constraint which transforms primitive values into appropriate objects. */
class TypeConstraintTransformThis : public TypeConstraint
{
public:
TypeSet *target;
TypeConstraintTransformThis(TypeSet *target)
: TypeConstraint("transformthis"), target(target)
{}
void newType(JSContext *cx, TypeSet *source, jstype type);
};
void
TypeSet::addTransformThis(JSContext *cx, JSArenaPool &pool, TypeSet *target)
{
JS_ASSERT(this->pool == &pool);
add(cx, ArenaNew<TypeConstraintTransformThis>(pool, target));
}
/* Subset constraint which filters out primitive types. */
class TypeConstraintFilterPrimitive : public TypeConstraint
{
public:
TypeSet *target;
/* Primitive types other than null and undefined are passed through. */
bool onlyNullVoid;
TypeConstraintFilterPrimitive(TypeSet *target, bool onlyNullVoid)
: TypeConstraint("filter"), target(target), onlyNullVoid(onlyNullVoid)
{}
void newType(JSContext *cx, TypeSet *source, jstype type);
};
void
TypeSet::addFilterPrimitives(JSContext *cx, JSArenaPool &pool, TypeSet *target, bool onlyNullVoid)
{
JS_ASSERT(this->pool == &pool);
add(cx, ArenaNew<TypeConstraintFilterPrimitive>(pool, target, onlyNullVoid));
}
/*
* Subset constraint for property reads which monitors accesses on properties
* with scripted getters and polymorphic types.
*/
class TypeConstraintMonitorRead : public TypeConstraint
{
public:
analyze::Bytecode *code;
TypeSet *target;
TypeConstraintMonitorRead(analyze::Bytecode *code, TypeSet *target)
: TypeConstraint("monitorRead"), code(code), target(target)
{}
void newType(JSContext *cx, TypeSet *source, jstype type);
};
void
TypeSet::addMonitorRead(JSContext *cx, JSArenaPool &pool, analyze::Bytecode *code, TypeSet *target)
{
JS_ASSERT(this->pool == &pool);
add(cx, ArenaNew<TypeConstraintMonitorRead>(pool, code, target));
}
/*
* Type constraint which marks the result of 'for in' loops as unknown if the
* iterated value could be a generator.
*/
class TypeConstraintGenerator : public TypeConstraint
{
public:
TypeSet *target;
TypeConstraintGenerator(TypeSet *target)
: TypeConstraint("generator"), target(target)
{}
void newType(JSContext *cx, TypeSet *source, jstype type);
};
/* Update types with the possible values bound by the for loop in code. */
static inline void
SetForTypes(JSContext *cx, const analyze::Script::AnalyzeState &state,
analyze::Bytecode *code, TypeSet *types)
{
JS_ASSERT(code->stackDepth == state.stackDepth);
if (state.popped(0).isForEach)
types->addType(cx, TYPE_UNKNOWN);
else
types->addType(cx, TYPE_STRING);
code->popped(0)->add(cx, ArenaNew<TypeConstraintGenerator>(code->pool(), types));
}
/////////////////////////////////////////////////////////////////////
// TypeConstraint
/////////////////////////////////////////////////////////////////////
void
TypeConstraintSubset::newType(JSContext *cx, TypeSet *source, jstype type)
{
/* Basic subset constraint, move all types to the target. */
target->addType(cx, type);
}
/* Get the object to use for a property access on type. */
static inline TypeObject *
GetPropertyObject(JSContext *cx, jstype type)
{
if (TypeIsObject(type))
return (TypeObject*) type;
/*
* Handle properties attached to primitive types, treating this access as a
* read on the primitive's new object.
*/
switch (type) {
case TYPE_INT32:
case TYPE_DOUBLE:
if (!cx->globalObject->getReservedSlot(JSProto_Number).isObject())
js_InitNumberClass(cx, cx->globalObject);
return cx->getFixedTypeObject(TYPE_OBJECT_NEW_NUMBER);
case TYPE_BOOLEAN:
if (!cx->globalObject->getReservedSlot(JSProto_Boolean).isObject())
js_InitBooleanClass(cx, cx->globalObject);
return cx->getFixedTypeObject(TYPE_OBJECT_NEW_BOOLEAN);
case TYPE_STRING:
if (!cx->globalObject->getReservedSlot(JSProto_String).isObject())
js_InitStringClass(cx, cx->globalObject);
return cx->getFixedTypeObject(TYPE_OBJECT_NEW_STRING);
default:
/* undefined and null do not have properties. */
return NULL;
}
}
/*
* Handle a property access on a specific object. All property accesses go through
* here, whether via x.f, x[f], or global name accesses.
*/
static inline void
PropertyAccess(JSContext *cx, analyze::Bytecode *code, TypeObject *object,
bool assign, TypeSet *target, jsid id)
{
JS_ASSERT_IF(!target, assign);
/* Reads from objects with unknown properties are unknown, writes to such objects are ignored. */
if (object->unknownProperties) {
if (!assign)
target->addType(cx, TYPE_UNKNOWN);
return;
}
/* Monitor assigns on the 'prototype' property. */
if (assign && id == id_prototype(cx)) {
cx->compartment->types.monitorBytecode(cx, code);
return;
}
/* Monitor accesses on other properties with special behavior we don't keep track of. */
if (id == id___proto__(cx) || id == id_constructor(cx) || id == id_caller(cx)) {
if (assign)
cx->compartment->types.monitorBytecode(cx, code);
else
target->addType(cx, TYPE_UNKNOWN);
return;
}
/* Mark arrays with possible non-integer properties as not dense. */
if (assign && !JSID_IS_VOID(id))
cx->markTypeArrayNotPacked(object, true, false);
/* Capture the effects of a standard property access. */
if (target) {
TypeSet *types = object->getProperty(cx, id, assign);
if (assign)
target->addSubset(cx, code->pool(), types);
else
types->addMonitorRead(cx, *object->pool, code, target);
} else {
TypeSet *readTypes = object->getProperty(cx, id, false);
TypeSet *writeTypes = object->getProperty(cx, id, true);
if (code->hasIncDecOverflow)
writeTypes->addType(cx, TYPE_DOUBLE);
readTypes->addArith(cx, *object->pool, code, writeTypes);
}
}
void
TypeConstraintProp::newType(JSContext *cx, TypeSet *source, jstype type)
{
if (type == TYPE_UNKNOWN) {
/*
* Access on an unknown object. Reads produce an unknown result, writes
* need to be monitored. Note: this isn't a problem for handling overflows
* on inc/dec below, as these go through a slow path which must call
* addTypeProperty.
*/
if (assign)
cx->compartment->types.monitorBytecode(cx, code);
else
target->addType(cx, TYPE_UNKNOWN);
return;
}
TypeObject *object = GetPropertyObject(cx, type);
if (object)
PropertyAccess(cx, code, object, assign, target, id);
}
void
TypeConstraintElem::newType(JSContext *cx, TypeSet *source, jstype type)
{
switch (type) {
case TYPE_UNDEFINED:
case TYPE_BOOLEAN:
case TYPE_NULL:
case TYPE_INT32:
case TYPE_DOUBLE:
/*
* Integer index access, these are all covered by the JSID_VOID property.
* We are optimistically treating non-number accesses as not actually occurring,
* and double accesses as getting an integer property. This must be checked
* at runtime.
*/
if (assign)
object->addSetProperty(cx, code, target, JSID_VOID);
else
object->addGetProperty(cx, code, target, JSID_VOID);
break;
default:
/*
* Access to a potentially arbitrary element. Monitor assignments to unknown
* elements, and treat reads of unknown elements as unknown.
*/
if (assign)
cx->compartment->types.monitorBytecode(cx, code);
else
target->addType(cx, TYPE_UNKNOWN);
}
};
class TypeConstraintNewObject : public TypeConstraint
{
TypeSet *target;
public:
TypeConstraintNewObject(TypeSet *target)
: TypeConstraint("newObject"), target(target)
{}
void newType(JSContext *cx, TypeSet *source, jstype type)
{
if (type == TYPE_UNKNOWN) {
target->addType(cx, TYPE_UNKNOWN);
return;
}
/* :FIXME: Handle non-object prototype case dynamically. */
if (TypeIsObject(type)) {
TypeObject *object = (TypeObject *) type;
target->addType(cx, (jstype) object->getNewObject(cx));
}
}
};
void
TypeConstraintCall::newType(JSContext *cx, TypeSet *source, jstype type)
{
if (type == TYPE_UNKNOWN) {
/* Monitor calls on unknown functions. */
cx->compartment->types.monitorBytecode(cx, callsite->code);
return;
}
/* Get the function being invoked. */
TypeFunction *function = NULL;
if (TypeIsObject(type)) {
TypeObject *object = (TypeObject*) type;
if (object->isFunction) {
function = (TypeFunction*) object;
} else {
/* Unknown return value for calls on non-function objects. */
cx->compartment->types.monitorBytecode(cx, callsite->code);
}
}
if (!function)
return;
JSArenaPool &pool = callsite->pool();
if (!function->script) {
JS_ASSERT(function->handler);
if (function->handler == JS_TypeHandlerDynamic) {
/* Unknown result. */
if (callsite->returnTypes)
callsite->returnTypes->addType(cx, TYPE_UNKNOWN);
} else if (function->isGeneric) {
if (callsite->argumentCount == 0) {
/* Generic methods called with zero arguments generate runtime errors. */
return;
}
/*
* Make a new callsite transforming the arguments appropriately, as is
* done by the generic native dispatchers. watch out for cases where the
* first argument is null, which will transform to the global object.
*/
TypeSet *thisTypes = TypeSet::make(cx, pool, "genericthis");
callsite->argumentTypes[0]->addTransformThis(cx, pool, thisTypes);
TypeCallsite *newSite = ArenaNew<TypeCallsite>(pool, callsite->code, callsite->isNew,
callsite->argumentCount - 1);
newSite->thisTypes = thisTypes;
newSite->returnTypes = callsite->returnTypes;
for (unsigned i = 0; i < callsite->argumentCount - 1; i++)
newSite->argumentTypes[i] = callsite->argumentTypes[i + 1];
function->handler(cx, (JSTypeFunction*)function, (JSTypeCallsite*)newSite);
} else {
/* Model the function's effects directly. */
function->handler(cx, (JSTypeFunction*)function, (JSTypeCallsite*)callsite);
}
/*
* When invoking 'new' on natives other than Object, Function, and Array
* (whose handlers take care of the 'new' case), add the 'new' object of
* the function to the return types.
*/
if (callsite->isNew && callsite->returnTypes &&
function != cx->getFixedTypeObject(TYPE_OBJECT_OBJECT) &&
function != cx->getFixedTypeObject(TYPE_OBJECT_FUNCTION) &&
function != cx->getFixedTypeObject(TYPE_OBJECT_ARRAY)) {
if (!function->prototypeObject)
function->getProperty(cx, id_prototype(cx), false);
TypeObject *object = function->prototypeObject->getNewObject(cx);
if (callsite->returnTypes)
callsite->returnTypes->addType(cx, (jstype) object);
}
return;
}
analyze::Script *script = function->script->analysis;
/* Add bindings for the arguments of the call. */
for (unsigned i = 0; i < callsite->argumentCount; i++) {
TypeSet *argTypes = callsite->argumentTypes[i];
jsid id = script->getArgumentId(i);
if (!JSID_IS_VOID(id)) {
TypeSet *types = script->getVariable(cx, id);
argTypes->addSubset(cx, pool, types);
} else {
/*
* This argument exceeds the number of formals. ignore the binding,
* the value can only be accessed through the arguments object,
* which is monitored.
*/
}
}
/* Add void type for any formals in the callee not supplied at the call site. */
for (unsigned i = callsite->argumentCount; i < script->argCount(); i++) {
jsid id = script->getArgumentId(i);
TypeSet *types = script->getVariable(cx, id);
types->addType(cx, TYPE_UNDEFINED);
}
/* Add a binding for the receiver object of the call. */
if (callsite->isNew) {
/* The receiver object is the 'new' object for the function's prototype. */
if (function->unknownProperties) {
script->thisTypes.addType(cx, TYPE_UNKNOWN);
} else {
TypeSet *prototypeTypes = function->getProperty(cx, id_prototype(cx), false);
prototypeTypes->add(cx,
ArenaNew<TypeConstraintNewObject>(*function->pool, &script->thisTypes));
}
/*
* If the script does not return a value then the pushed value is the new
* object (typical case).
*/
if (callsite->returnTypes) {
script->thisTypes.addSubset(cx, script->pool, callsite->returnTypes);
function->returnTypes.addFilterPrimitives(cx, *function->pool,
callsite->returnTypes, false);
}
} else {
if (callsite->thisTypes) {
/* Add a binding for the receiver object of the call. */
callsite->thisTypes->addSubset(cx, pool, &script->thisTypes);
} else {
JS_ASSERT(callsite->thisType != TYPE_NULL);
script->thisTypes.addType(cx, callsite->thisType);
}
/* Add a binding for the return value of the call. */
if (callsite->returnTypes)
function->returnTypes.addSubset(cx, *function->pool, callsite->returnTypes);
}
/* Analyze the function if we have not already done so. */
if (!script->hasAnalyzed())
script->analyze(cx);
}
void
TypeConstraintArith::newType(JSContext *cx, TypeSet *source, jstype type)
{
if (other) {
/*
* Addition operation, consider these cases:
* {int,bool} x {int,bool} -> int
* float x {int,bool,float} -> float
* string x any -> string
*/
switch (type) {
case TYPE_UNDEFINED:
case TYPE_NULL:
case TYPE_INT32:
case TYPE_BOOLEAN:
/* Note: need to account for overflows from, e.g. int + void */
if (other->typeFlags & (TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL |
TYPE_FLAG_INT32 | TYPE_FLAG_BOOLEAN))
target->addType(cx, TYPE_INT32);
if (other->typeFlags & TYPE_FLAG_DOUBLE)
target->addType(cx, TYPE_DOUBLE);
break;
case TYPE_DOUBLE:
if (other->typeFlags & (TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL |
TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_BOOLEAN))
target->addType(cx, TYPE_DOUBLE);
break;
case TYPE_STRING:
target->addType(cx, TYPE_STRING);
break;
default:
/*
* Don't try to model arithmetic on objects, this can invoke valueOf,
* operate on XML objects, etc.
*/
target->addType(cx, TYPE_UNKNOWN);
break;
}
} else {
/* Note: same issues with undefined as addition. */
switch (type) {
case TYPE_UNDEFINED:
case TYPE_NULL:
case TYPE_INT32:
case TYPE_BOOLEAN:
target->addType(cx, TYPE_INT32);
break;
case TYPE_DOUBLE:
target->addType(cx, TYPE_DOUBLE);
break;
default:
target->addType(cx, TYPE_UNKNOWN);
break;
}
}
}
void
TypeConstraintTransformThis::newType(JSContext *cx, TypeSet *source, jstype type)
{
if (type == TYPE_UNKNOWN || TypeIsObject(type)) {
target->addType(cx, type);
return;
}
/* TODO: handle strict mode code correctly. */
TypeObject *object = NULL;
switch (type) {
case TYPE_NULL:
case TYPE_UNDEFINED:
object = cx->getGlobalTypeObject();
break;
case TYPE_INT32:
case TYPE_DOUBLE:
object = cx->getFixedTypeObject(TYPE_OBJECT_NEW_NUMBER);
break;
case TYPE_BOOLEAN:
object = cx->getFixedTypeObject(TYPE_OBJECT_NEW_BOOLEAN);
break;
case TYPE_STRING:
object = cx->getFixedTypeObject(TYPE_OBJECT_NEW_STRING);
break;
default:
JS_NOT_REACHED("Bad type");
}
target->addType(cx, (jstype) object);
}
void
TypeConstraintFilterPrimitive::newType(JSContext *cx, TypeSet *source, jstype type)
{
if (onlyNullVoid) {
if (type == TYPE_NULL || type == TYPE_UNDEFINED)
return;
} else if (type != TYPE_UNKNOWN && TypeIsPrimitive(type)) {
return;
}
target->addType(cx, type);
}
void
TypeConstraintMonitorRead::newType(JSContext *cx, TypeSet *source, jstype type)
{
if (type == (jstype) cx->getFixedTypeObject(TYPE_OBJECT_GETSET)) {
target->addType(cx, TYPE_UNKNOWN);
return;
}
target->addType(cx, type);
}
void
TypeConstraintGenerator::newType(JSContext *cx, TypeSet *source, jstype type)
{
if (type == TYPE_UNKNOWN)
target->addType(cx, TYPE_UNKNOWN);
if (type == (jstype) cx->getFixedTypeObject(TYPE_OBJECT_NEW_ITERATOR) ||
type == (jstype) cx->getFixedTypeObject(TYPE_OBJECT_NEW_GENERATOR)) {
target->addType(cx, TYPE_UNKNOWN);
}
}
/* Constraint marking incoming arrays as possibly packed. */
class TypeConstraintPossiblyPacked : public TypeConstraint
{
public:
TypeConstraintPossiblyPacked() : TypeConstraint("possiblyPacked") {}
void newType(JSContext *cx, TypeSet *source, jstype type)
{
if (type != TYPE_UNKNOWN && TypeIsObject(type)) {
TypeObject *object = (TypeObject *) type;
object->possiblePackedArray = true;
}
}
};
/////////////////////////////////////////////////////////////////////
// Freeze constraints
/////////////////////////////////////////////////////////////////////
/*
* Constraint which triggers recompilation of a script if a possible new JSValueType
* tag is realized for a type set.
*/
class TypeConstraintFreezeTypeTag : public TypeConstraint
{
public:
JSScript *script;
/*
* Whether the type tag has been marked unknown due to a type change which
* occurred after this constraint was generated (and which triggered recompilation).
*/
bool typeUnknown;
TypeConstraintFreezeTypeTag(JSScript *script)
: TypeConstraint("freezeTypeTag"),
script(script), typeUnknown(false)
{}
void newType(JSContext *cx, TypeSet *source, jstype type)
{
if (typeUnknown)
return;
if (type != TYPE_UNKNOWN && TypeIsObject(type)) {
/* Ignore new objects when the type set already has other objects. */
if (source->objectCount >= 2) {
JS_ASSERT(source->typeFlags == TYPE_FLAG_OBJECT);
return;
}
}
typeUnknown = true;
cx->compartment->types.addPendingRecompile(cx, script);
}
};
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_OBJECT:
return JSVAL_TYPE_OBJECT;
default:
return JSVAL_TYPE_UNKNOWN;
}
}
JSValueType
TypeSet::getKnownTypeTag(JSContext *cx, JSScript *script)
{
JSValueType type = GetValueTypeFromTypeFlags(typeFlags);
if (script && type != JSVAL_TYPE_UNKNOWN) {
JS_ASSERT(this->pool == &script->analysis->pool);
add(cx, ArenaNew<TypeConstraintFreezeTypeTag>(script->analysis->pool, script), false);
}
return type;
}
/* Compute the meet of kind with the kind of object, per the ObjectKind lattice. */
static inline ObjectKind
CombineObjectKind(TypeObject *object, ObjectKind kind)
{
/*
* Our initial guess is that all arrays are packed, but if the array is
* created through [], Array() or Array(N) and we don't see later code
* which looks to be filling it in starting at zero, consider it not packed.
* All requests for the kind of an object go through here, so there are
* no FreezeObjectKind constraints to update if we unset isPackedArray here.
*/
if (object->isPackedArray && !object->possiblePackedArray) {
InferSpew(ISpewDynamic, "Possible unpacked array: %s", TypeIdString(object->name));
object->isPackedArray = false;
}
ObjectKind nkind;
if (object->isFunction)
nkind = object->asFunction()->script ? OBJECT_SCRIPTED_FUNCTION : OBJECT_NATIVE_FUNCTION;
else if (object->isPackedArray)
nkind = OBJECT_PACKED_ARRAY;
else if (object->isDenseArray)
nkind = OBJECT_DENSE_ARRAY;
else
nkind = OBJECT_UNKNOWN;
if (kind == OBJECT_UNKNOWN || nkind == OBJECT_UNKNOWN)
return OBJECT_UNKNOWN;
if (kind == nkind || kind == OBJECT_NONE)
return nkind;
if ((kind == OBJECT_PACKED_ARRAY && nkind == OBJECT_DENSE_ARRAY) ||
(kind == OBJECT_DENSE_ARRAY && nkind == OBJECT_PACKED_ARRAY)) {
return OBJECT_DENSE_ARRAY;
}
return OBJECT_UNKNOWN;
}
/* Constraint which triggers recompilation if an array becomes not-packed or not-dense. */
class TypeConstraintFreezeArray : public TypeConstraint
{
public:
/*
* Array kind being specialized on by the FreezeObjectConstraint. This may have
* become OBJECT_UNKNOWN due to subsequent type changes and recompilation.
*/
ObjectKind *pkind;
JSScript *script;
TypeConstraintFreezeArray(ObjectKind *pkind, JSScript *script)
: TypeConstraint("freezeArray"),
pkind(pkind), script(script)
{
JS_ASSERT(*pkind == OBJECT_PACKED_ARRAY || *pkind == OBJECT_DENSE_ARRAY);
}
void newType(JSContext *cx, TypeSet *source, jstype type) {}
void arrayNotPacked(JSContext *cx, bool notDense)
{
if (*pkind == OBJECT_UNKNOWN) {
/* Despecialized the kind we were interested in due to recompilation. */
return;
}
JS_ASSERT(*pkind == OBJECT_PACKED_ARRAY || *pkind == OBJECT_DENSE_ARRAY);
if (!notDense && *pkind == OBJECT_DENSE_ARRAY) {
/* Marking an array as not packed, but we were already accounting for this. */
return;
}
cx->compartment->types.addPendingRecompile(cx, script);
}
};
/*
* Constraint which triggers recompilation if objects of a different kind are
* added to a type set.
*/
class TypeConstraintFreezeObjectKind : public TypeConstraint
{
public:
ObjectKind kind;
JSScript *script;
TypeConstraintFreezeObjectKind(ObjectKind kind, JSScript *script)
: TypeConstraint("freezeObjectKind"),
kind(kind), script(script)
{}
void newType(JSContext *cx, TypeSet *source, jstype type)
{
if (kind == OBJECT_UNKNOWN) {
/* Despecialized the kind we were interested in due to recompilation. */
return;
}
if (type == TYPE_UNKNOWN) {
kind = OBJECT_UNKNOWN;
} else if (TypeIsObject(type)) {
TypeObject *object = (TypeObject *) type;
ObjectKind nkind = CombineObjectKind(object, kind);
if (nkind != OBJECT_UNKNOWN &&
(kind == OBJECT_PACKED_ARRAY || kind == OBJECT_DENSE_ARRAY)) {
/*
* Arrays can become not-packed or not-dense dynamically.
* Add a constraint on the element type of the object to pick
* up such changes.
*/
TypeSet *elementTypes = object->getProperty(cx, JSID_VOID, false);
elementTypes->add(cx,
ArenaNew<TypeConstraintFreezeArray>(*object->pool, &kind, script), false);
}
if (nkind == kind) {
/* New object with the same kind we are interested in. */
return;
}
kind = nkind;
cx->compartment->types.addPendingRecompile(cx, script);
}
}
};
ObjectKind
TypeSet::getKnownObjectKind(JSContext *cx, JSScript *script)
{
JS_ASSERT(this->pool == &script->analysis->pool);
ObjectKind kind = OBJECT_NONE;
if (objectCount >= 2) {
unsigned objectCapacity = HashSetCapacity(objectCount);
for (unsigned i = 0; i < objectCapacity; i++) {
TypeObject *object = objectSet[i];
if (object)
kind = CombineObjectKind(object, kind);
}
} else if (objectCount == 1) {
kind = CombineObjectKind((TypeObject *) objectSet, kind);
}
if (kind != OBJECT_UNKNOWN) {
/*
* Watch for new objects of different kind, and re-traverse existing types
* in this set to add any needed FreezeArray constraints.
*/
add(cx, ArenaNew<TypeConstraintFreezeObjectKind>(script->analysis->pool,
kind, script), true);
}
return kind;
}
/*
* Constraint which triggers recompilation of a script if a getter or setter is added
* to a type set.
*/
class TypeConstraintFreezeGetSet : public TypeConstraint
{
public:
JSScript *script;
bool hasGetSet;
TypeConstraintFreezeGetSet(JSScript *script)
: TypeConstraint("freezeGetSet"),
script(script), hasGetSet(false)
{}
void newType(JSContext *cx, TypeSet *source, jstype type)
{
if (hasGetSet)
return;
if (type != TYPE_UNKNOWN) {
if (!TypeIsObject(type))
return;
if (cx->getFixedTypeObject(TYPE_OBJECT_GETSET) != (TypeObject *) type)
return;
}
hasGetSet = true;
cx->compartment->types.addPendingRecompile(cx, script);
}
};
bool
TypeSet::hasGetterSetter(JSContext *cx, JSScript *script)
{
TypeObject *getset = cx->getFixedTypeObject(TYPE_OBJECT_GETSET);
if (objectCount >= 2) {
unsigned objectCapacity = HashSetCapacity(objectCount);
for (unsigned i = 0; i < objectCapacity; i++) {
if (getset == objectSet[i])
return true;
}
} else if (objectCount == 1) {
if (getset == (TypeObject *) objectSet)
return true;
}
add(cx, ArenaNew<TypeConstraintFreezeGetSet>(script->analysis->pool, script), true);
return false;
}
/////////////////////////////////////////////////////////////////////
// TypeCompartment
/////////////////////////////////////////////////////////////////////
/*
* These names need to be consistent with those of the function, prototype and new
* objects produced by js_InitClass where objects have propagation other than from
* Array.prototype/Object.prototype. Since names are unique identifiers for type
* objects within a compartment, this ensures that the propagation performed by
* js_InitClass affects the objects later accessed via getFixedTypeObject. This
* design is pretty goofy and fragile but keeps js_InitClass simple.
*/
const char * const fixedTypeObjectNames[] = {
"Object",
"Function",
"Array",
"Function:prototype",
"#EmptyFunction",
"Object:prototype",
"Array:prototype",
"Boolean:prototype:new",
"Number:prototype:new",
"String:prototype:new",
"RegExp:prototype:new",
"Iterator:prototype:new",
"Generator:prototype:new",
"ArrayBuffer:prototype:new",
"#XML",
"#Arguments",
"#NoSuchMethod",
"#NoSuchMethodArguments",
"#PropertyDescriptor",
"#KeyValuePair",
"#JSON",
"#Proxy",
"#RegExpMatchArray",
"#StringSplitArray",
"#UnknownArray",
"#CloneArray",
"#PropertyArray",
"#ReflectArray",
"#UnknownObject",
"#CloneObject",
"#ReflectObject",
"#XMLSettings",
"#GetSet",
"#RegExpStatics",
"#Call",
"#DeclEnv",
"#SharpArray",
"#With",
"#Block",
"#NullClosure",
"#PropertyIterator",
"#Script"
};
JS_STATIC_ASSERT(JS_ARRAY_LENGTH(fixedTypeObjectNames) == TYPE_OBJECT_FIXED_LIMIT);
void
TypeCompartment::init()
{
PodZero(this);
JS_InitArenaPool(&pool, "typeinfer", 512, 8, NULL);
objectNameTable = new ObjectNameTable();
#ifdef DEBUG
bool success =
#endif
objectNameTable->init();
JS_ASSERT(success);
}
TypeCompartment::~TypeCompartment()
{
/* fclose(out); */
JS_FinishArenaPool(&pool);
delete objectNameTable;
JS_ASSERT(!pendingRecompiles);
}
void
TypeCompartment::growPendingArray()
{
pendingCapacity = js::Max(unsigned(100), pendingCapacity * 2);
PendingWork *oldArray = pendingArray;
pendingArray = ArenaArray<PendingWork>(pool, pendingCapacity);
memcpy(pendingArray, oldArray, pendingCount * sizeof(PendingWork));
}
TypeObject *
TypeCompartment::makeFixedTypeObject(JSContext *cx, FixedTypeObjectName which)
{
const char *name = fixedTypeObjectNames[which];
switch (which) {
case TYPE_OBJECT_OBJECT:
case TYPE_OBJECT_FUNCTION:
case TYPE_OBJECT_ARRAY:
return cx->getTypeFunction(name);
case TYPE_OBJECT_FUNCTION_PROTOTYPE: {
TypeObject *proto = cx->getFixedTypeObject(TYPE_OBJECT_OBJECT_PROTOTYPE);
return cx->getTypeFunctionHandler(name, JS_TypeHandlerVoid, proto);
}
case TYPE_OBJECT_EMPTY_FUNCTION:
return cx->getTypeFunction(name, NULL);
case TYPE_OBJECT_OBJECT_PROTOTYPE:
return getTypeObject(cx, NULL, name, false, NULL);
case TYPE_OBJECT_ARRAY_PROTOTYPE:
return cx->getTypeObject(name, NULL);
case TYPE_OBJECT_NEW_BOOLEAN:
return cx->getTypeObject(name, cx->getTypeObject("Boolean:prototype", NULL));
case TYPE_OBJECT_NEW_NUMBER:
return cx->getTypeObject(name, cx->getTypeObject("Number:prototype", NULL));
case TYPE_OBJECT_NEW_STRING:
return cx->getTypeObject(name, cx->getTypeObject("String:prototype", NULL));
case TYPE_OBJECT_NEW_REGEXP:
return cx->getTypeObject(name, cx->getTypeObject("RegExp:prototype", NULL));
case TYPE_OBJECT_NEW_ITERATOR:
return cx->getTypeObject(name, cx->getTypeObject("Iterator:prototype", NULL));
case TYPE_OBJECT_NEW_GENERATOR:
return cx->getTypeObject(name, cx->getTypeObject("Generator:prototype", NULL));
case TYPE_OBJECT_NEW_ARRAYBUFFER:
return cx->getTypeObject(name, cx->getTypeObject("ArrayBuffer:prototype", NULL));
case TYPE_OBJECT_XML:
case TYPE_OBJECT_ARGUMENTS:
case TYPE_OBJECT_NOSUCHMETHOD:
case TYPE_OBJECT_NOSUCHMETHOD_ARGUMENTS:
case TYPE_OBJECT_PROPERTY_DESCRIPTOR:
case TYPE_OBJECT_KEY_VALUE_PAIR:
case TYPE_OBJECT_JSON:
case TYPE_OBJECT_PROXY: {
TypeObject *object = getTypeObject(cx, NULL, name, false, NULL);
cx->markTypeObjectUnknownProperties(object);
return object;
}
case TYPE_OBJECT_REGEXP_MATCH_ARRAY:
case TYPE_OBJECT_STRING_SPLIT_ARRAY:
case TYPE_OBJECT_UNKNOWN_ARRAY:
case TYPE_OBJECT_CLONE_ARRAY:
case TYPE_OBJECT_PROPERTY_ARRAY:
case TYPE_OBJECT_REFLECT_ARRAY:
return cx->getTypeObject(name, cx->getFixedTypeObject(TYPE_OBJECT_ARRAY_PROTOTYPE));
case TYPE_OBJECT_UNKNOWN_OBJECT:
case TYPE_OBJECT_CLONE_OBJECT:
case TYPE_OBJECT_REFLECT_OBJECT:
case TYPE_OBJECT_XML_SETTINGS:
return cx->getTypeObject(name, NULL);
case TYPE_OBJECT_GETSET:
case TYPE_OBJECT_REGEXP_STATICS:
case TYPE_OBJECT_CALL:
case TYPE_OBJECT_DECLENV:
case TYPE_OBJECT_SHARP_ARRAY:
case TYPE_OBJECT_WITH:
case TYPE_OBJECT_BLOCK:
case TYPE_OBJECT_NULL_CLOSURE:
case TYPE_OBJECT_PROPERTY_ITERATOR:
case TYPE_OBJECT_SCRIPT:
return getTypeObject(cx, NULL, name, false, NULL);
default:
JS_NOT_REACHED("Unknown fixed object");
return NULL;
}
}
TypeObject *
TypeCompartment::getTypeObject(JSContext *cx, analyze::Script *script, const char *name,
bool isFunction, TypeObject *prototype)
{
#ifdef JS_TYPE_INFERENCE
jsid id = ATOM_TO_JSID(js_Atomize(cx, name, strlen(name), 0));
JSArenaPool &pool = script ? script->pool : this->pool;
/*
* Check for an existing object with the same name first. If we have one
* then reuse it.
*/
ObjectNameTable::AddPtr p = objectNameTable->lookupForAdd(id);
if (p) {
js::types::TypeObject *object = p->value;
JS_ASSERT(object->isFunction == isFunction);
JS_ASSERT(object->prototype == prototype);
JS_ASSERT(object->pool == &pool);
return object;
}
js::types::TypeObject *object;
if (isFunction)
object = ArenaNew<TypeFunction>(pool, cx, &pool, id, prototype);
else
object = ArenaNew<TypeObject>(pool, cx, &pool, id, prototype);
TypeObject *&objects = script ? script->objects : this->objects;
object->next = objects;
objects = object;
objectNameTable->add(p, id, object);
return object;
#else
return NULL;
#endif
}
void
TypeCompartment::addDynamicType(JSContext *cx, TypeSet *types, jstype type)
{
JS_ASSERT(!types->hasType(type));
interpreting = false;
uint64_t startTime = currentTime();
types->addType(cx, type);
uint64_t endTime = currentTime();
analysisTime += (endTime - startTime);
interpreting = true;
if (hasPendingRecompiles())
processPendingRecompiles(cx);
}
void
TypeCompartment::addDynamicPush(JSContext *cx, analyze::Bytecode &code,
unsigned index, jstype type)
{
js::types::TypeSet *types = code.pushed(index);
JS_ASSERT(!types->hasType(type));
InferSpew(ISpewDynamic, "MonitorResult: #%u:%05u %u: %s",
code.script->id, code.offset, index, TypeString(type));
interpreting = false;
uint64_t startTime = currentTime();
types->addType(cx, type);
/*
* For inc/dec ops, we need to go back and reanalyze the affected opcode
* taking the overflow into account. We won't see an explicit adjustment
* of the type of the thing being inc/dec'ed, nor will adding the type to
* the pushed value affect the type.
*/
JSOp op = JSOp(code.script->getScript()->code[code.offset]);
const JSCodeSpec *cs = &js_CodeSpec[op];
if (cs->format & (JOF_INC | JOF_DEC)) {
JS_ASSERT(!code.hasIncDecOverflow);
code.hasIncDecOverflow = true;
/* Inc/dec ops do not use the temporary analysis state. */
analyze::Script::AnalyzeState state;
code.script->analyzeTypes(cx, &code, state);
}
uint64_t endTime = currentTime();
analysisTime += (endTime - startTime);
interpreting = true;
if (hasPendingRecompiles())
processPendingRecompiles(cx);
}
void
TypeCompartment::processPendingRecompiles(JSContext *cx)
{
/*
* :FIXME: The case where recompilation fails needs to be handled (along with OOM
* checks pretty much everywhere else in this file). This function should return
* false, and the caller must return and throw a JS exception *before* performing
* the side effect which triggered the recompilation.
*/
JS_ASSERT(pendingRecompiles);
for (unsigned i = 0; i < pendingRecompiles->length(); i++) {
#ifdef JS_METHODJIT
JSScript *script = (*pendingRecompiles)[i];
mjit::Recompiler recompiler(cx, script);
if (!recompiler.recompile())
JS_NOT_REACHED("Recompilation failed!");
#endif
}
delete pendingRecompiles;
pendingRecompiles = NULL;
}
void
TypeCompartment::addPendingRecompile(JSContext *cx, JSScript *script)
{
if (!script->jitNormal && !script->jitCtor) {
/* Scripts which haven't been compiled yet don't need to be recompiled. */
return;
}
if (!pendingRecompiles)
pendingRecompiles = new Vector<JSScript*>(ContextAllocPolicy(cx));
for (unsigned i = 0; i < pendingRecompiles->length(); i++) {
if (script == (*pendingRecompiles)[i])
return;
}
recompilations++;
pendingRecompiles->append(script);
}
void
TypeCompartment::dynamicAssign(JSContext *cx, JSObject *obj, jsid id, const Value &rval)
{
jstype rvtype = GetValueType(cx, rval);
TypeObject *object = obj->getTypeObject();
if (object->unknownProperties)
return;
TypeSet *assignTypes;
/*
* :XXX: Does this need to be moved to a more general place? We aren't considering
* call objects in, e.g. addTypeProperty, but call objects might not be able to
* flow there as they do not escape to scripts.
*/
if (obj->isCall() || obj->isBlock()) {
/* Local variable, let variable or argument assignment. */
while (!obj->isCall())
obj = obj->getParent();
analyze::Script *script = obj->getCallObjCalleeFunction()->script()->analysis;
JS_ASSERT(!script->isEval());
assignTypes = script->getVariable(cx, id);
} else {
id = MakeTypeId(id);
if (!JSID_IS_VOID(id) && id != id_prototype(cx) && id != id___proto__(cx)) {
/*
* Monitor any object which has had dynamic assignments to string properties,
* to avoid making large numbers of type properties for hashmap-style objects.
* :FIXME: this is too aggressive for things like prototype library initialization.
*/
cx->markTypeObjectUnknownProperties(object);
if (hasPendingRecompiles())
processPendingRecompiles(cx);
return;
}
assignTypes = object->getProperty(cx, id, true);
/*
* Writing the __proto__ property marks the object's type as unknown.
* Can't analyze objects with a mutable prototype.
*/
if (id == id___proto__(cx))
cx->markTypeObjectUnknownProperties(object);
}
if (assignTypes->hasType(rvtype))
return;
InferSpew(ISpewDynamic, "MonitorAssign: %s %s: %s",
TypeIdString(object->name), TypeIdString(id), TypeString(rvtype));
addDynamicType(cx, assignTypes, rvtype);
}
void
TypeCompartment::monitorBytecode(JSContext *cx, analyze::Bytecode *code)
{
if (code->monitorNeeded)
return;
/*
* Make sure monitoring is limited to property sets and calls where the
* target of the set/call could be statically unknown, and mark the bytecode
* results as unknown.
*/
JSOp op = JSOp(code->script->getScript()->code[code->offset]);
switch (op) {
case JSOP_SETNAME:
case JSOP_SETGNAME:
case JSOP_SETELEM:
case JSOP_SETPROP:
case JSOP_SETMETHOD:
case JSOP_INITPROP:
case JSOP_INITMETHOD:
case JSOP_FORPROP:
case JSOP_FORNAME:
case JSOP_ENUMELEM:
case JSOP_DEFFUN:
case JSOP_DEFFUN_FC:
break;
case JSOP_INCNAME:
case JSOP_DECNAME:
case JSOP_NAMEINC:
case JSOP_NAMEDEC:
case JSOP_INCGNAME:
case JSOP_DECGNAME:
case JSOP_GNAMEINC:
case JSOP_GNAMEDEC:
case JSOP_INCELEM:
case JSOP_DECELEM:
case JSOP_ELEMINC:
case JSOP_ELEMDEC:
case JSOP_INCPROP:
case JSOP_DECPROP:
case JSOP_PROPINC:
case JSOP_PROPDEC:
case JSOP_CALL:
case JSOP_SETCALL:
case JSOP_EVAL:
case JSOP_FUNCALL:
case JSOP_FUNAPPLY:
case JSOP_NEW:
code->setFixed(cx, 0, TYPE_UNKNOWN);
break;
default:
TypeFailure(cx, "Monitoring unknown bytecode: %s", js_CodeNameTwo[op]);
}
InferSpew(ISpewOps, "addMonitorNeeded: #%u:%05u", code->script->id, code->offset);
code->monitorNeeded = true;
JSScript *script = code->script->getScript();
if (script->hasJITCode())
cx->compartment->types.addPendingRecompile(cx, script);
}
void
TypeCompartment::finish(JSContext *cx, JSCompartment *compartment)
{
JS_ASSERT(this == &compartment->types);
if (!InferSpewActive(ISpewResult) || JS_CLIST_IS_EMPTY(&compartment->scripts))
return;
for (JSScript *script = (JSScript *)compartment->scripts.next;
&script->links != &compartment->scripts;
script = (JSScript *)script->links.next) {
if (script->analysis)
script->analysis->finish(cx);
}
#ifdef DEBUG
TypeObject *object = objects;
while (object) {
object->print(cx);
object = object->next;
}
#endif
double millis = analysisTime / 1000.0;
printf("Counts: ");
for (unsigned count = 0; count < TYPE_COUNT_LIMIT; count++) {
if (count)
printf("/");
printf("%u", typeCounts[count]);
}
printf(" (%u over)\n", typeCountOver);
printf("Recompilations: %u\n", recompilations);
printf("Time: %.2f ms\n", millis);
}
/////////////////////////////////////////////////////////////////////
// TypeStack
/////////////////////////////////////////////////////////////////////
void
TypeStack::merge(JSContext *cx, TypeStack *one, TypeStack *two)
{
JS_ASSERT((one == NULL) == (two == NULL));
if (!one)
return;
one = one->group();
two = two->group();
/* Check if the classes are already the same. */
if (one == two)
return;
/* There should not be any types or constraints added to the stack types. */
JS_ASSERT(one->types.typeFlags == 0 && one->types.constraintList == NULL);
JS_ASSERT(two->types.typeFlags == 0 && two->types.constraintList == NULL);
/* Merge any inner portions of the stack for the two nodes. */
if (one->innerStack)
merge(cx, one->innerStack, two->innerStack);
InferSpew(ISpewOps, "merge: T%u T%u", one->types.id(), two->types.id());
/* one has now been merged into two, do the actual join. */
PodZero(one);
one->mergedGroup = two;
}
/////////////////////////////////////////////////////////////////////
// TypeObject
/////////////////////////////////////////////////////////////////////
TypeObject::TypeObject(JSContext *cx, JSArenaPool *pool, jsid name, TypeObject *prototype)
: name(name), isFunction(false), propertySet(NULL), propertyCount(0),
prototype(prototype), instanceList(NULL), instanceNext(NULL), newObject(NULL),
pool(pool), next(NULL), unknownProperties(false),
isDenseArray(false), isPackedArray(false), possiblePackedArray(false)
{
InferSpew(ISpewOps, "newObject: %s", TypeIdString(name));
if (prototype) {
if (prototype == cx->compartment->types.fixedTypeObjects[TYPE_OBJECT_ARRAY_PROTOTYPE])
isDenseArray = isPackedArray = true;
if (prototype->unknownProperties)
unknownProperties = true;
instanceNext = prototype->instanceList;
prototype->instanceList = this;
}
}
void
TypeObject::storeToInstances(JSContext *cx, Property *base)
{
TypeObject *object = instanceList;
while (object) {
Property *p =
HashSetLookup<jsid,Property,Property>(object->propertySet, object->propertyCount, base->id);
if (p)
base->ownTypes.addSubset(cx, *pool, &p->types);
if (object->instanceList)
object->storeToInstances(cx, base);
object = object->instanceNext;
}
}
void
TypeObject::addProperty(JSContext *cx, jsid id, Property *&base)
{
JS_ASSERT(!base);
base = ArenaNew<Property>(*pool, pool, id);
InferSpew(ISpewOps, "addProperty: %s %s T%u own T%u",
TypeIdString(name), TypeIdString(id), base->types.id(), base->ownTypes.id());
base->ownTypes.addSubset(cx, *pool, &base->types);
if (unknownProperties) {
/*
* Immediately mark the variable as unknown. Ideally we won't be doing this
* too often, but we don't assert !unknownProperties to avoid extra complexity
* in other code accessing object properties.
*/
base->ownTypes.addType(cx, TYPE_UNKNOWN);
}
/* Check all transitive instances for this property. */
if (instanceList)
storeToInstances(cx, base);
/* Pull in this property from all prototypes up the chain. */
TypeObject *object = prototype;
while (object) {
Property *p =
HashSetLookup<jsid,Property,Property>(object->propertySet, object->propertyCount, id);
if (p)
p->ownTypes.addSubset(cx, *object->pool, &base->types);
object = object->prototype;
}
/*
* If this is the 'prototype' property on a function with a lazily generated
* prototype (not builtin), make the object now.
*/
if (!isFunction || asFunction()->isBuiltin || id != id_prototype(cx))
return;
TypeFunction *function = asFunction();
JS_ASSERT(!function->prototypeObject);
const char *baseName = js_GetStringBytes(JSID_TO_ATOM(name));
unsigned len = strlen(baseName) + 15;
char *prototypeName = (char *)alloca(len);
JS_snprintf(prototypeName, len, "%s:prototype", baseName);
function->prototypeObject = cx->getTypeObject(prototypeName, NULL);
base->ownTypes.addType(cx, (jstype) function->prototypeObject);
}
void
TypeObject::markUnknown(JSContext *cx)
{
JS_ASSERT(!unknownProperties);
unknownProperties = true;
cx->markTypeArrayNotPacked(this, true, false);
/*
* 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.
*/
if (propertyCount >= 2) {
unsigned capacity = HashSetCapacity(propertyCount);
for (unsigned i = 0; i < capacity; i++) {
Property *prop = propertySet[i];
if (prop)
prop->ownTypes.addType(cx, TYPE_UNKNOWN);
}
} else if (propertyCount == 1) {
Property *prop = (Property *) propertySet;
prop->ownTypes.addType(cx, TYPE_UNKNOWN);
}
/* Mark existing instances as unknown. */
TypeObject *instance = instanceList;
while (instance) {
if (!instance->unknownProperties)
instance->markUnknown(cx);
instance = instance->instanceNext;
}
}
TypeObject *
TypeObject::getNewObject(JSContext *cx)
{
if (newObject)
return newObject;
const char *baseName = js_GetStringBytes(JSID_TO_ATOM(name));
unsigned len = strlen(baseName) + 10;
char *newName = (char *)alloca(len);
JS_snprintf(newName, len, "%s:new", baseName);
newObject = cx->getTypeObject(newName, this);
return newObject;
}
void
TypeObject::print(JSContext *cx)
{
printf("%s : %s", TypeIdString(name), prototype ? TypeIdString(prototype->name) : "(null)");
if (propertyCount == 0) {
printf(" {}\n");
return;
}
printf(" {");
if (propertyCount >= 2) {
unsigned capacity = HashSetCapacity(propertyCount);
for (unsigned i = 0; i < capacity; i++) {
Property *prop = propertySet[i];
if (prop) {
printf("\n %s:", TypeIdString(prop->id));
prop->ownTypes.print(cx);
}
}
} else if (propertyCount == 1) {
Property *prop = (Property *) propertySet;
printf("\n %s:", TypeIdString(prop->id));
prop->ownTypes.print(cx);
}
printf("\n}\n");
}
/////////////////////////////////////////////////////////////////////
// TypeFunction
/////////////////////////////////////////////////////////////////////
TypeFunction::TypeFunction(JSContext *cx, JSArenaPool *pool, jsid name, TypeObject *prototype)
: TypeObject(cx, pool, name, prototype), handler(NULL), script(NULL),
prototypeObject(NULL), returnTypes(pool),
isBuiltin(false), isGeneric(false)
{
isFunction = true;
InferSpew(ISpewOps, "newFunction: %s return T%u", TypeIdString(name), returnTypes.id());
}
/////////////////////////////////////////////////////////////////////
// TypeScript
/////////////////////////////////////////////////////////////////////
} } /* namespace js::types */
/*
* Returns true if we don't expect to compute the correct types for some value
* popped by the specified bytecode.
*/
static inline bool
IgnorePopped(JSOp op, unsigned index)
{
switch (op) {
case JSOP_LEAVEBLOCK:
case JSOP_LEAVEBLOCKEXPR:
/*
* We don't model 'let' variables as stack operands, the values popped
* when the 'let' finishes will be missing.
*/
return true;
case JSOP_FORNAME:
case JSOP_FORLOCAL:
case JSOP_FORGLOBAL:
case JSOP_FORARG:
case JSOP_FORPROP:
case JSOP_FORELEM:
case JSOP_MOREITER:
case JSOP_ENDITER:
/* We don't keep track of the iteration state for 'for in' or 'for each in' loops. */
return true;
case JSOP_ENUMELEM:
return (index == 1 || index == 2);
/* We keep track of the scopes pushed by BINDNAME separately. */
case JSOP_SETNAME:
case JSOP_SETGNAME:
return (index == 1);
case JSOP_GETXPROP:
case JSOP_DUP:
return true;
case JSOP_SETXMLNAME:
return (index == 1 || index == 2);
case JSOP_ENDFILTER:
/* We don't keep track of XML filter state. */
return (index == 0);
case JSOP_LEAVEWITH:
/* We don't keep track of objects bound by a 'with'. */
return true;
case JSOP_RETSUB:
case JSOP_POPN:
/* We don't keep track of state indicating whether there is a pending exception. */
return true;
default:
return false;
}
}
void
JSScript::typeCheckBytecode(JSContext *cx, const jsbytecode *pc, const js::Value *sp)
{
if (analysis->failed())
return;
js::analyze::Bytecode &code = analysis->getCode(pc);
JS_ASSERT(code.analyzed);
int useCount = js::analyze::GetUseCount(this, code.offset);
JSOp op = (JSOp) *pc;
if (!useCount)
return;
js::types::TypeStack *stack = code.inStack->group();
for (int i = 0; i < useCount; i++) {
const js::Value &val = sp[-1 - i];
js::types::TypeSet *types = &stack->types;
bool ignore = val.isMagic(JS_ARRAY_HOLE) || stack->ignoreTypeTag || IgnorePopped(op, i);
stack = stack->innerStack ? stack->innerStack->group() : NULL;
if (ignore)
continue;
js::types::jstype type = js::types::GetValueType(cx, val);
if (!types->hasType(type)) {
js::types::TypeFailure(cx, "Missing type at #%u:%05u popped %u: %s",
analysis->id, code.offset, i, js::types::TypeString(type));
return;
}
if (js::types::TypeIsObject(type)) {
JS_ASSERT(val.isObject());
JSObject *obj = &val.toObject();
js::types::TypeObject *object = (js::types::TypeObject *) type;
if (object->unknownProperties) {
JS_ASSERT(!object->isDenseArray);
continue;
}
/*
* Check that the immediate prototype is correct. We don't check when
* making the object as the prototypes of some objects change after creation.
*/
if (((object->prototype != NULL) != (obj->getProto() != NULL)) ||
(object->prototype && object->prototype != obj->getProto()->getTypeObject())) {
jsid protoName = object->prototype ? object->prototype->name : JSID_VOID;
jsid needName = obj->getProto() ? obj->getProto()->getTypeObject()->name : JSID_VOID;
js::types::TypeFailure(cx, "Wrong prototype %s for %s at #%u:%05u popped %u: need %s",
js::types::TypeIdString(protoName),
js::types::TypeIdString(object->name),
analysis->id, code.offset, i,
js::types::TypeIdString(needName));
}
/* Make sure information about the array status of this object is right. */
JS_ASSERT_IF(object->isPackedArray, object->isDenseArray);
if (object->isDenseArray) {
if (!obj->isDenseArray() ||
(object->isPackedArray && !obj->isPackedDenseArray())) {
js::types::TypeFailure(cx, "Object not %s array at #%u:%05u popped %u: %s",
object->isPackedArray ? "packed" : "dense",
analysis->id, code.offset, i,
js::types::TypeIdString(object->name));
}
}
}
}
}
namespace js {
namespace analyze {
using namespace types;
void
Script::addVariable(JSContext *cx, jsid id, types::Variable *&var)
{
JS_ASSERT(!var);
var = ArenaNew<types::Variable>(pool, &pool, id);
InferSpew(ISpewOps, "addVariable: #%lu %s T%u",
this->id, TypeIdString(id), var->types.id());
}
inline Bytecode*
Script::parentCode()
{
return parent ? &parent->analysis->getCode(parentpc) : NULL;
}
inline Script*
Script::evalParent()
{
Script *script = this;
while (script->parent && !script->fun)
script = script->parent->analysis;
return script;
}
void
Script::setFunction(JSContext *cx, JSFunction *fun)
{
JS_ASSERT(!this->fun);
this->fun = fun;
/* Add the return type for the empty script, which we do not explicitly analyze. */
if (script->isEmpty())
function()->returnTypes.addType(cx, TYPE_UNDEFINED);
/*
* Construct the arguments and locals of this function, and mark them as
* definitely declared for scope lookups. Note that we don't do this for the
* global script (don't need to, everything not in another scope is global),
* nor for eval scripts --- if an eval declares a variable the declaration
* will be merged with any declaration in the context the eval occurred in,
* and definitions information will be cleared for any scripts that could use
* the declared variable.
*/
if (fun->hasLocalNames())
localNames = fun->getLocalNameArray(cx, &pool);
/* Make a local variable for the function. */
if (fun->atom)
getVariable(cx, ATOM_TO_JSID(fun->atom))->addType(cx, (jstype) fun->getTypeObject());
}
static inline ptrdiff_t
GetJumpOffset(jsbytecode *pc, jsbytecode *pc2)
{
uint32 type = JOF_OPTYPE(*pc);
if (JOF_TYPE_IS_EXTENDED_JUMP(type))
return GET_JUMPX_OFFSET(pc2);
return GET_JUMP_OFFSET(pc2);
}
/* Return whether op bytecodes do not fallthrough (they may do a jump). */
static inline bool
BytecodeNoFallThrough(JSOp op)
{
switch (op) {
case JSOP_GOTO:
case JSOP_GOTOX:
case JSOP_DEFAULT:
case JSOP_DEFAULTX:
case JSOP_RETURN:
case JSOP_STOP:
case JSOP_RETRVAL:
case JSOP_THROW:
case JSOP_TABLESWITCH:
case JSOP_TABLESWITCHX:
case JSOP_LOOKUPSWITCH:
case JSOP_LOOKUPSWITCHX:
case JSOP_FILTER:
return true;
case JSOP_GOSUB:
case JSOP_GOSUBX:
/* These fall through indirectly, after executing a 'finally'. */
return false;
default:
return false;
}
}
/*
* Get the variable set which declares id, either the local variables of a script
* or the properties of the global object. NULL if the scope is ambiguous due
* to a 'with', SCOPE_GLOBAL if the scope is definitely global.
*/
static Script * const SCOPE_GLOBAL = (Script *) 0x1;
static Script *
SearchScope(JSContext *cx, Script *script, TypeStack *stack, jsid id)
{
/* Search up until we find a local variable with the specified name. */
while (true) {
/* Search the stack for any 'with' objects or 'let' variables. */
while (stack) {
stack = stack->group();
if (stack->boundWith) {
/* Enclosed within a 'with', ambiguous scope. */
return NULL;
}
if (stack->letVariable == id) {
/* The variable is definitely bound by this scope. */
return script->evalParent();
}
stack = stack->innerStack;
}
if (script->isEval()) {
/* eval scripts have no local variables to consider (they may have 'let' vars). */
JS_ASSERT(!script->variableCount);
stack = script->parentCode()->inStack;
script = script->parent->analysis;
continue;
}
if (!script->parent)
break;
/* Function scripts have 'arguments' local variables. */
if (id == id_arguments(cx) && script->fun) {
TypeSet *types = script->getVariable(cx, id);
types->addType(cx, (jstype) cx->getFixedTypeObject(TYPE_OBJECT_ARGUMENTS));
return script;
}
/* Function scripts with names have local variables of that name. */
if (script->fun && id == ATOM_TO_JSID(script->fun->atom)) {
TypeSet *types = script->getVariable(cx, id);
types->addType(cx, (jstype) script->function());
return script;
}
unsigned nargs = script->argCount();
for (unsigned i = 0; i < nargs; i++) {
if (id == script->getArgumentId(i))
return script;
}
unsigned nfixed = script->getScript()->nfixed;
for (unsigned i = 0; i < nfixed; i++) {
if (id == script->getLocalId(i, NULL))
return script;
}
stack = script->parentCode()->inStack;
script = script->parent->analysis;
}
return SCOPE_GLOBAL;
}
/* Mark the specified variable as undefined in any scope it could refer to. */
void
TrashScope(JSContext *cx, Script *script, jsid id)
{
while (true) {
if (!script->isEval()) {
TypeSet *types = script->getVariable(cx, id);
types->addType(cx, TYPE_UNKNOWN);
}
if (!script->parent)
break;
script = script->parent->analysis;
}
TypeSet *types = cx->getGlobalTypeObject()->getProperty(cx, id, true);
types->addType(cx, TYPE_UNKNOWN);
}
static inline jsid
GetAtomId(JSContext *cx, Script *script, const jsbytecode *pc, unsigned offset)
{
unsigned index = js_GetIndexFromBytecode(cx, script->getScript(), (jsbytecode*) pc, offset);
return MakeTypeId(ATOM_TO_JSID(script->getScript()->getAtom(index)));
}
static inline jsid
GetGlobalId(JSContext *cx, Script *script, const jsbytecode *pc)
{
unsigned index = GET_SLOTNO(pc);
return MakeTypeId(ATOM_TO_JSID(script->getScript()->getGlobalAtom(index)));
}
static inline JSObject *
GetScriptObject(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset)
{
unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, offset);
return script->getObject(index);
}
static inline const Value &
GetScriptConst(JSContext *cx, JSScript *script, const jsbytecode *pc)
{
unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, 0);
return script->getConst(index);
}
/* Get the script and id of a variable referred to by an UPVAR opcode. */
static inline Script *
GetUpvarVariable(JSContext *cx, Bytecode *code, unsigned index, jsid *id)
{
JSUpvarArray *uva = code->script->getScript()->upvars();
JS_ASSERT(index < uva->length);
js::UpvarCookie cookie = uva->vector[index];
uint16 level = code->script->getScript()->staticLevel - cookie.level();
uint16 slot = cookie.slot();
/* Find the script with the static level we're searching for. */
Bytecode *newCode = code;
while (newCode->script->getScript()->staticLevel != level)
newCode = newCode->script->parentCode();
Script *newScript = newCode->script;
/*
* Get the name of the variable being referenced. It is either an argument,
* a local or the function itself.
*/
if (!newScript->fun)
*id = newScript->getLocalId(newScript->getScript()->nfixed + slot, newCode);
else if (slot < newScript->argCount())
*id = newScript->getArgumentId(slot);
else if (slot == UpvarCookie::CALLEE_SLOT)
*id = ATOM_TO_JSID(newScript->fun->atom);
else
*id = newScript->getLocalId(slot - newScript->argCount(), newCode);
JS_ASSERT(!JSID_IS_VOID(*id));
return newScript->evalParent();
}
/* Constraint which preserves primitives and converts objects to strings. */
class TypeConstraintToString : public TypeConstraint
{
public:
TypeSet *target;
TypeConstraintToString(TypeSet *_target)
: TypeConstraint("tostring"), target(_target)
{}
void newType(JSContext *cx, TypeSet *source, jstype type)
{
if (TypeIsObject(type))
target->addType(cx, TYPE_STRING);
else
target->addType(cx, type);
}
};
/*
* If the bytecode immediately following code/pc is a test of the value
* pushed by code, mark that value as possibly void.
*/
static inline void
CheckNextTest(JSContext *cx, Bytecode *code, jsbytecode *pc)
{
jsbytecode *next = pc + GetBytecodeLength(pc);
switch ((JSOp)*next) {
case JSOP_IFEQ:
case JSOP_IFNE:
case JSOP_NOT:
case JSOP_TYPEOF:
case JSOP_TYPEOFEXPR:
code->pushed(0)->addType(cx, TYPE_UNDEFINED);
break;
default:
break;
}
}
/* Propagate the specified types into the Nth value pushed by an instruction. */
static inline void
MergePushed(JSContext *cx, JSArenaPool &pool, Bytecode *code, unsigned num, TypeSet *types)
{
types->addSubset(cx, pool, code->pushed(num));
}
void
Script::analyzeTypes(JSContext *cx, Bytecode *code, AnalyzeState &state)
{
unsigned offset = code->offset;
JS_ASSERT(code->analyzed);
jsbytecode *pc = script->code + offset;
JSOp op = (JSOp)*pc;
InferSpew(ISpewOps, "analyze: #%u:%05u", id, offset);
if (code->stackDepth > state.stackDepth && state.stack) {
#ifdef DEBUG
/*
* Check that we aren't destroying any useful information. This should only
* occur around exception handling bytecode.
*/
for (unsigned i = state.stackDepth; i < code->stackDepth; i++) {
JS_ASSERT(!state.stack[i].isForEach);
JS_ASSERT(!state.stack[i].hasDouble);
JS_ASSERT(!state.stack[i].scope);
}
#endif
unsigned ndefs = code->stackDepth - state.stackDepth;
memset(&state.stack[state.stackDepth], 0, ndefs * sizeof(AnalyzeStateStack));
}
state.stackDepth = code->stackDepth;
/* Add type constraints for the various opcodes. */
switch (op) {
/* Nop bytecodes. */
case JSOP_POP:
case JSOP_NOP:
case JSOP_TRACE:
case JSOP_GOTO:
case JSOP_GOTOX:
case JSOP_IFEQ:
case JSOP_IFEQX:
case JSOP_IFNE:
case JSOP_IFNEX:
case JSOP_LINENO:
case JSOP_LOOKUPSWITCH:
case JSOP_LOOKUPSWITCHX:
case JSOP_TABLESWITCH:
case JSOP_TABLESWITCHX:
case JSOP_DEFCONST:
case JSOP_LEAVEWITH:
case JSOP_LEAVEBLOCK:
case JSOP_RETRVAL:
case JSOP_ENDITER:
case JSOP_TRY:
case JSOP_THROWING:
case JSOP_GOSUB:
case JSOP_GOSUBX:
case JSOP_RETSUB:
case JSOP_CONDSWITCH:
case JSOP_DEFAULT:
case JSOP_POPN:
case JSOP_UNBRANDTHIS:
case JSOP_STARTXML:
case JSOP_STARTXMLEXPR:
case JSOP_DEFXMLNS:
case JSOP_SHARPINIT:
case JSOP_INDEXBASE:
case JSOP_INDEXBASE1:
case JSOP_INDEXBASE2:
case JSOP_INDEXBASE3:
case JSOP_RESETBASE:
case JSOP_RESETBASE0:
case JSOP_BLOCKCHAIN:
case JSOP_NULLBLOCKCHAIN:
case JSOP_POPV:
case JSOP_FORELEM:
case JSOP_DEBUGGER:
break;
/* Bytecodes pushing values of known type. */
case JSOP_VOID:
case JSOP_PUSH:
code->setFixed(cx, 0, TYPE_UNDEFINED);
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:
/* :TODO: Add heuristics for guessing URSH which can overflow. */
code->setFixed(cx, 0, TYPE_INT32);
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:
case JSOP_DELDESC:
code->setFixed(cx, 0, TYPE_BOOLEAN);
break;
case JSOP_DOUBLE:
code->setFixed(cx, 0, TYPE_DOUBLE);
break;
case JSOP_STRING:
case JSOP_TYPEOF:
case JSOP_TYPEOFEXPR:
case JSOP_QNAMEPART:
case JSOP_XMLTAGEXPR:
case JSOP_TOATTRVAL:
case JSOP_ADDATTRNAME:
case JSOP_ADDATTRVAL:
case JSOP_XMLELTEXPR:
code->setFixed(cx, 0, TYPE_STRING);
break;
case JSOP_NULL:
code->setFixed(cx, 0, TYPE_NULL);
break;
case JSOP_REGEXP:
code->setFixed(cx, 0, (jstype) cx->getFixedTypeObject(TYPE_OBJECT_NEW_REGEXP));
break;
case JSOP_STOP:
/* If a stop is reachable then the return type may be void. */
if (fun)
function()->returnTypes.addType(cx, TYPE_UNDEFINED);
break;
case JSOP_OR:
case JSOP_ORX:
case JSOP_AND:
case JSOP_ANDX:
/* OR/AND push whichever operand determined the result. */
code->popped(0)->addSubset(cx, pool, code->pushed(0));
break;
case JSOP_DUP:
MergePushed(cx, pool, code, 0, code->popped(0));
MergePushed(cx, pool, code, 1, code->popped(0));
break;
case JSOP_DUP2:
MergePushed(cx, pool, code, 0, code->popped(1));
MergePushed(cx, pool, code, 1, code->popped(0));
MergePushed(cx, pool, code, 2, code->popped(1));
MergePushed(cx, pool, code, 3, code->popped(0));
break;
case JSOP_GETGLOBAL:
case JSOP_CALLGLOBAL:
case JSOP_GETGNAME:
case JSOP_CALLGNAME:
case JSOP_NAME:
case JSOP_CALLNAME: {
/* Get the type set being updated, if we can determine it. */
jsid id;
Script *scope;
switch (op) {
case JSOP_GETGLOBAL:
case JSOP_CALLGLOBAL:
id = GetGlobalId(cx, this, pc);
scope = SCOPE_GLOBAL;
break;
case JSOP_GETGNAME:
case JSOP_CALLGNAME:
id = GetAtomId(cx, this, pc, 0);
scope = SCOPE_GLOBAL;
break;
default:
id = GetAtomId(cx, this, pc, 0);
scope = SearchScope(cx, this, code->inStack, id);
break;
}
if (scope == SCOPE_GLOBAL) {
/*
* This might be a lazily loaded property of the global object.
* Resolve it now. Subtract this from the total analysis time.
*/
uint64_t startTime = cx->compartment->types.currentTime();
JSObject *obj;
JSProperty *prop;
js_LookupPropertyWithFlags(cx, cx->globalObject, id,
JSRESOLVE_QUALIFIED, &obj, &prop);
uint64_t endTime = cx->compartment->types.currentTime();
cx->compartment->types.analysisTime -= (endTime - startTime);
/* Handle as a property access. */
PropertyAccess(cx, code, cx->getGlobalTypeObject(), false, code->pushed(0), id);
} else if (scope) {
/* Definitely a local variable. */
TypeSet *types = scope->getVariable(cx, id);
types->addSubset(cx, scope->pool, code->pushed(0));
} else {
/* Ambiguous access, unknown result. */
code->setFixed(cx, 0, TYPE_UNKNOWN);
}
if (op == JSOP_CALLGLOBAL || op == JSOP_CALLGNAME || op == JSOP_CALLNAME)
code->setFixed(cx, 1, scope ? TYPE_UNDEFINED : TYPE_UNKNOWN);
CheckNextTest(cx, code, pc);
break;
}
case JSOP_BINDGNAME:
case JSOP_BINDNAME:
/* Handled below. */
break;
case JSOP_SETGNAME:
case JSOP_SETNAME: {
jsid id = GetAtomId(cx, this, pc, 0);
const AnalyzeStateStack &stack = state.popped(1);
if (stack.scope == SCOPE_GLOBAL) {
PropertyAccess(cx, code, cx->getGlobalTypeObject(), true, code->popped(0), id);
} else if (stack.scope) {
TypeSet *types = stack.scope->getVariable(cx, id);
code->popped(0)->addSubset(cx, pool, types);
} else {
cx->compartment->types.monitorBytecode(cx, code);
}
MergePushed(cx, pool, code, 0, code->popped(0));
break;
}
case JSOP_GETXPROP: {
jsid id = GetAtomId(cx, this, pc, 0);
const AnalyzeStateStack &stack = state.popped(0);
if (stack.scope == SCOPE_GLOBAL) {
PropertyAccess(cx, code, cx->getGlobalTypeObject(), false, code->pushed(0), id);
} else if (stack.scope) {
TypeSet *types = stack.scope->getVariable(cx, id);
types->addSubset(cx, stack.scope->pool, code->pushed(0));
} else {
code->setFixed(cx, 0, TYPE_UNKNOWN);
}
break;
}
case JSOP_INCNAME:
case JSOP_DECNAME:
case JSOP_NAMEINC:
case JSOP_NAMEDEC: {
jsid id = GetAtomId(cx, this, pc, 0);
Script *scope = SearchScope(cx, this, code->inStack, id);
if (scope == SCOPE_GLOBAL) {
PropertyAccess(cx, code, cx->getGlobalTypeObject(), true, NULL, id);
PropertyAccess(cx, code, cx->getGlobalTypeObject(), false, code->pushed(0), id);
} else if (scope) {
TypeSet *types = scope->getVariable(cx, id);
types->addSubset(cx, scope->pool, code->pushed(0));
types->addArith(cx, scope->pool, code, types);
if (code->hasIncDecOverflow)
types->addType(cx, TYPE_DOUBLE);
} else {
cx->compartment->types.monitorBytecode(cx, code);
}
break;
}
case JSOP_SETGLOBAL:
case JSOP_SETCONST: {
/*
* Even though they are on the global object, GLOBAL accesses do not run into
* the issues which require monitoring that other property accesses do:
* __proto__ is still emitted as a SETGNAME even if there is a 'var __proto__',
* and there will be no getter/setter in a prototype, and 'constructor',
* 'prototype' and 'caller' do not have special meaning on the global object.
*/
jsid id = (op == JSOP_SETGLOBAL) ? GetGlobalId(cx, this, pc) : GetAtomId(cx, this, pc, 0);
TypeSet *types = cx->getGlobalTypeObject()->getProperty(cx, id, true);
code->popped(0)->addSubset(cx, pool, types);
MergePushed(cx, pool, code, 0, code->popped(0));
break;
}
case JSOP_INCGLOBAL:
case JSOP_DECGLOBAL:
case JSOP_GLOBALINC:
case JSOP_GLOBALDEC: {
jsid id = GetGlobalId(cx, this, pc);
TypeSet *types = cx->getGlobalTypeObject()->getProperty(cx, id, true);
types->addArith(cx, cx->compartment->types.pool, code, types);
MergePushed(cx, cx->compartment->types.pool, code, 0, types);
if (code->hasIncDecOverflow)
types->addType(cx, TYPE_DOUBLE);
break;
}
case JSOP_INCGNAME:
case JSOP_DECGNAME:
case JSOP_GNAMEINC:
case JSOP_GNAMEDEC: {
jsid id = GetAtomId(cx, this, pc, 0);
PropertyAccess(cx, code, cx->getGlobalTypeObject(), true, NULL, id);
PropertyAccess(cx, code, cx->getGlobalTypeObject(), false, code->pushed(0), id);
break;
}
case JSOP_GETUPVAR:
case JSOP_CALLUPVAR:
case JSOP_GETFCSLOT:
case JSOP_CALLFCSLOT: {
unsigned index = GET_UINT16(pc);
jsid id = JSID_VOID;
Script *newScript = GetUpvarVariable(cx, code, index, &id);
TypeSet *types = newScript->getVariable(cx, id);
MergePushed(cx, newScript->pool, code, 0, types);
if (op == JSOP_CALLUPVAR || op == JSOP_CALLFCSLOT)
code->setFixed(cx, 1, TYPE_UNDEFINED);
break;
}
case JSOP_GETARG:
case JSOP_SETARG:
case JSOP_CALLARG: {
jsid id = getArgumentId(GET_ARGNO(pc));
if (!JSID_IS_VOID(id)) {
TypeSet *types = getVariable(cx, id);
MergePushed(cx, pool, code, 0, types);
if (op == JSOP_SETARG)
code->popped(0)->addSubset(cx, pool, types);
} else {
code->setFixed(cx, 0, TYPE_UNKNOWN);
}
if (op == JSOP_CALLARG)
code->setFixed(cx, 1, TYPE_UNDEFINED);
break;
}
case JSOP_INCARG:
case JSOP_DECARG:
case JSOP_ARGINC:
case JSOP_ARGDEC: {
jsid id = getArgumentId(GET_ARGNO(pc));
TypeSet *types = getVariable(cx, id);
types->addArith(cx, pool, code, types);
MergePushed(cx, pool, code, 0, types);
if (code->hasIncDecOverflow)
types->addType(cx, TYPE_DOUBLE);
break;
}
case JSOP_ARGSUB:
code->setFixed(cx, 0, TYPE_UNKNOWN);
break;
case JSOP_GETLOCAL:
case JSOP_SETLOCAL:
case JSOP_SETLOCALPOP:
case JSOP_CALLLOCAL: {
uint32 local = GET_SLOTNO(pc);
jsid id = getLocalId(local, code);
TypeSet *types;
JSArenaPool *pool;
if (!JSID_IS_VOID(id)) {
types = evalParent()->getVariable(cx, id);
pool = &evalParent()->pool;
} else {
types = getStackTypes(GET_SLOTNO(pc), code);
pool = &this->pool;
}
if (op != JSOP_SETLOCALPOP) {
MergePushed(cx, *pool, code, 0, types);
if (op == JSOP_CALLLOCAL)
code->setFixed(cx, 1, TYPE_UNDEFINED);
}
if (op == JSOP_SETLOCAL || op == JSOP_SETLOCALPOP) {
state.clearLocal(local);
if (state.popped(0).isConstant)
state.addConstLocal(local, state.popped(0).isZero);
code->popped(0)->addSubset(cx, this->pool, types);
} else {
/*
* Add void type if the variable might be undefined. TODO: monitor for
* undefined read instead? localDefined returns false for
* variables which could have a legitimate use-before-def, for let
* variables and variables exceeding the LOCAL_LIMIT threshold.
*/
if (localHasUseBeforeDef(local) || !localDefined(local, pc))
code->pushed(0)->addType(cx, TYPE_UNDEFINED);
}
break;
}
case JSOP_INCLOCAL:
case JSOP_DECLOCAL:
case JSOP_LOCALINC:
case JSOP_LOCALDEC: {
uint32 local = GET_SLOTNO(pc);
jsid id = getLocalId(local, code);
state.clearLocal(local);
TypeSet *types = evalParent()->getVariable(cx, id);
types->addArith(cx, evalParent()->pool, code, types);
MergePushed(cx, evalParent()->pool, code, 0, types);
if (code->hasIncDecOverflow)
types->addType(cx, TYPE_DOUBLE);
break;
}
case JSOP_ARGUMENTS: {
/*
* This can show up either when the arguments array is being accessed
* or when there is a local variable named arguments.
*/
JS_ASSERT(fun);
TypeSet *types = getVariable(cx, id_arguments(cx));
types->addType(cx, (jstype) cx->getFixedTypeObject(TYPE_OBJECT_ARGUMENTS));
MergePushed(cx, pool, code, 0, types);
break;
}
case JSOP_ARGCNT: {
JS_ASSERT(fun);
TypeSet *types = getVariable(cx, id_arguments(cx));
types->addType(cx, (jstype) cx->getFixedTypeObject(TYPE_OBJECT_ARGUMENTS));
types->addGetProperty(cx, code, code->pushed(0), id_length(cx));
break;
}
case JSOP_SETPROP:
case JSOP_SETMETHOD: {
jsid id = GetAtomId(cx, this, pc, 0);
code->popped(1)->addSetProperty(cx, code, code->popped(0), id);
MergePushed(cx, pool, code, 0, code->popped(0));
break;
}
case JSOP_GETPROP:
case JSOP_CALLPROP: {
jsid id = GetAtomId(cx, this, pc, 0);
code->popped(0)->addGetProperty(cx, code, code->pushed(0), id);
if (op == JSOP_CALLPROP)
code->popped(0)->addFilterPrimitives(cx, pool, code->pushed(1), true);
CheckNextTest(cx, code, pc);
break;
}
case JSOP_INCPROP:
case JSOP_DECPROP:
case JSOP_PROPINC:
case JSOP_PROPDEC: {
jsid id = GetAtomId(cx, this, pc, 0);
code->popped(0)->addGetProperty(cx, code, code->pushed(0), id);
code->popped(0)->addSetProperty(cx, code, NULL, id);
break;
}
case JSOP_GETTHISPROP: {
jsid id = GetAtomId(cx, this, pc, 0);
/* Need a new type set to model conversion of NULL to the global object. */
TypeSet *newTypes = TypeSet::make(cx, pool, "thisprop");
thisTypes.addTransformThis(cx, pool, newTypes);
newTypes->addGetProperty(cx, code, code->pushed(0), id);
CheckNextTest(cx, code, pc);
break;
}
case JSOP_GETARGPROP: {
jsid id = getArgumentId(GET_ARGNO(pc));
TypeSet *types = getVariable(cx, id);
jsid propid = GetAtomId(cx, this, pc, SLOTNO_LEN);
types->addGetProperty(cx, code, code->pushed(0), propid);
CheckNextTest(cx, code, pc);
break;
}
case JSOP_GETLOCALPROP: {
jsid id = getLocalId(GET_SLOTNO(pc), code);
TypeSet *types = evalParent()->getVariable(cx, id);
jsid propid = GetAtomId(cx, this, pc, SLOTNO_LEN);
types->addGetProperty(cx, code, code->pushed(0), propid);
CheckNextTest(cx, code, pc);
break;
}
case JSOP_GETELEM:
case JSOP_CALLELEM:
code->popped(0)->addGetElem(cx, code, code->popped(1), code->pushed(0));
CheckNextTest(cx, code, pc);
if (op == JSOP_CALLELEM)
code->popped(1)->addFilterPrimitives(cx, pool, code->pushed(1), true);
break;
case JSOP_SETELEM:
if (state.popped(1).isZero) {
/*
* Initializing the array with what looks like it could be zero.
* This is sensitive to the order in which bytecodes are emitted
* for common loop forms: '(for i = 0;; i++) a[i] = ...' and
* 'i = 0; while () { a[i] = ...; i++ }. In the bytecode the increment
* will appear after the initialization, and we are looking for arrays
* initialized between the two statements.
*/
code->popped(2)->add(cx, ArenaNew<TypeConstraintPossiblyPacked>(pool));
}
code->popped(1)->addSetElem(cx, code, code->popped(2), code->popped(0));
MergePushed(cx, pool, code, 0, code->popped(0));
break;
case JSOP_INCELEM:
case JSOP_DECELEM:
case JSOP_ELEMINC:
case JSOP_ELEMDEC:
code->popped(0)->addGetElem(cx, code, code->popped(1), code->pushed(0));
code->popped(0)->addSetElem(cx, code, code->popped(1), NULL);
break;
case JSOP_LENGTH:
/* Treat this as an access to the length property. */
code->popped(0)->addGetProperty(cx, code, code->pushed(0), id_length(cx));
break;
case JSOP_THIS:
thisTypes.addTransformThis(cx, pool, code->pushed(0));
/* 'this' refers to the parent global/scope object in non-function scripts. */
if (!fun)
code->setFixed(cx, 0, (jstype) cx->getGlobalTypeObject());
break;
case JSOP_RETURN:
case JSOP_SETRVAL:
if (fun)
code->popped(0)->addSubset(cx, pool, &function()->returnTypes);
break;
case JSOP_ADD:
code->popped(0)->addArith(cx, pool, code, code->pushed(0), code->popped(1));
code->popped(1)->addArith(cx, pool, code, code->pushed(0), code->popped(0));
break;
case JSOP_SUB:
case JSOP_MUL:
case JSOP_MOD:
case JSOP_DIV:
code->popped(0)->addArith(cx, pool, code, code->pushed(0));
code->popped(1)->addArith(cx, pool, code, code->pushed(0));
if (op == JSOP_DIV) {
/* Guess that divisions other than x/n produce doubles. */
if (!state.popped(0).isConstant)
code->setFixed(cx, 0, TYPE_DOUBLE);
}
break;
case JSOP_NEG:
case JSOP_POS:
code->popped(0)->addArith(cx, pool, code, code->pushed(0));
break;
case JSOP_LAMBDA:
case JSOP_LAMBDA_FC:
case JSOP_DEFFUN:
case JSOP_DEFFUN_FC:
case JSOP_DEFLOCALFUN:
case JSOP_DEFLOCALFUN_FC: {
unsigned funOffset = 0;
if (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC)
funOffset = SLOTNO_LEN;
unsigned off = (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC) ? SLOTNO_LEN : 0;
JSObject *obj = GetScriptObject(cx, script, pc, off);
TypeFunction *function = obj->getTypeObject()->asFunction();
/* Remember where this script was defined. */
function->script->analysis->parent = script;
function->script->analysis->parentpc = pc;
TypeSet *res = NULL;
if (op == JSOP_LAMBDA || op == JSOP_LAMBDA_FC) {
res = code->pushed(0);
} else if (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC) {
jsid id = getLocalId(GET_SLOTNO(pc), code);
res = evalParent()->getVariable(cx, id);
} else {
JSAtom *atom = obj->getFunctionPrivate()->atom;
JS_ASSERT(atom);
jsid id = ATOM_TO_JSID(atom);
if (isGlobal()) {
/* Defined function at global scope. */
res = cx->getGlobalTypeObject()->getProperty(cx, id, true);
} else {
/* Defined function in a function eval() or ambiguous function scope. */
TrashScope(cx, this, id);
break;
}
}
if (res)
res->addType(cx, (jstype) function);
else
cx->compartment->types.monitorBytecode(cx, code);
break;
}
case JSOP_CALL:
case JSOP_SETCALL:
case JSOP_EVAL:
case JSOP_FUNCALL:
case JSOP_FUNAPPLY:
case JSOP_NEW: {
/* Construct the base call information about this site. */
unsigned argCount = GetUseCount(script, offset) - 2;
TypeCallsite *callsite = ArenaNew<TypeCallsite>(pool, code, op == JSOP_NEW, argCount);
callsite->thisTypes = code->popped(argCount);
callsite->returnTypes = code->pushed(0);
for (unsigned i = 0; i < argCount; i++) {
TypeSet *argTypes = code->popped(argCount - 1 - i);
callsite->argumentTypes[i] = argTypes;
}
code->popped(argCount + 1)->addCall(cx, callsite);
break;
}
case JSOP_NEWINIT:
case JSOP_NEWARRAY:
case JSOP_NEWOBJECT: {
TypeObject *object;
if (op == JSOP_NEWARRAY || (op == JSOP_NEWINIT && pc[1] == JSProto_Array)) {
object = code->initArray;
jsbytecode *next = pc + GetBytecodeLength(pc);
if (JSOp(*next) != JSOP_ENDINIT)
object->possiblePackedArray = true;
} else {
object = code->initObject;
}
code->pushed(0)->addType(cx, (jstype) object);
break;
}
case JSOP_ENDINIT:
break;
case JSOP_INITELEM: {
TypeObject *object = code->initObject;
JS_ASSERT(object);
code->pushed(0)->addType(cx, (jstype) object);
TypeSet *types;
if (state.popped(1).hasDouble) {
Value val = DoubleValue(state.popped(1).doubleValue);
jsid id;
if (!js_InternNonIntElementId(cx, NULL, val, &id))
JS_NOT_REACHED("Bad");
types = object->getProperty(cx, id, true);
} else {
types = object->getProperty(cx, JSID_VOID, true);
}
if (state.hasGetSet)
types->addType(cx, (jstype) cx->getFixedTypeObject(TYPE_OBJECT_GETSET));
else if (state.hasHole)
cx->markTypeArrayNotPacked(object, false);
else
code->popped(0)->addSubset(cx, pool, types);
state.hasGetSet = false;
state.hasHole = false;
break;
}
case JSOP_GETTER:
case JSOP_SETTER:
state.hasGetSet = true;
break;
case JSOP_HOLE:
state.hasHole = true;
break;
case JSOP_INITPROP:
case JSOP_INITMETHOD: {
TypeObject *object = code->initObject;
JS_ASSERT(object);
code->pushed(0)->addType(cx, (jstype) object);
jsid id = GetAtomId(cx, this, pc, 0);
TypeSet *types = object->getProperty(cx, id, true);
if (id == id___proto__(cx) || id == id_prototype(cx))
cx->compartment->types.monitorBytecode(cx, code);
else if (state.hasGetSet)
types->addType(cx, (jstype) cx->getFixedTypeObject(TYPE_OBJECT_GETSET));
else
code->popped(0)->addSubset(cx, pool, types);
state.hasGetSet = false;
JS_ASSERT(!state.hasHole);
break;
}
case JSOP_ENTERWITH:
/*
* Scope lookups can occur on the value being pushed here. We don't track
* the value or its properties, and just monitor all name opcodes contained
* by the with.
*/
code->pushedArray[0].group()->boundWith = true;
break;
case JSOP_ENTERBLOCK: {
JSObject *obj = GetScriptObject(cx, script, pc, 0);
unsigned defCount = GetDefCount(script, offset);
const Shape *shape = obj->lastProperty();
for (unsigned i = 0; i < defCount; i++) {
code->pushedArray[i].group()->letVariable = shape->id;
shape = shape->previous();
}
break;
}
case JSOP_ITER:
/*
* The actual pushed value is an iterator object, which we don't care about.
* Propagate the target of the iteration itself so that we'll be able to detect
* when an object of Iterator class flows to the JSOP_FOR* opcode, which could
* be a generator that produces arbitrary values with 'for in' syntax.
*/
MergePushed(cx, pool, code, 0, code->popped(0));
code->pushedArray[0].group()->ignoreTypeTag = true;
break;
case JSOP_MOREITER:
code->pushedArray[0].group()->ignoreTypeTag = true;
MergePushed(cx, pool, code, 0, code->popped(0));
code->setFixed(cx, 1, TYPE_BOOLEAN);
break;
case JSOP_FORNAME: {
jsid id = GetAtomId(cx, this, pc, 0);
Script *scope = SearchScope(cx, this, code->inStack, id);
if (scope == SCOPE_GLOBAL)
SetForTypes(cx, state, code, cx->getGlobalTypeObject()->getProperty(cx, id, true));
else if (scope)
SetForTypes(cx, state, code, scope->getVariable(cx, id));
else
cx->compartment->types.monitorBytecode(cx, code);
break;
}
case JSOP_FORGLOBAL: {
jsid id = GetGlobalId(cx, this, pc);
SetForTypes(cx, state, code, cx->getGlobalTypeObject()->getProperty(cx, id, true));
break;
}
case JSOP_FORLOCAL: {
jsid id = getLocalId(GET_SLOTNO(pc), code);
JS_ASSERT(!JSID_IS_VOID(id));
SetForTypes(cx, state, code, evalParent()->getVariable(cx, id));
break;
}
case JSOP_FORARG: {
jsid id = getArgumentId(GET_ARGNO(pc));
JS_ASSERT(!JSID_IS_VOID(id));
SetForTypes(cx, state, code, getVariable(cx, id));
break;
}
case JSOP_FORPROP:
case JSOP_ENUMELEM:
cx->compartment->types.monitorBytecode(cx, code);
break;
case JSOP_ARRAYPUSH: {
TypeSet *types = getStackTypes(GET_SLOTNO(pc), code);
types->addSetProperty(cx, code, code->popped(0), JSID_VOID);
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_EXCEPTION:
code->setFixed(cx, 0, TYPE_UNKNOWN);
break;
case JSOP_DEFVAR:
/*
* Watch for variable declarations within an 'eval'. We will effectively
* ignore this declaration, merging references to it into the innermost
* containing scope which declares a variable with the same name.
* Get that scope and mark its type as unknown. The same goes for variables
* defined in a function script using DEFVAR or DEFFUN. :FIXME: this approach
* needs monitoring if there is a DEFVAR is an ambiguous scope, and may be
* broken altogether.
*/
if (!isGlobal()) {
jsid id = GetAtomId(cx, this, pc, 0);
TrashScope(cx, this, id);
}
break;
case JSOP_DELPROP:
case JSOP_DELELEM:
case JSOP_DELNAME:
/* TODO: watch for deletes on the global object. */
code->setFixed(cx, 0, TYPE_BOOLEAN);
break;
case JSOP_LEAVEBLOCKEXPR:
MergePushed(cx, pool, code, 0, code->popped(0));
break;
case JSOP_CASE:
MergePushed(cx, pool, code, 0, code->popped(1));
break;
case JSOP_UNBRAND:
MergePushed(cx, pool, code, 0, code->popped(0));
break;
case JSOP_GENERATOR:
if (fun) {
TypeObject *object = cx->getFixedTypeObject(TYPE_OBJECT_NEW_GENERATOR);
function()->returnTypes.addType(cx, (jstype) object);
}
break;
case JSOP_YIELD:
code->setFixed(cx, 0, TYPE_UNKNOWN);
break;
case JSOP_CALLXMLNAME:
code->setFixed(cx, 1, TYPE_UNKNOWN);
/* FALLTHROUGH */
case JSOP_XMLNAME:
code->setFixed(cx, 0, TYPE_UNKNOWN);
break;
case JSOP_SETXMLNAME:
cx->compartment->types.monitorBytecode(cx, code);
break;
case JSOP_BINDXMLNAME:
break;
case JSOP_TOXML:
case JSOP_TOXMLLIST:
case JSOP_XMLPI:
case JSOP_XMLCDATA:
case JSOP_XMLCOMMENT:
case JSOP_DESCENDANTS:
case JSOP_TOATTRNAME:
case JSOP_QNAMECONST:
case JSOP_QNAME:
case JSOP_ANYNAME:
case JSOP_GETFUNNS:
code->setFixed(cx, 0, (jstype) cx->getFixedTypeObject(TYPE_OBJECT_XML));
break;
case JSOP_FILTER:
/* Note: the second value pushed by filter is a hole, and not modelled. */
MergePushed(cx, pool, code, 0, code->popped(0));
code->pushedArray[0].group()->boundWith = true;
/* Name binding inside filters is currently broken :FIXME: bug 605200. */
break;
case JSOP_ENDFILTER:
MergePushed(cx, pool, code, 0, code->popped(1));
break;
case JSOP_DEFSHARP: {
/*
* This bytecode uses the value at the top of the stack, though this is
* not specified in the opcode table.
*/
JS_ASSERT(code->inStack);
TypeSet *value = &code->inStack->group()->types;
/* Model sharp values as local variables. */
char name[24];
JS_snprintf(name, sizeof(name), "#%d:%d",
GET_UINT16(pc), GET_UINT16(pc + UINT16_LEN));
JSAtom *atom = js_Atomize(cx, name, strlen(name), ATOM_PINNED);
jsid id = ATOM_TO_JSID(atom);
TypeSet *types = evalParent()->getVariable(cx, id);
value->addSubset(cx, pool, types);
break;
}
case JSOP_USESHARP: {
char name[24];
JS_snprintf(name, sizeof(name), "#%d:%d",
GET_UINT16(pc), GET_UINT16(pc + UINT16_LEN));
JSAtom *atom = js_Atomize(cx, name, strlen(name), ATOM_PINNED);
jsid id = ATOM_TO_JSID(atom);
TypeSet *types = evalParent()->getVariable(cx, id);
MergePushed(cx, evalParent()->pool, code, 0, types);
break;
}
case JSOP_CALLEE:
code->setFixed(cx, 0, (jstype) function());
break;
default:
TypeFailure(cx, "Unknown bytecode: %s", js_CodeNameTwo[op]);
}
/* Compute temporary analysis state after the bytecode. */
if (!state.stack)
return;
if (op == JSOP_DUP) {
state.stack[code->stackDepth] = state.stack[code->stackDepth - 1];
state.stackDepth = code->stackDepth + 1;
} else if (op == JSOP_DUP2) {
state.stack[code->stackDepth] = state.stack[code->stackDepth - 2];
state.stack[code->stackDepth + 1] = state.stack[code->stackDepth - 1];
state.stackDepth = code->stackDepth + 2;
} else {
unsigned nuses = GetUseCount(script, offset);
unsigned ndefs = GetDefCount(script, offset);
memset(&state.stack[code->stackDepth - nuses], 0, ndefs * sizeof(AnalyzeStateStack));
state.stackDepth = code->stackDepth - nuses + ndefs;
}
switch (op) {
case JSOP_BINDGNAME: {
AnalyzeStateStack &stack = state.popped(0);
stack.scope = SCOPE_GLOBAL;
break;
}
case JSOP_BINDNAME: {
jsid id = GetAtomId(cx, this, pc, 0);
AnalyzeStateStack &stack = state.popped(0);
stack.scope = SearchScope(cx, this, code->inStack, id);
break;
}
case JSOP_ITER: {
uintN flags = pc[1];
if (flags & JSITER_FOREACH)
state.popped(0).isForEach = true;
break;
}
case JSOP_DOUBLE: {
AnalyzeStateStack &stack = state.popped(0);
stack.hasDouble = true;
stack.doubleValue = GetScriptConst(cx, script, pc).toDouble();
break;
}
case JSOP_ZERO:
state.popped(0).isZero = true;
/* FALLTHROUGH */
case JSOP_ONE:
case JSOP_INT8:
case JSOP_INT32:
case JSOP_UINT16:
case JSOP_UINT24:
state.popped(0).isConstant = true;
break;
case JSOP_GETLOCAL:
if (state.maybeLocalConst(GET_SLOTNO(pc), false)) {
state.popped(0).isConstant = true;
if (state.maybeLocalConst(GET_SLOTNO(pc), true))
state.popped(0).isZero = true;
}
break;
default:;
}
}
/////////////////////////////////////////////////////////////////////
// Printing
/////////////////////////////////////////////////////////////////////
#ifdef DEBUG
void
Bytecode::print(JSContext *cx)
{
jsbytecode *pc = script->getScript()->code + offset;
JSOp op = (JSOp)*pc;
JS_ASSERT(op < JSOP_LIMIT);
const JSCodeSpec *cs = &js_CodeSpec[op];
const char *name = js_CodeNameTwo[op];
uint32 type = JOF_TYPE(cs->format);
switch (type) {
case JOF_BYTE:
case JOF_TABLESWITCH:
case JOF_TABLESWITCHX:
case JOF_LOOKUPSWITCH:
case JOF_LOOKUPSWITCHX:
printf("%s", name);
break;
case JOF_JUMP:
case JOF_JUMPX: {
ptrdiff_t off = GetJumpOffset(pc, pc);
printf("%s %u", name, unsigned(offset + off));
break;
}
case JOF_ATOM: {
if (op == JSOP_DOUBLE) {
printf("%s", name);
} else {
jsid id = GetAtomId(cx, script, pc, 0);
if (JSID_IS_STRING(id))
printf("%s %s", name, TypeIdString(id));
else
printf("%s (index)", name);
}
break;
}
case JOF_OBJECT:
printf("%s (object)", name);
break;
case JOF_REGEXP:
printf("%s (regexp)", name);
break;
case JOF_UINT16PAIR:
printf("%s %d %d", name, GET_UINT16(pc), GET_UINT16(pc + UINT16_LEN));
break;
case JOF_UINT16:
printf("%s %d", name, GET_UINT16(pc));
break;
case JOF_QARG: {
jsid id = script->getArgumentId(GET_ARGNO(pc));
printf("%s %s", name, TypeIdString(id));
break;
}
case JOF_GLOBAL:
printf("%s %s", name, TypeIdString(GetGlobalId(cx, script, pc)));
break;
case JOF_LOCAL:
if ((op != JSOP_ARRAYPUSH) && (analyzed || (GET_SLOTNO(pc) < script->getScript()->nfixed))) {
jsid id = script->getLocalId(GET_SLOTNO(pc), this);
printf("%s %d %s", name, GET_SLOTNO(pc), TypeIdString(id));
} else {
printf("%s %d", name, GET_SLOTNO(pc));
}
break;
case JOF_SLOTATOM: {
jsid id = GetAtomId(cx, script, pc, SLOTNO_LEN);
jsid slotid = JSID_VOID;
if (op == JSOP_GETARGPROP)
slotid = script->getArgumentId(GET_ARGNO(pc));
if (op == JSOP_GETLOCALPROP && (analyzed || (GET_SLOTNO(pc) < script->getScript()->nfixed)))
slotid = script->getLocalId(GET_SLOTNO(pc), this);
printf("%s %u %s %s", name, GET_SLOTNO(pc), TypeIdString(slotid), TypeIdString(id));
break;
}
case JOF_SLOTOBJECT:
printf("%s %u (object)", name, GET_SLOTNO(pc));
break;
case JOF_UINT24:
JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY);
printf("%s %d", name, (jsint)GET_UINT24(pc));
break;
case JOF_UINT8:
printf("%s %d", name, (jsint)pc[1]);
break;
case JOF_INT8:
printf("%s %d", name, (jsint)GET_INT8(pc));
break;
case JOF_INT32:
printf("%s %d", name, (jsint)GET_INT32(pc));
break;
default:
JS_NOT_REACHED("Unknown opcode type");
}
}
#endif
void
Script::finish(JSContext *cx)
{
if (failed() || !codeArray)
return;
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++) {
Bytecode *code = codeArray[offset];
if (!code || !code->analyzed)
continue;
unsigned useCount = GetUseCount(script, offset);
if (!useCount)
continue;
TypeStack *stack = code->inStack->group();
for (unsigned i = 0; i < useCount; i++) {
TypeSet *types = &stack->types;
/* TODO: distinguish direct and indirect call sites. */
unsigned typeCount = types->objectCount ? 1 : 0;
for (jstype type = TYPE_UNDEFINED; type <= TYPE_STRING; type++) {
if (types->typeFlags & (1 << type))
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->typeFlags & TYPE_FLAG_DOUBLE) {
JS_ASSERT(types->typeFlags & TYPE_FLAG_INT32);
typeCount--;
}
if ((types->typeFlags & TYPE_FLAG_UNKNOWN) ||
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]++;
}
stack = stack->innerStack ? stack->innerStack->group() : NULL;
}
}
#ifdef DEBUG
if (parent) {
if (fun)
printf("Function");
else
printf("Eval");
printf(" #%u @%u\n", id, parent->analysis->id);
} else {
printf("Main #%u:\n", id);
}
if (!codeArray) {
printf("(unused)\n");
return;
}
/* Print out points where variables became unconditionally defined. */
printf("defines:");
for (unsigned i = 0; i < localCount(); i++) {
if (locals[i] != LOCAL_USE_BEFORE_DEF && locals[i] != LOCAL_CONDITIONALLY_DEFINED)
printf(" %s@%u", TypeIdString(getLocalId(i, NULL)), locals[i]);
}
printf("\n");
printf("locals:");
if (variableCount >= 2) {
unsigned capacity = HashSetCapacity(variableCount);
for (unsigned i = 0; i < capacity; i++) {
Variable *var = variableSet[i];
if (var) {
printf("\n %s:", TypeIdString(var->id));
var->types.print(cx);
}
}
} else if (variableCount == 1) {
Variable *var = (Variable *) variableSet;
printf("\n %s:", TypeIdString(var->id));
var->types.print(cx);
}
printf("\n");
int id_count = 0;
for (unsigned offset = 0; offset < script->length; offset++) {
Bytecode *code = codeArray[offset];
if (!code)
continue;
printf("#%u:%05u: ", id, offset);
code->print(cx);
printf("\n");
if (code->defineCount) {
printf(" defines:");
for (unsigned i = 0; i < code->defineCount; i++) {
uint32 local = code->defineArray[i];
printf(" %s", TypeIdString(getLocalId(local, NULL)));
}
printf("\n");
}
TypeStack *stack;
unsigned useCount = GetUseCount(script, offset);
if (useCount) {
printf(" use:");
stack = code->inStack->group();
for (unsigned i = 0; i < useCount; i++) {
if (!stack->id)
stack->id = ++id_count;
printf(" %d", stack->id);
stack = stack->innerStack ? stack->innerStack->group() : NULL;
}
printf("\n");
/* Watch for stack values without any types. */
stack = code->inStack->group();
for (unsigned i = 0; i < useCount; i++) {
if (!IgnorePopped((JSOp)script->code[offset], i)) {
if (stack->types.typeFlags == 0)
printf(" missing stack: %d\n", stack->id);
}
stack = stack->innerStack ? stack->innerStack->group() : NULL;
}
}
unsigned defCount = GetDefCount(script, offset);
if (defCount) {
printf(" def:");
for (unsigned i = 0; i < defCount; i++) {
stack = code->pushedArray[i].group();
if (!stack->id)
stack->id = ++id_count;
printf(" %d", stack->id);
}
printf("\n");
for (unsigned i = 0; i < defCount; i++) {
stack = code->pushedArray[i].group();
printf(" type %d:", stack->id);
stack->types.print(cx);
printf("\n");
}
}
if (code->monitorNeeded)
printf(" monitored\n");
}
printf("\n");
TypeObject *object = objects;
while (object) {
object->print(cx);
object = object->next;
}
#endif /* DEBUG */
}
} } /* namespace js::analyze */
/////////////////////////////////////////////////////////////////////
// Tracing
/////////////////////////////////////////////////////////////////////
namespace js {
static inline void
TraceObjectList(JSTracer *trc, types::TypeObject *objects)
{
types::TypeObject *object = objects;
while (object) {
gc::MarkString(trc, JSID_TO_STRING(object->name), "type_object_name");
if (object->propertyCount >= 2) {
unsigned capacity = types::HashSetCapacity(object->propertyCount);
for (unsigned i = 0; i < capacity; i++) {
types::Property *prop = object->propertySet[i];
if (prop && !JSID_IS_VOID(prop->id))
gc::MarkString(trc, JSID_TO_STRING(prop->id), "type_property");
}
} else if (object->propertyCount == 1) {
types::Property *prop = (types::Property *) object->propertySet;
if (!JSID_IS_VOID(prop->id))
gc::MarkString(trc, JSID_TO_STRING(prop->id), "type_property");
}
object = object->next;
}
}
void
analyze::Script::trace(JSTracer *trc)
{
if (fun) {
JS_SET_TRACING_NAME(trc, "type_script");
gc::Mark(trc, fun);
}
TraceObjectList(trc, objects);
if (variableCount >= 2) {
unsigned capacity = types::HashSetCapacity(variableCount);
for (unsigned i = 0; i < capacity; i++) {
types::Variable *var = variableSet[i];
if (var)
gc::MarkString(trc, JSID_TO_STRING(var->id), "type_property");
}
} else if (variableCount == 1) {
types::Variable *var = (types::Variable *) variableSet;
gc::MarkString(trc, JSID_TO_STRING(var->id), "type_property");
}
unsigned nameCount = script->nfixed + argCount();
for (unsigned i = 0; i < nameCount; i++) {
if (localNames[i]) {
JSAtom *atom = JS_LOCAL_NAME_TO_ATOM(localNames[i]);
gc::MarkString(trc, ATOM_TO_STRING(atom), "type_script_local");
}
}
}
void
types::TypeCompartment::trace(JSTracer *trc)
{
TraceObjectList(trc, objects);
}
} /* namespace js */