mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
5412 lines
169 KiB
C++
5412 lines
169 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 "jsgcmark.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 "jsgcinlines.h"
|
|
#include "jsinferinlines.h"
|
|
#include "jsobjinlines.h"
|
|
#include "jsscriptinlines.h"
|
|
#include "vm/Stack-inl.h"
|
|
|
|
#ifdef JS_HAS_XML_SUPPORT
|
|
#include "jsxml.h"
|
|
#endif
|
|
|
|
using namespace js;
|
|
using namespace js::types;
|
|
using namespace js::analyze;
|
|
|
|
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);
|
|
}
|
|
|
|
const char *
|
|
types::TypeIdStringImpl(jsid id)
|
|
{
|
|
if (JSID_IS_VOID(id))
|
|
return "(index)";
|
|
if (JSID_IS_EMPTY(id))
|
|
return "(new)";
|
|
static char bufs[4][100];
|
|
static unsigned which = 0;
|
|
which = (which + 1) & 3;
|
|
PutEscapedString(bufs[which], 100, JSID_TO_FLAT_STRING(id), 0);
|
|
return bufs[which];
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// 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, "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 *
|
|
types::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_LAZYARGS:
|
|
return "lazyargs";
|
|
case TYPE_UNKNOWN:
|
|
return "unknown";
|
|
default: {
|
|
JS_ASSERT(TypeIsObject(type));
|
|
TypeObject *object = (TypeObject *) type;
|
|
return object->name();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
types::InferSpew(SpewChannel channel, const char *fmt, ...)
|
|
{
|
|
if (!InferSpewActive(channel))
|
|
return;
|
|
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
fprintf(stdout, "[infer] ");
|
|
vfprintf(stdout, fmt, ap);
|
|
fprintf(stdout, "\n");
|
|
va_end(ap);
|
|
}
|
|
|
|
/* Whether types can be considered to contain type or an equivalent, for checking results. */
|
|
bool
|
|
types::TypeMatches(JSContext *cx, TypeSet *types, jstype type)
|
|
{
|
|
if (types->hasType(type))
|
|
return true;
|
|
|
|
/*
|
|
* If this is a type for an object with unknown properties, match any object
|
|
* in the type set which also has unknown properties. This avoids failure
|
|
* on objects whose prototype (and thus type) changes dynamically, which will
|
|
* mark the old and new type objects as unknown.
|
|
*/
|
|
if (TypeIsObject(type) && ((TypeObject*)type)->unknownProperties()) {
|
|
unsigned count = types->getObjectCount();
|
|
for (unsigned i = 0; i < count; i++) {
|
|
TypeObject *object = types->getObject(i);
|
|
if (object && object->unknownProperties())
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
types::TypeHasProperty(JSContext *cx, TypeObject *obj, jsid id, const Value &value)
|
|
{
|
|
/*
|
|
* Check the correctness of the type information in the object's property
|
|
* against an actual value. Note that we are only checking the .types set,
|
|
* not the .ownTypes set, and could miss cases where a type set is missing
|
|
* entries from its ownTypes set when they are shadowed by a prototype property.
|
|
*/
|
|
if (cx->typeInferenceEnabled() && !obj->unknownProperties() && !value.isUndefined()) {
|
|
id = MakeTypeId(cx, id);
|
|
|
|
/* Watch for properties which inference does not monitor. */
|
|
if (id == id___proto__(cx) || id == id_constructor(cx) || id == id_caller(cx))
|
|
return true;
|
|
|
|
/*
|
|
* If we called in here while resolving a type constraint, we may be in the
|
|
* middle of resolving a standard class and the type sets will not be updated
|
|
* until the outer TypeSet::add finishes.
|
|
*/
|
|
if (cx->compartment->types.pendingCount)
|
|
return true;
|
|
|
|
jstype type = GetValueType(cx, value);
|
|
|
|
AutoEnterTypeInference enter(cx);
|
|
|
|
TypeSet *types = obj->getProperty(cx, id, false);
|
|
if (types && !TypeMatches(cx, types, type)) {
|
|
TypeFailure(cx, "Missing type in object %s %s: %s",
|
|
obj->name(), TypeIdString(id), TypeString(type));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
void
|
|
types::TypeFailure(JSContext *cx, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
fprintf(stderr, "[infer failure] ");
|
|
vfprintf(stderr, fmt, ap);
|
|
fprintf(stderr, "\n");
|
|
va_end(ap);
|
|
|
|
cx->compartment->types.print(cx, cx->compartment);
|
|
|
|
fflush(stderr);
|
|
*((int*)NULL) = 0; /* Type warnings */
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// TypeSet
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
TypeSet::addTypeSet(JSContext *cx, ClonedTypeSet *types)
|
|
{
|
|
if (types->typeFlags & TYPE_FLAG_UNKNOWN) {
|
|
addType(cx, TYPE_UNKNOWN);
|
|
return;
|
|
}
|
|
|
|
for (jstype type = TYPE_UNDEFINED; type < TYPE_UNKNOWN; type++) {
|
|
if (types->typeFlags & (1 << type))
|
|
addType(cx, type);
|
|
}
|
|
|
|
if (types->objectCount >= 2) {
|
|
for (unsigned i = 0; i < types->objectCount; i++)
|
|
addType(cx, (jstype) types->objectSet[i]);
|
|
} else if (types->objectCount == 1) {
|
|
addType(cx, (jstype) types->objectSet);
|
|
}
|
|
}
|
|
|
|
inline void
|
|
TypeSet::add(JSContext *cx, TypeConstraint *constraint, bool callExisting)
|
|
{
|
|
if (!constraint) {
|
|
/* OOM failure while constructing the constraint. */
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
|
|
JS_ASSERT_IF(!constraint->condensed() && !constraint->persistentObject(),
|
|
constraint->script->compartment == cx->compartment);
|
|
JS_ASSERT_IF(!constraint->condensed(), cx->compartment->activeInference);
|
|
JS_ASSERT_IF(intermediate(), !constraint->persistentObject() && !constraint->condensed());
|
|
|
|
InferSpew(ISpewOps, "addConstraint: T%p C%p %s",
|
|
this, constraint, 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_UNKNOWN; type++) {
|
|
if (typeFlags & (1 << type))
|
|
cx->compartment->types.addPending(cx, constraint, this, type);
|
|
}
|
|
|
|
unsigned count = getObjectCount();
|
|
for (unsigned i = 0; i < count; i++) {
|
|
TypeObject *object = getObject(i);
|
|
if (object)
|
|
cx->compartment->types.addPending(cx, constraint, this, (jstype) object);
|
|
}
|
|
|
|
cx->compartment->types.resolvePending(cx);
|
|
}
|
|
|
|
void
|
|
TypeSet::print(JSContext *cx)
|
|
{
|
|
if (typeFlags & TYPE_FLAG_OWN_PROPERTY)
|
|
printf(" [own]");
|
|
if (typeFlags & TYPE_FLAG_CONFIGURED_PROPERTY)
|
|
printf(" [configured]");
|
|
|
|
if (isDefiniteProperty())
|
|
printf(" [definite:%d]", definiteSlot());
|
|
|
|
if (baseFlags() == 0 && !objectCount) {
|
|
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_LAZYARGS)
|
|
printf(" lazyargs");
|
|
|
|
if (objectCount) {
|
|
printf(" object[%u]", objectCount);
|
|
|
|
unsigned count = getObjectCount();
|
|
for (unsigned i = 0; i < count; i++) {
|
|
TypeObject *object = getObject(i);
|
|
if (object)
|
|
printf(" %s", object->name());
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// TypeSet constraints
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
/* Standard subset constraint, propagate all types from one set to another. */
|
|
class TypeConstraintSubset : public TypeConstraint
|
|
{
|
|
public:
|
|
TypeSet *target;
|
|
|
|
TypeConstraintSubset(JSScript *script, TypeSet *target)
|
|
: TypeConstraint("subset", script), target(target)
|
|
{
|
|
JS_ASSERT(target);
|
|
}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
/* Basic subset constraint, move all types to the target. */
|
|
target->addType(cx, type);
|
|
}
|
|
};
|
|
|
|
void
|
|
TypeSet::addSubset(JSContext *cx, JSScript *script, TypeSet *target)
|
|
{
|
|
add(cx, ArenaNew<TypeConstraintSubset>(cx->compartment->pool, script, target));
|
|
}
|
|
|
|
/* Subset constraint not associated with a script's analysis. */
|
|
class TypeConstraintBaseSubset : public TypeConstraint
|
|
{
|
|
public:
|
|
TypeObject *object;
|
|
TypeSet *target;
|
|
|
|
TypeConstraintBaseSubset(TypeObject *object, TypeSet *target)
|
|
: TypeConstraint("baseSubset", (JSScript *) 0x1),
|
|
object(object), target(target)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
target->addType(cx, type);
|
|
}
|
|
|
|
TypeObject * persistentObject() { return object; }
|
|
};
|
|
|
|
void
|
|
TypeSet::addBaseSubset(JSContext *cx, TypeObject *obj, TypeSet *target)
|
|
{
|
|
add(cx, cx->new_<TypeConstraintBaseSubset>(obj, target));
|
|
}
|
|
|
|
/* Condensed constraint marking a script dependent on this type set. */
|
|
class TypeConstraintCondensed : public TypeConstraint
|
|
{
|
|
public:
|
|
TypeConstraintCondensed(JSScript *script)
|
|
: TypeConstraint("condensed", script)
|
|
{}
|
|
|
|
void checkAnalysis(JSContext *cx)
|
|
{
|
|
if (script->hasAnalysis() && script->analysis(cx)->ranInference()) {
|
|
/*
|
|
* The script was analyzed, had the analysis collected/condensed,
|
|
* and then was reanalyzed. There are other constraints specifying
|
|
* exactly what the script depends on to trigger recompilation, and
|
|
* we can ignore this new type.
|
|
*
|
|
* Note that for this to hold, reanalysis of a script must always
|
|
* trigger recompilation, to ensure the freeze constraints which
|
|
* describe what the compiler depends on are in place.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
script->analysis(cx)->analyzeTypes(cx);
|
|
}
|
|
|
|
void newType(JSContext *cx, TypeSet*, jstype) { checkAnalysis(cx); }
|
|
void newPropertyState(JSContext *cx, TypeSet*) { checkAnalysis(cx); }
|
|
void newObjectState(JSContext *cx, TypeObject*, bool) { checkAnalysis(cx); }
|
|
|
|
bool condensed() { return true; }
|
|
};
|
|
|
|
bool
|
|
TypeSet::addCondensed(JSContext *cx, JSScript *script)
|
|
{
|
|
/* Condensed constraints are added during GC, so we need off-the-books allocation. */
|
|
TypeConstraintCondensed *constraint = OffTheBooks::new_<TypeConstraintCondensed>(script);
|
|
|
|
if (!constraint)
|
|
return false;
|
|
|
|
add(cx, constraint, false);
|
|
return true;
|
|
}
|
|
|
|
/* Constraints for reads/writes on object properties. */
|
|
class TypeConstraintProp : public TypeConstraint
|
|
{
|
|
public:
|
|
jsbytecode *pc;
|
|
|
|
/*
|
|
* If assign is true, the target is used to update a property of the object.
|
|
* If assign is false, the target is assigned the value of the property.
|
|
*/
|
|
bool assign;
|
|
TypeSet *target;
|
|
|
|
/* Property being accessed. */
|
|
jsid id;
|
|
|
|
TypeConstraintProp(JSScript *script, jsbytecode *pc,
|
|
TypeSet *target, jsid id, bool assign)
|
|
: TypeConstraint("prop", script), pc(pc),
|
|
assign(assign), target(target), id(id)
|
|
{
|
|
JS_ASSERT(script && pc);
|
|
|
|
/* 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, JSScript *script, jsbytecode *pc,
|
|
TypeSet *target, jsid id)
|
|
{
|
|
add(cx, ArenaNew<TypeConstraintProp>(cx->compartment->pool, script, pc, target, id, false));
|
|
}
|
|
|
|
void
|
|
TypeSet::addSetProperty(JSContext *cx, JSScript *script, jsbytecode *pc,
|
|
TypeSet *target, jsid id)
|
|
{
|
|
add(cx, ArenaNew<TypeConstraintProp>(cx->compartment->pool, script, pc, target, id, true));
|
|
}
|
|
|
|
/*
|
|
* Constraints for updating the 'this' types of callees on CALLPROP/CALLELEM.
|
|
* These are derived from the types on the properties themselves, rather than
|
|
* those pushed in the 'this' slot at the call site, which allows us to retain
|
|
* correlations between the type of the 'this' object and the associated
|
|
* callee scripts at polymorphic call sites.
|
|
*/
|
|
class TypeConstraintCallProp : public TypeConstraint
|
|
{
|
|
public:
|
|
jsbytecode *callpc;
|
|
|
|
/* Property being accessed. */
|
|
jsid id;
|
|
|
|
TypeConstraintCallProp(JSScript *script, jsbytecode *callpc, jsid id)
|
|
: TypeConstraint("callprop", script), callpc(callpc), id(id)
|
|
{
|
|
JS_ASSERT(script && callpc);
|
|
}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type);
|
|
};
|
|
|
|
void
|
|
TypeSet::addCallProperty(JSContext *cx, JSScript *script, jsbytecode *pc, jsid id)
|
|
{
|
|
/*
|
|
* For calls which will go through JSOP_NEW, don't add any constraints to
|
|
* modify the 'this' types of callees. The initial 'this' value will be
|
|
* outright ignored.
|
|
*/
|
|
jsbytecode *callpc = script->analysis(cx)->getCallPC(pc);
|
|
UntrapOpcode untrap(cx, script, callpc);
|
|
if (JSOp(*callpc) == JSOP_NEW)
|
|
return;
|
|
|
|
add(cx, ArenaNew<TypeConstraintCallProp>(cx->compartment->pool, script, callpc, id));
|
|
}
|
|
|
|
/* Constraints for determining the 'this' object at sites invoked using 'new'. */
|
|
class TypeConstraintNewObject : public TypeConstraint
|
|
{
|
|
TypeFunction *fun;
|
|
TypeSet *target;
|
|
|
|
public:
|
|
TypeConstraintNewObject(JSScript *script, TypeFunction *fun, TypeSet *target)
|
|
: TypeConstraint("newObject", script), fun(fun), target(target)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type);
|
|
};
|
|
|
|
void
|
|
TypeSet::addNewObject(JSContext *cx, JSScript *script, TypeFunction *fun, TypeSet *target)
|
|
{
|
|
add(cx, ArenaNew<TypeConstraintNewObject>(cx->compartment->pool, script, fun, target));
|
|
}
|
|
|
|
/*
|
|
* 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->script), callsite(callsite)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type);
|
|
};
|
|
|
|
void
|
|
TypeSet::addCall(JSContext *cx, TypeCallsite *site)
|
|
{
|
|
add(cx, ArenaNew<TypeConstraintCall>(cx->compartment->pool, site));
|
|
}
|
|
|
|
/* Constraints for arithmetic operations. */
|
|
class TypeConstraintArith : public TypeConstraint
|
|
{
|
|
public:
|
|
/* Type set receiving the result of the arithmetic. */
|
|
TypeSet *target;
|
|
|
|
/* For addition operations, the other operand. */
|
|
TypeSet *other;
|
|
|
|
TypeConstraintArith(JSScript *script, TypeSet *target, TypeSet *other)
|
|
: TypeConstraint("arith", script), target(target), other(other)
|
|
{
|
|
JS_ASSERT(target);
|
|
}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type);
|
|
};
|
|
|
|
void
|
|
TypeSet::addArith(JSContext *cx, JSScript *script, TypeSet *target, TypeSet *other)
|
|
{
|
|
add(cx, ArenaNew<TypeConstraintArith>(cx->compartment->pool, script, target, other));
|
|
}
|
|
|
|
/* Subset constraint which transforms primitive values into appropriate objects. */
|
|
class TypeConstraintTransformThis : public TypeConstraint
|
|
{
|
|
public:
|
|
TypeSet *target;
|
|
|
|
TypeConstraintTransformThis(JSScript *script, TypeSet *target)
|
|
: TypeConstraint("transformthis", script), target(target)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type);
|
|
};
|
|
|
|
void
|
|
TypeSet::addTransformThis(JSContext *cx, JSScript *script, TypeSet *target)
|
|
{
|
|
add(cx, ArenaNew<TypeConstraintTransformThis>(cx->compartment->pool, script, target));
|
|
}
|
|
|
|
/*
|
|
* Constraint which adds a particular type to the 'this' types of all
|
|
* discovered scripted functions.
|
|
*/
|
|
class TypeConstraintPropagateThis : public TypeConstraint
|
|
{
|
|
public:
|
|
jsbytecode *callpc;
|
|
jstype type;
|
|
|
|
TypeConstraintPropagateThis(JSScript *script, jsbytecode *callpc, jstype type)
|
|
: TypeConstraint("propagatethis", script), callpc(callpc), type(type)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type);
|
|
};
|
|
|
|
void
|
|
TypeSet::addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, jstype type)
|
|
{
|
|
/* Don't add constraints when the call will be 'new' (see addCallProperty). */
|
|
jsbytecode *callpc = script->analysis(cx)->getCallPC(pc);
|
|
UntrapOpcode untrap(cx, script, callpc);
|
|
if (JSOp(*callpc) == JSOP_NEW)
|
|
return;
|
|
|
|
add(cx, ArenaNew<TypeConstraintPropagateThis>(cx->compartment->pool, script, callpc, type));
|
|
}
|
|
|
|
/* 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(JSScript *script, TypeSet *target, bool onlyNullVoid)
|
|
: TypeConstraint("filter", script), target(target), onlyNullVoid(onlyNullVoid)
|
|
{}
|
|
|
|
void 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
|
|
TypeSet::addFilterPrimitives(JSContext *cx, JSScript *script, TypeSet *target, bool onlyNullVoid)
|
|
{
|
|
add(cx, ArenaNew<TypeConstraintFilterPrimitive>(cx->compartment->pool,
|
|
script, target, onlyNullVoid));
|
|
}
|
|
|
|
void
|
|
ScriptAnalysis::pruneTypeBarriers(uint32 offset)
|
|
{
|
|
TypeBarrier **pbarrier = &getCode(offset).typeBarriers;
|
|
while (*pbarrier) {
|
|
TypeBarrier *barrier = *pbarrier;
|
|
if (barrier->target->hasType(barrier->type)) {
|
|
/* Barrier is now obsolete, it can be removed. */
|
|
*pbarrier = barrier->next;
|
|
} else {
|
|
pbarrier = &barrier->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Cheesy limit on the number of objects we will tolerate in an observed type
|
|
* set before refusing to add new type barriers for objects.
|
|
* :FIXME: this heuristic sucks, and doesn't handle calls.
|
|
*/
|
|
static const uint32 BARRIER_OBJECT_LIMIT = 10;
|
|
|
|
void ScriptAnalysis::breakTypeBarriers(JSContext *cx, uint32 offset, bool all)
|
|
{
|
|
TypeBarrier **pbarrier = &getCode(offset).typeBarriers;
|
|
while (*pbarrier) {
|
|
TypeBarrier *barrier = *pbarrier;
|
|
if (barrier->target->hasType(barrier->type) ) {
|
|
/* Barrier is now obsolete, it can be removed. */
|
|
*pbarrier = barrier->next;
|
|
} else if (all || (TypeIsObject(barrier->type) &&
|
|
barrier->target->getObjectCount() >= BARRIER_OBJECT_LIMIT)) {
|
|
/* Force removal of the barrier. */
|
|
barrier->target->addType(cx, barrier->type);
|
|
*pbarrier = barrier->next;
|
|
} else {
|
|
pbarrier = &barrier->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptAnalysis::breakTypeBarriersSSA(JSContext *cx, const SSAValue &v)
|
|
{
|
|
if (v.kind() != SSAValue::PUSHED)
|
|
return;
|
|
|
|
uint32 offset = v.pushedOffset();
|
|
if (JSOp(script->code[offset]) == JSOP_GETPROP)
|
|
breakTypeBarriersSSA(cx, poppedValue(offset, 0));
|
|
|
|
breakTypeBarriers(cx, offset, true);
|
|
}
|
|
|
|
/*
|
|
* Subset constraint for property reads and argument passing which can add type
|
|
* barriers on the read instead of passing types along.
|
|
*/
|
|
class TypeConstraintSubsetBarrier : public TypeConstraint
|
|
{
|
|
public:
|
|
jsbytecode *pc;
|
|
TypeSet *target;
|
|
|
|
TypeConstraintSubsetBarrier(JSScript *script, jsbytecode *pc, TypeSet *target)
|
|
: TypeConstraint("subsetBarrier", script), pc(pc), target(target)
|
|
{
|
|
JS_ASSERT(!target->intermediate());
|
|
}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
if (!target->hasType(type)) {
|
|
script->analysis(cx)->addTypeBarrier(cx, pc, target, type);
|
|
return;
|
|
}
|
|
|
|
target->addType(cx, type);
|
|
}
|
|
};
|
|
|
|
void
|
|
TypeSet::addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target)
|
|
{
|
|
add(cx, ArenaNew<TypeConstraintSubsetBarrier>(cx->compartment->pool, script, pc, target));
|
|
}
|
|
|
|
/*
|
|
* Constraint which marks a pushed ARGUMENTS value as unknown if the script has
|
|
* an arguments object created in the future.
|
|
*/
|
|
class TypeConstraintLazyArguments : public TypeConstraint
|
|
{
|
|
public:
|
|
jsbytecode *pc;
|
|
TypeSet *target;
|
|
|
|
TypeConstraintLazyArguments(JSScript *script, TypeSet *target)
|
|
: TypeConstraint("lazyArgs", script), target(target)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type) {}
|
|
|
|
void newObjectState(JSContext *cx, TypeObject *object, bool force)
|
|
{
|
|
if (object->hasAnyFlags(OBJECT_FLAG_CREATED_ARGUMENTS))
|
|
target->addType(cx, TYPE_UNKNOWN);
|
|
}
|
|
};
|
|
|
|
void
|
|
TypeSet::addLazyArguments(JSContext *cx, JSScript *script, TypeSet *target)
|
|
{
|
|
add(cx, ArenaNew<TypeConstraintLazyArguments>(cx->compartment->pool, script, 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(JSScript *script, TypeSet *target)
|
|
: TypeConstraint("generator", script), target(target)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
if (type == TYPE_UNKNOWN) {
|
|
target->addType(cx, TYPE_UNKNOWN);
|
|
return;
|
|
}
|
|
|
|
if (TypeIsPrimitive(type))
|
|
return;
|
|
|
|
/*
|
|
* Watch for 'for in' on Iterator and Generator objects, which can
|
|
* produce values other than strings.
|
|
*/
|
|
TypeObject *object = (TypeObject *) type;
|
|
if (object->proto) {
|
|
Class *clasp = object->proto->getClass();
|
|
if (clasp == &js_IteratorClass || clasp == &js_GeneratorClass)
|
|
target->addType(cx, TYPE_UNKNOWN);
|
|
}
|
|
}
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// TypeConstraint
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
/* Get the object to use for a property access on type. */
|
|
static inline TypeObject *
|
|
GetPropertyObject(JSContext *cx, JSScript *script, 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.
|
|
*/
|
|
TypeObject *object = NULL;
|
|
switch (type) {
|
|
|
|
case TYPE_INT32:
|
|
case TYPE_DOUBLE:
|
|
object = script->getTypeNewObject(cx, JSProto_Number);
|
|
break;
|
|
|
|
case TYPE_BOOLEAN:
|
|
object = script->getTypeNewObject(cx, JSProto_Boolean);
|
|
break;
|
|
|
|
case TYPE_STRING:
|
|
object = script->getTypeNewObject(cx, JSProto_String);
|
|
break;
|
|
|
|
default:
|
|
/* undefined, null and lazy arguments do not have properties. */
|
|
return NULL;
|
|
}
|
|
|
|
if (!object)
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return object;
|
|
}
|
|
|
|
static inline void
|
|
MarkPropertyAccessUnknown(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target)
|
|
{
|
|
if (CanHaveReadBarrier(pc))
|
|
script->analysis(cx)->addTypeBarrier(cx, pc, target, TYPE_UNKNOWN);
|
|
else
|
|
target->addType(cx, TYPE_UNKNOWN);
|
|
}
|
|
|
|
/*
|
|
* 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, JSScript *script, jsbytecode *pc, TypeObject *object,
|
|
bool assign, TypeSet *target, jsid id)
|
|
{
|
|
JS_ASSERT_IF(!target, assign);
|
|
|
|
/* Monitor assigns on the 'prototype' property. */
|
|
if (assign && id == id_prototype(cx)) {
|
|
cx->compartment->types.monitorBytecode(cx, script, pc - script->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, script, pc - script->code);
|
|
else
|
|
target->addType(cx, TYPE_UNKNOWN);
|
|
return;
|
|
}
|
|
|
|
/* Reads from objects with unknown properties are unknown, writes to such objects are ignored. */
|
|
if (object->unknownProperties()) {
|
|
if (!assign)
|
|
MarkPropertyAccessUnknown(cx, script, pc, target);
|
|
return;
|
|
}
|
|
|
|
/* Capture the effects of a standard property access. */
|
|
if (target) {
|
|
TypeSet *types = object->getProperty(cx, id, assign);
|
|
if (!types)
|
|
return;
|
|
if (assign)
|
|
target->addSubset(cx, script, types);
|
|
else if (CanHaveReadBarrier(pc))
|
|
types->addSubsetBarrier(cx, script, pc, target);
|
|
else
|
|
types->addSubset(cx, script, target);
|
|
} else {
|
|
TypeSet *readTypes = object->getProperty(cx, id, false);
|
|
TypeSet *writeTypes = object->getProperty(cx, id, true);
|
|
if (!readTypes || !writeTypes)
|
|
return;
|
|
readTypes->addArith(cx, script, writeTypes);
|
|
}
|
|
}
|
|
|
|
void
|
|
TypeConstraintProp::newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
UntrapOpcode untrap(cx, script, pc);
|
|
|
|
if (type == TYPE_UNKNOWN || (!TypeIsObject(type) && !script->global)) {
|
|
/*
|
|
* 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, script, pc - script->code);
|
|
else
|
|
MarkPropertyAccessUnknown(cx, script, pc, target);
|
|
return;
|
|
}
|
|
|
|
if (type == TYPE_LAZYARGS) {
|
|
/* Ignore cases which will be accounted for by the followEscapingArguments analysis. */
|
|
if (assign || (id != JSID_VOID && id != id_length(cx)))
|
|
return;
|
|
|
|
if (id == JSID_VOID)
|
|
MarkPropertyAccessUnknown(cx, script, pc, target);
|
|
else
|
|
target->addType(cx, TYPE_INT32);
|
|
return;
|
|
}
|
|
|
|
TypeObject *object = GetPropertyObject(cx, script, type);
|
|
if (object)
|
|
PropertyAccess(cx, script, pc, object, assign, target, id);
|
|
}
|
|
|
|
void
|
|
TypeConstraintCallProp::newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
UntrapOpcode untrap(cx, script, callpc);
|
|
|
|
/*
|
|
* For CALLPROP and CALLELEM, we need to update not just the pushed types
|
|
* but also the 'this' types of possible callees. If we can't figure out
|
|
* that set of callees, monitor the call to make sure discovered callees
|
|
* get their 'this' types updated.
|
|
*/
|
|
|
|
if (type == TYPE_UNKNOWN || (!TypeIsObject(type) && !script->global)) {
|
|
cx->compartment->types.monitorBytecode(cx, script, callpc - script->code);
|
|
return;
|
|
}
|
|
|
|
TypeObject *object = GetPropertyObject(cx, script, type);
|
|
if (object) {
|
|
if (object->unknownProperties()) {
|
|
cx->compartment->types.monitorBytecode(cx, script, callpc - script->code);
|
|
} else {
|
|
TypeSet *types = object->getProperty(cx, id, false);
|
|
if (!types)
|
|
return;
|
|
/* Bypass addPropagateThis, we already have the callpc. */
|
|
types->add(cx, ArenaNew<TypeConstraintPropagateThis>(cx->compartment->pool,
|
|
script, callpc, type));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
TypeConstraintNewObject::newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
if (type == TYPE_UNKNOWN) {
|
|
target->addType(cx, TYPE_UNKNOWN);
|
|
return;
|
|
}
|
|
|
|
if (TypeIsObject(type)) {
|
|
TypeObject *object = (TypeObject *) type;
|
|
if (object->unknownProperties()) {
|
|
target->addType(cx, TYPE_UNKNOWN);
|
|
} else {
|
|
TypeSet *newTypes = object->getProperty(cx, JSID_EMPTY, false);
|
|
if (!newTypes)
|
|
return;
|
|
newTypes->addSubset(cx, script, target);
|
|
}
|
|
} else if (!fun->script) {
|
|
/*
|
|
* This constraint should only be used for scripted functions and for
|
|
* native constructors with immutable non-primitive prototypes.
|
|
* Disregard primitives here.
|
|
*/
|
|
} else if (!fun->script->global) {
|
|
target->addType(cx, TYPE_UNKNOWN);
|
|
} else {
|
|
TypeObject *object = fun->script->getTypeNewObject(cx, JSProto_Object);
|
|
if (!object) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
target->addType(cx, (jstype) object);
|
|
}
|
|
}
|
|
|
|
void
|
|
TypeConstraintCall::newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
JSScript *script = callsite->script;
|
|
jsbytecode *pc = callsite->pc;
|
|
|
|
if (type == TYPE_UNKNOWN) {
|
|
/* Monitor calls on unknown functions. */
|
|
cx->compartment->types.monitorBytecode(cx, script, pc - script->code);
|
|
return;
|
|
}
|
|
|
|
if (!TypeIsObject(type))
|
|
return;
|
|
|
|
/* Get the function being invoked. */
|
|
TypeObject *object = (TypeObject*) type;
|
|
if (object->unknownProperties()) {
|
|
/* Unknown return value for calls on generic objects. */
|
|
cx->compartment->types.monitorBytecode(cx, script, pc - script->code);
|
|
return;
|
|
}
|
|
if (!object->isFunction) {
|
|
/*
|
|
* If a call on a non-function actually occurs, the call's result
|
|
* should be marked as unknown.
|
|
*/
|
|
return;
|
|
}
|
|
TypeFunction *function = object->asFunction();
|
|
|
|
if (!function->script) {
|
|
JS_ASSERT(function->handler && function->singleton);
|
|
|
|
/*
|
|
* When creating objects, natives may use the wrong type/prototype for
|
|
* the result if called by a frame parented to a different global
|
|
* :FIXME: bug 631135. Rather than try to model this, just mark the
|
|
* result of cross-global native calls as unknown.
|
|
*/
|
|
if (!script->global || script->global != function->singleton->getGlobal()) {
|
|
callsite->returnTypes->addType(cx, TYPE_UNKNOWN);
|
|
return;
|
|
}
|
|
|
|
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, "genericthis");
|
|
if (!thisTypes)
|
|
return;
|
|
callsite->argumentTypes[0]->addTransformThis(cx, script, thisTypes);
|
|
|
|
TypeCallsite *newSite = ArenaNew<TypeCallsite>(cx->compartment->pool,
|
|
cx, script, pc, callsite->isNew,
|
|
callsite->argumentCount - 1);
|
|
if (!newSite || (callsite->argumentCount > 1 && !newSite->argumentTypes)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
JSScript *callee = function->script;
|
|
unsigned nargs = callee->fun->nargs;
|
|
|
|
if (!callee->ensureTypeArray(cx))
|
|
return;
|
|
|
|
/* Analyze the function if we have not already done so. */
|
|
if (!callee->ranInference) {
|
|
ScriptAnalysis *calleeAnalysis = callee->analysis(cx);
|
|
if (!calleeAnalysis) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
calleeAnalysis->analyzeTypes(cx);
|
|
}
|
|
|
|
/* Add bindings for the arguments of the call. */
|
|
for (unsigned i = 0; i < callsite->argumentCount && i < nargs; i++) {
|
|
TypeSet *argTypes = callsite->argumentTypes[i];
|
|
TypeSet *types = callee->argTypes(i);
|
|
argTypes->addSubsetBarrier(cx, script, pc, types);
|
|
}
|
|
|
|
/* Add void type for any formals in the callee not supplied at the call site. */
|
|
for (unsigned i = callsite->argumentCount; i < nargs; i++) {
|
|
TypeSet *types = callee->argTypes(i);
|
|
types->addType(cx, TYPE_UNDEFINED);
|
|
}
|
|
|
|
if (callsite->isNew) {
|
|
/* Mark the callee as having been invoked with 'new'. */
|
|
callee->typeSetNewCalled(cx);
|
|
|
|
/*
|
|
* If the script does not return a value then the pushed value is the new
|
|
* object (typical case).
|
|
*/
|
|
callee->thisTypes()->addSubset(cx, script, callsite->returnTypes);
|
|
callee->returnTypes()->addFilterPrimitives(cx, script, callsite->returnTypes, false);
|
|
} else {
|
|
/*
|
|
* Add a binding for the return value of the call. We don't add a
|
|
* binding for the receiver object, as this is done with PropagateThis
|
|
* constraints added by the original JSOP_CALL* op. The type sets we
|
|
* manipulate here have lost any correlations between particular types
|
|
* in the 'this' and 'callee' sets, which we want to maintain for
|
|
* polymorphic JSOP_CALLPROP invocations.
|
|
*/
|
|
callee->returnTypes()->addSubset(cx, script, callsite->returnTypes);
|
|
}
|
|
}
|
|
|
|
void
|
|
TypeConstraintPropagateThis::newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
if (type == TYPE_UNKNOWN) {
|
|
/*
|
|
* The callee is unknown, make sure the call is monitored so we pick up
|
|
* possible this/callee correlations. This only comes into play for
|
|
* CALLPROP and CALLELEM, for other calls we are past the type barrier
|
|
* already and a TypeConstraintCall will also monitor the call.
|
|
*/
|
|
cx->compartment->types.monitorBytecode(cx, script, callpc - script->code);
|
|
return;
|
|
}
|
|
|
|
/* Ignore calls to primitives, these will go through a stub. */
|
|
if (!TypeIsObject(type))
|
|
return;
|
|
|
|
/* Ignore calls to natives, these will be handled by TypeConstraintCall. */
|
|
TypeObject *object = (TypeObject*) type;
|
|
if (object->unknownProperties() || !object->isFunction)
|
|
return;
|
|
TypeFunction *function = object->asFunction();
|
|
|
|
if (!function->script)
|
|
return;
|
|
|
|
JSScript *callee = function->script;
|
|
|
|
if (!callee->ensureTypeArray(cx))
|
|
return;
|
|
|
|
callee->thisTypes()->addType(cx, this->type);
|
|
}
|
|
|
|
void
|
|
TypeConstraintArith::newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
/*
|
|
* We only model a subset of the arithmetic behavior that is actually
|
|
* possible. The following need to be watched for at runtime:
|
|
*
|
|
* 1. Operations producing a double where no operand was a double.
|
|
* 2. Operations producing a string where no operand was a string (addition only).
|
|
* 3. Operations producing a value other than int/double/string.
|
|
*/
|
|
if (other) {
|
|
/*
|
|
* Addition operation, consider these cases:
|
|
* {int,bool} x {int,bool} -> int
|
|
* double x {int,bool,double} -> double
|
|
* string x any -> string
|
|
*/
|
|
if (other->unknown()) {
|
|
target->addType(cx, TYPE_UNKNOWN);
|
|
return;
|
|
}
|
|
switch (type) {
|
|
case TYPE_DOUBLE:
|
|
if (other->hasAnyFlag(TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL |
|
|
TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_BOOLEAN) ||
|
|
other->getObjectCount() != 0) {
|
|
target->addType(cx, TYPE_DOUBLE);
|
|
}
|
|
break;
|
|
case TYPE_STRING:
|
|
target->addType(cx, TYPE_STRING);
|
|
break;
|
|
case TYPE_UNKNOWN:
|
|
target->addType(cx, TYPE_UNKNOWN);
|
|
default:
|
|
if (other->hasAnyFlag(TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL |
|
|
TYPE_FLAG_INT32 | TYPE_FLAG_BOOLEAN) ||
|
|
other->getObjectCount() != 0) {
|
|
target->addType(cx, TYPE_INT32);
|
|
}
|
|
if (other->hasAnyFlag(TYPE_FLAG_DOUBLE))
|
|
target->addType(cx, TYPE_DOUBLE);
|
|
break;
|
|
}
|
|
} else {
|
|
switch (type) {
|
|
case TYPE_DOUBLE:
|
|
target->addType(cx, TYPE_DOUBLE);
|
|
break;
|
|
case TYPE_UNKNOWN:
|
|
target->addType(cx, TYPE_UNKNOWN);
|
|
default:
|
|
target->addType(cx, TYPE_INT32);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
TypeConstraintTransformThis::newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
if (type == TYPE_UNKNOWN || TypeIsObject(type) || script->strictModeCode) {
|
|
target->addType(cx, type);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Note: if |this| is null or undefined, the pushed value is the outer window. We
|
|
* can't use script->getGlobalType() here because it refers to the inner window.
|
|
*/
|
|
if (!script->global || type == TYPE_NULL || type == TYPE_UNDEFINED) {
|
|
target->addType(cx, TYPE_UNKNOWN);
|
|
return;
|
|
}
|
|
|
|
TypeObject *object = NULL;
|
|
switch (type) {
|
|
case TYPE_INT32:
|
|
case TYPE_DOUBLE:
|
|
object = script->getTypeNewObject(cx, JSProto_Number);
|
|
break;
|
|
case TYPE_BOOLEAN:
|
|
object = script->getTypeNewObject(cx, JSProto_Boolean);
|
|
break;
|
|
case TYPE_STRING:
|
|
object = script->getTypeNewObject(cx, JSProto_String);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (!object) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
|
|
target->addType(cx, (jstype) object);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Freeze constraints
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
/* Constraint which marks all types as pushed by some bytecode. */
|
|
class TypeConstraintPushAll : public TypeConstraint
|
|
{
|
|
public:
|
|
const jsbytecode *pc;
|
|
|
|
TypeConstraintPushAll(JSScript *script, const jsbytecode *pc)
|
|
: TypeConstraint("pushAll", script), pc(pc)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
cx->compartment->types.dynamicPush(cx, script, pc - script->code, type);
|
|
}
|
|
};
|
|
|
|
void
|
|
TypeSet::pushAllTypes(JSContext *cx, JSScript *script, const jsbytecode *pc)
|
|
{
|
|
add(cx, ArenaNew<TypeConstraintPushAll>(cx->compartment->pool, script, pc));
|
|
}
|
|
|
|
/* Constraint which triggers recompilation of a script if any type is added to a type set. */
|
|
class TypeConstraintFreeze : public TypeConstraint
|
|
{
|
|
public:
|
|
/* Whether a new type has already been added, triggering recompilation. */
|
|
bool typeAdded;
|
|
|
|
TypeConstraintFreeze(JSScript *script)
|
|
: TypeConstraint("freeze", script), typeAdded(false)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
if (typeAdded)
|
|
return;
|
|
|
|
typeAdded = true;
|
|
cx->compartment->types.addPendingRecompile(cx, script);
|
|
}
|
|
};
|
|
|
|
void
|
|
TypeSet::addFreeze(JSContext *cx)
|
|
{
|
|
add(cx, ArenaNew<TypeConstraintFreeze>(cx->compartment->pool,
|
|
cx->compartment->types.compiledScript), false);
|
|
}
|
|
|
|
void
|
|
TypeSet::Clone(JSContext *cx, TypeSet *source, ClonedTypeSet *target)
|
|
{
|
|
if (!source) {
|
|
target->typeFlags = TYPE_FLAG_UNKNOWN;
|
|
return;
|
|
}
|
|
|
|
if (cx->compartment->types.compiledScript && !source->unknown())
|
|
source->addFreeze(cx);
|
|
|
|
target->typeFlags = source->baseFlags();
|
|
target->objectCount = source->objectCount;
|
|
if (source->objectCount >= 2) {
|
|
target->objectSet = (TypeObject **) cx->calloc_(sizeof(TypeObject*) * source->objectCount);
|
|
if (!target->objectSet) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
target->objectCount = 0;
|
|
return;
|
|
}
|
|
unsigned objectCapacity = HashSetCapacity(source->objectCount);
|
|
unsigned index = 0;
|
|
for (unsigned i = 0; i < objectCapacity; i++) {
|
|
TypeObject *object = source->objectSet[i];
|
|
if (object)
|
|
target->objectSet[index++] = object;
|
|
}
|
|
JS_ASSERT(index == source->objectCount);
|
|
} else if (source->objectCount == 1) {
|
|
target->objectSet = source->objectSet;
|
|
} else {
|
|
target->objectSet = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Constraint which triggers recompilation of a script if a possible new JSValueType
|
|
* tag is realized for a type set.
|
|
*/
|
|
class TypeConstraintFreezeTypeTag : public TypeConstraint
|
|
{
|
|
public:
|
|
/*
|
|
* 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), 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->getObjectCount() >= 2)
|
|
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_LAZYARGS:
|
|
return JSVAL_TYPE_MAGIC;
|
|
default:
|
|
return JSVAL_TYPE_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
JSValueType
|
|
TypeSet::getKnownTypeTag(JSContext *cx)
|
|
{
|
|
TypeFlags flags = baseFlags();
|
|
JSValueType type;
|
|
|
|
if (objectCount)
|
|
type = flags ? JSVAL_TYPE_UNKNOWN : JSVAL_TYPE_OBJECT;
|
|
else
|
|
type = GetValueTypeFromTypeFlags(flags);
|
|
|
|
/*
|
|
* If the type set is totally empty then it will be treated as unknown,
|
|
* but we still need to record the dependency as adding a new type can give
|
|
* it a definite type tag. This is not needed if there are enough types
|
|
* that the exact tag is unknown, as it will stay unknown as more types are
|
|
* added to the set.
|
|
*/
|
|
bool empty = flags == 0 && objectCount == 0;
|
|
JS_ASSERT_IF(empty, type == JSVAL_TYPE_UNKNOWN);
|
|
|
|
if (cx->compartment->types.compiledScript && (empty || type != JSVAL_TYPE_UNKNOWN)) {
|
|
add(cx, ArenaNew<TypeConstraintFreezeTypeTag>(cx->compartment->pool,
|
|
cx->compartment->types.compiledScript), false);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
/* Constraint which triggers recompilation if an object acquires particular flags. */
|
|
class TypeConstraintFreezeObjectFlags : public TypeConstraint
|
|
{
|
|
public:
|
|
/* Flags we are watching for on this object. */
|
|
TypeObjectFlags flags;
|
|
|
|
/* Whether the object has already been marked as having one of the flags. */
|
|
bool *pmarked;
|
|
bool localMarked;
|
|
|
|
TypeConstraintFreezeObjectFlags(JSScript *script, TypeObjectFlags flags, bool *pmarked)
|
|
: TypeConstraint("freezeObjectFlags", script), flags(flags),
|
|
pmarked(pmarked), localMarked(false)
|
|
{}
|
|
|
|
TypeConstraintFreezeObjectFlags(JSScript *script, TypeObjectFlags flags)
|
|
: TypeConstraint("freezeObjectFlags", script), flags(flags),
|
|
pmarked(&localMarked), localMarked(false)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type) {}
|
|
|
|
void newObjectState(JSContext *cx, TypeObject *object, bool force)
|
|
{
|
|
if (object->hasAnyFlags(flags) && !*pmarked) {
|
|
*pmarked = true;
|
|
cx->compartment->types.addPendingRecompile(cx, script);
|
|
} else if (force) {
|
|
cx->compartment->types.addPendingRecompile(cx, script);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Constraint which triggers recompilation if any object in a type set acquire
|
|
* particular flags.
|
|
*/
|
|
class TypeConstraintFreezeObjectFlagsSet : public TypeConstraint
|
|
{
|
|
public:
|
|
TypeObjectFlags flags;
|
|
bool marked;
|
|
|
|
TypeConstraintFreezeObjectFlagsSet(JSScript *script, TypeObjectFlags flags)
|
|
: TypeConstraint("freezeObjectKindSet", script), flags(flags), marked(false)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type)
|
|
{
|
|
if (marked) {
|
|
/* Despecialized the kind we were interested in due to recompilation. */
|
|
return;
|
|
}
|
|
|
|
if (type == TYPE_UNKNOWN) {
|
|
/* Fallthrough and recompile. */
|
|
} else if (TypeIsObject(type)) {
|
|
TypeObject *object = (TypeObject *) type;
|
|
if (!object->hasAnyFlags(flags)) {
|
|
/*
|
|
* Add a constraint on the element type of the object to pick up
|
|
* changes in the object's properties.
|
|
*/
|
|
TypeSet *elementTypes = object->getProperty(cx, JSID_VOID, false);
|
|
if (!elementTypes)
|
|
return;
|
|
elementTypes->add(cx,
|
|
ArenaNew<TypeConstraintFreezeObjectFlags>(cx->compartment->pool,
|
|
script, flags, &marked), false);
|
|
return;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
marked = true;
|
|
cx->compartment->types.addPendingRecompile(cx, script);
|
|
}
|
|
};
|
|
|
|
bool
|
|
TypeSet::hasObjectFlags(JSContext *cx, TypeObjectFlags flags)
|
|
{
|
|
if (unknown())
|
|
return true;
|
|
|
|
/*
|
|
* Treat type sets containing no objects as having all object flags,
|
|
* to spare callers from having to check this.
|
|
*/
|
|
if (objectCount == 0)
|
|
return true;
|
|
|
|
unsigned count = getObjectCount();
|
|
for (unsigned i = 0; i < count; i++) {
|
|
TypeObject *object = getObject(i);
|
|
if (object && object->hasAnyFlags(flags))
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Watch for new objects of different kind, and re-traverse existing types
|
|
* in this set to add any needed FreezeArray constraints.
|
|
*/
|
|
add(cx, ArenaNew<TypeConstraintFreezeObjectFlagsSet>(cx->compartment->pool,
|
|
cx->compartment->types.compiledScript, flags));
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
TypeSet::HasObjectFlags(JSContext *cx, TypeObject *object, TypeObjectFlags flags)
|
|
{
|
|
if (object->hasAnyFlags(flags))
|
|
return true;
|
|
|
|
TypeSet *elementTypes = object->getProperty(cx, JSID_VOID, false);
|
|
if (!elementTypes)
|
|
return true;
|
|
elementTypes->add(cx,
|
|
ArenaNew<TypeConstraintFreezeObjectFlags>(cx->compartment->pool,
|
|
cx->compartment->types.compiledScript, flags), false);
|
|
return false;
|
|
}
|
|
|
|
void
|
|
FixLazyArguments(JSContext *cx, JSScript *script)
|
|
{
|
|
#ifdef JS_METHODJIT
|
|
mjit::ExpandInlineFrames(cx, FRAME_EXPAND_ALL);
|
|
#endif
|
|
|
|
/* :FIXME: handle OOM at calls here. */
|
|
ScriptAnalysis *analysis = script->analysis(cx);
|
|
if (analysis && !analysis->ranBytecode())
|
|
analysis->analyzeBytecode(cx);
|
|
if (!analysis || analysis->OOM())
|
|
return;
|
|
|
|
for (AllFramesIter iter(cx); !iter.done(); ++iter) {
|
|
StackFrame *fp = iter.fp();
|
|
if (fp->isScriptFrame() && fp->script() == script) {
|
|
JSInlinedSite *inline_;
|
|
jsbytecode *pc = fp->pc(cx, NULL, &inline_);
|
|
JS_ASSERT(!inline_);
|
|
|
|
/*
|
|
* Check locals and stack slots, assignment to individual arguments
|
|
* is treated as an escape on the arguments.
|
|
*/
|
|
Value *sp = fp->base() + analysis->getCode(pc).stackDepth;
|
|
for (Value *vp = fp->slots(); vp < sp; vp++) {
|
|
if (vp->isMagicCheck(JS_LAZY_ARGUMENTS)) {
|
|
if (!js_GetArgsValue(cx, fp, vp)) {
|
|
/* FIXME */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
ObjectStateChange(JSContext *cx, TypeObject *object, bool markingUnknown, bool force)
|
|
{
|
|
if (object->unknownProperties())
|
|
return;
|
|
|
|
/* All constraints listening to state changes are on the element types. */
|
|
TypeSet *elementTypes = object->getProperty(cx, JSID_VOID, false);
|
|
if (!elementTypes)
|
|
return;
|
|
if (markingUnknown) {
|
|
JSScript *fixArgsScript = NULL;
|
|
if (!(object->flags & OBJECT_FLAG_CREATED_ARGUMENTS) && object->isFunction) {
|
|
TypeFunction *fun = object->asFunction();
|
|
if (fun->script && fun->script->usedLazyArgs)
|
|
fixArgsScript = fun->script;
|
|
}
|
|
|
|
/* Mark as unknown after getting the element types, to avoid assertion. */
|
|
object->flags = OBJECT_FLAG_UNKNOWN_MASK;
|
|
|
|
if (fixArgsScript)
|
|
FixLazyArguments(cx, fixArgsScript);
|
|
}
|
|
|
|
TypeConstraint *constraint = elementTypes->constraintList;
|
|
while (constraint) {
|
|
constraint->newObjectState(cx, object, force);
|
|
constraint = constraint->next;
|
|
}
|
|
}
|
|
|
|
void
|
|
TypeSet::WatchObjectReallocation(JSContext *cx, JSObject *obj)
|
|
{
|
|
JS_ASSERT(obj->isGlobal() && !obj->getType()->unknownProperties());
|
|
TypeSet *types = obj->getType()->getProperty(cx, JSID_VOID, false);
|
|
if (!types)
|
|
return;
|
|
|
|
/*
|
|
* Reallocating the slots on a global object triggers an object state
|
|
* change on the object with the 'force' parameter set, so we just need
|
|
* a constraint which watches for such changes but no actual object flags.
|
|
*/
|
|
types->add(cx, ArenaNew<TypeConstraintFreezeObjectFlags>(cx->compartment->pool,
|
|
cx->compartment->types.compiledScript,
|
|
0));
|
|
}
|
|
|
|
class TypeConstraintFreezeOwnProperty : public TypeConstraint
|
|
{
|
|
public:
|
|
bool updated;
|
|
bool configurable;
|
|
|
|
TypeConstraintFreezeOwnProperty(JSScript *script, bool configurable)
|
|
: TypeConstraint("freezeOwnProperty", script),
|
|
updated(false), configurable(configurable)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type) {}
|
|
|
|
void newPropertyState(JSContext *cx, TypeSet *source)
|
|
{
|
|
if (updated)
|
|
return;
|
|
if (source->hasAnyFlag(configurable
|
|
? TYPE_FLAG_CONFIGURED_PROPERTY
|
|
: TYPE_FLAG_OWN_PROPERTY)) {
|
|
updated = true;
|
|
cx->compartment->types.addPendingRecompile(cx, script);
|
|
}
|
|
}
|
|
};
|
|
|
|
bool
|
|
TypeSet::isOwnProperty(JSContext *cx, bool configurable)
|
|
{
|
|
if (hasAnyFlag(configurable
|
|
? TYPE_FLAG_CONFIGURED_PROPERTY
|
|
: TYPE_FLAG_OWN_PROPERTY)) {
|
|
return true;
|
|
}
|
|
|
|
add(cx, ArenaNew<TypeConstraintFreezeOwnProperty>(cx->compartment->pool,
|
|
cx->compartment->types.compiledScript,
|
|
configurable), false);
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
TypeSet::knownNonEmpty(JSContext *cx)
|
|
{
|
|
if (baseFlags() != 0 || objectCount != 0)
|
|
return true;
|
|
|
|
add(cx, ArenaNew<TypeConstraintFreeze>(cx->compartment->pool,
|
|
cx->compartment->types.compiledScript), false);
|
|
|
|
return false;
|
|
}
|
|
|
|
JSObject *
|
|
TypeSet::getSingleton(JSContext *cx)
|
|
{
|
|
if (baseFlags() != 0 || objectCount != 1)
|
|
return NULL;
|
|
|
|
TypeObject *object = (TypeObject *) objectSet;
|
|
if (!object->singleton)
|
|
return NULL;
|
|
|
|
add(cx, ArenaNew<TypeConstraintFreeze>(cx->compartment->pool,
|
|
cx->compartment->types.compiledScript), false);
|
|
|
|
return object->singleton;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// TypeCompartment
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
TypeCompartment::init(JSContext *cx)
|
|
{
|
|
PodZero(this);
|
|
|
|
/*
|
|
* Initialize the empty type object. This is not threaded onto the objects list,
|
|
* will never be collected during GC, and does not have a proto or any properties
|
|
* that need to be marked. It *can* have empty shapes, which are weak references.
|
|
*/
|
|
#ifdef DEBUG
|
|
typeEmpty.name_ = JSID_VOID;
|
|
#endif
|
|
typeEmpty.flags = OBJECT_FLAG_UNKNOWN_MASK;
|
|
|
|
if (cx && cx->getRunOptions() & JSOPTION_TYPE_INFERENCE)
|
|
inferenceEnabled = true;
|
|
}
|
|
|
|
TypeObject *
|
|
TypeCompartment::newTypeObject(JSContext *cx, JSScript *script,
|
|
const char *name, const char *postfix,
|
|
bool isFunction, bool isArray, JSObject *proto)
|
|
{
|
|
#ifdef DEBUG
|
|
if (*postfix) {
|
|
unsigned len = strlen(name) + strlen(postfix) + 2;
|
|
char *newName = (char *) alloca(len);
|
|
JS_snprintf(newName, len, "%s:%s", name, postfix);
|
|
name = newName;
|
|
}
|
|
#if 0
|
|
/* Add a unique counter to the name, to distinguish objects from different globals. */
|
|
static unsigned nameCount = 0;
|
|
unsigned len = strlen(name) + 15;
|
|
char *newName = (char *) alloca(len);
|
|
JS_snprintf(newName, len, "%u:%s", ++nameCount, name);
|
|
name = newName;
|
|
#endif
|
|
JSAtom *atom = js_Atomize(cx, name, strlen(name));
|
|
if (!atom)
|
|
return NULL;
|
|
jsid id = ATOM_TO_JSID(atom);
|
|
#else
|
|
jsid id = JSID_VOID;
|
|
#endif
|
|
|
|
TypeObject *object = isFunction
|
|
? cx->new_<TypeFunction>(id, proto)
|
|
: cx->new_<TypeObject>(id, proto);
|
|
if (!object)
|
|
return NULL;
|
|
|
|
TypeObject *&objects = script ? script->typeObjects : this->objects;
|
|
object->next = objects;
|
|
objects = object;
|
|
|
|
if (!cx->typeInferenceEnabled())
|
|
object->flags = OBJECT_FLAG_UNKNOWN_MASK;
|
|
else if (!isArray)
|
|
object->flags = OBJECT_FLAG_NON_DENSE_ARRAY | OBJECT_FLAG_NON_PACKED_ARRAY;
|
|
|
|
if (proto) {
|
|
/* Thread onto the prototype's list of instance type objects. */
|
|
TypeObject *prototype = proto->getType();
|
|
if (prototype->unknownProperties())
|
|
object->flags = OBJECT_FLAG_UNKNOWN_MASK;
|
|
object->instanceNext = prototype->instanceList;
|
|
prototype->instanceList = object;
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
TypeObject *
|
|
TypeCompartment::newInitializerTypeObject(JSContext *cx, JSScript *script,
|
|
uint32 offset, bool isArray)
|
|
{
|
|
char *name = NULL;
|
|
#ifdef DEBUG
|
|
name = (char *) alloca(40);
|
|
JS_snprintf(name, 40, "#%lu:%lu:%s", script->id(), offset, isArray ? "Array" : "Object");
|
|
#endif
|
|
|
|
JSObject *proto;
|
|
JSProtoKey key = isArray ? JSProto_Array : JSProto_Object;
|
|
if (!js_GetClassPrototype(cx, script->getGlobal(), key, &proto, NULL))
|
|
return NULL;
|
|
|
|
TypeObject *res = newTypeObject(cx, script, name, "", false, isArray, proto);
|
|
if (!res)
|
|
return NULL;
|
|
|
|
if (isArray)
|
|
res->initializerArray = true;
|
|
else
|
|
res->initializerObject = true;
|
|
res->initializerOffset = offset;
|
|
|
|
jsbytecode *pc = script->code + offset;
|
|
UntrapOpcode untrap(cx, script, pc);
|
|
|
|
if (JSOp(*pc) == JSOP_NEWOBJECT) {
|
|
/*
|
|
* This object is always constructed the same way and will not be
|
|
* observed by other code before all properties have been added. Mark
|
|
* all the properties as definite properties of the object.
|
|
*/
|
|
JSObject *baseobj = script->getObject(GET_SLOTNO(pc));
|
|
|
|
if (!res->addDefiniteProperties(cx, baseobj))
|
|
return NULL;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static inline jsid
|
|
GetAtomId(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset)
|
|
{
|
|
unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, offset);
|
|
return MakeTypeId(cx, ATOM_TO_JSID(script->getAtom(index)));
|
|
}
|
|
|
|
static inline jsid
|
|
GetGlobalId(JSContext *cx, JSScript *script, const jsbytecode *pc)
|
|
{
|
|
unsigned index = GET_SLOTNO(pc);
|
|
return MakeTypeId(cx, ATOM_TO_JSID(script->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);
|
|
}
|
|
|
|
bool
|
|
types::UseNewType(JSContext *cx, JSScript *script, jsbytecode *pc)
|
|
{
|
|
JS_ASSERT(cx->typeInferenceEnabled());
|
|
|
|
UntrapOpcode untrap(cx, script, pc);
|
|
|
|
/*
|
|
* Make a heuristic guess at a use of JSOP_NEW that the constructed object
|
|
* should have a fresh type object. We do this when the NEW is immediately
|
|
* followed by a simple assignment to an object's .prototype field.
|
|
* This is designed to catch common patterns for subclassing in JS:
|
|
*
|
|
* function Super() { ... }
|
|
* function Sub1() { ... }
|
|
* function Sub2() { ... }
|
|
*
|
|
* Sub1.prototype = new Super();
|
|
* Sub2.prototype = new Super();
|
|
*
|
|
* Using distinct type objects for the particular prototypes of Sub1 and
|
|
* Sub2 lets us continue to distinguish the two subclasses and any extra
|
|
* properties added to those prototype objects.
|
|
*/
|
|
if (JSOp(*pc) != JSOP_NEW)
|
|
return false;
|
|
pc += JSOP_NEW_LENGTH;
|
|
if (JSOp(*pc) == JSOP_SETPROP) {
|
|
jsid id = GetAtomId(cx, script, pc, 0);
|
|
if (id == id_prototype(cx))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
TypeCompartment::growPendingArray(JSContext *cx)
|
|
{
|
|
unsigned newCapacity = js::Max(unsigned(100), pendingCapacity * 2);
|
|
PendingWork *newArray = (PendingWork *) js::OffTheBooks::calloc_(newCapacity * sizeof(PendingWork));
|
|
if (!newArray) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
|
|
memcpy(newArray, pendingArray, pendingCount * sizeof(PendingWork));
|
|
cx->free_(pendingArray);
|
|
|
|
pendingArray = newArray;
|
|
pendingCapacity = newCapacity;
|
|
}
|
|
|
|
void
|
|
TypeCompartment::dynamicCall(JSContext *cx, JSObject *callee,
|
|
const js::CallArgs &args, bool constructing)
|
|
{
|
|
unsigned nargs = callee->getFunctionPrivate()->nargs;
|
|
JSScript *script = callee->getFunctionPrivate()->script();
|
|
|
|
if (!script->ensureTypeArray(cx))
|
|
return;
|
|
|
|
if (constructing) {
|
|
script->typeSetNewCalled(cx);
|
|
} else {
|
|
jstype type = GetValueType(cx, args.thisv());
|
|
script->typeSetThis(cx, type);
|
|
}
|
|
|
|
/*
|
|
* Add constraints going up to the minimum of the actual and formal count.
|
|
* If there are more actuals than formals the later values can only be
|
|
* accessed through the arguments object, which is monitored.
|
|
*/
|
|
unsigned arg = 0;
|
|
for (; arg < args.argc() && arg < nargs; arg++)
|
|
script->typeSetArgument(cx, arg, args[arg]);
|
|
|
|
/* Watch for fewer actuals than formals to the call. */
|
|
for (; arg < nargs; arg++)
|
|
script->typeSetArgument(cx, arg, UndefinedValue());
|
|
}
|
|
|
|
/* Intermediate type information for a dynamic type pushed in a script. */
|
|
class TypeIntermediatePushed : public TypeIntermediate
|
|
{
|
|
uint32 offset;
|
|
jstype type;
|
|
|
|
public:
|
|
TypeIntermediatePushed(uint32 offset, jstype type)
|
|
: offset(offset), type(type)
|
|
{}
|
|
|
|
void replay(JSContext *cx, JSScript *script)
|
|
{
|
|
TypeSet *pushed = script->analysis(cx)->pushedTypes(offset);
|
|
pushed->addType(cx, type);
|
|
}
|
|
|
|
bool hasDynamicResult(uint32 offset, jstype type) {
|
|
return this->offset == offset && this->type == type;
|
|
}
|
|
|
|
bool sweep(JSContext *cx, JSCompartment *compartment)
|
|
{
|
|
if (!TypeIsObject(type))
|
|
return true;
|
|
|
|
TypeObject *object = (TypeObject *) type;
|
|
if (object->marked)
|
|
return true;
|
|
|
|
if (object->unknownProperties()) {
|
|
type = (jstype) &compartment->types.typeEmpty;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
void
|
|
TypeCompartment::dynamicPush(JSContext *cx, JSScript *script, uint32 offset, jstype type)
|
|
{
|
|
JS_ASSERT(cx->typeInferenceEnabled());
|
|
AutoEnterTypeInference enter(cx);
|
|
|
|
jsbytecode *pc = script->code + offset;
|
|
UntrapOpcode untrap(cx, script, pc);
|
|
|
|
/* Directly update associated type sets for applicable bytecodes. */
|
|
if (CanHaveReadBarrier(pc)) {
|
|
TypeSet *types = script->bytecodeTypes(pc);
|
|
if (!types->hasType(type)) {
|
|
InferSpew(ISpewOps, "externalType: monitorResult #%u:%05u: %s",
|
|
script->id(), offset, TypeString(type));
|
|
types->addType(cx, type);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* 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 TYPE_DOUBLE to
|
|
* the pushed value affect that type. We only handle inc/dec operations
|
|
* that do not have an object lvalue; INCNAME/INCPROP/INCELEM and friends
|
|
* should call addTypeProperty to reflect the property change.
|
|
*/
|
|
JSOp op = JSOp(*pc);
|
|
const JSCodeSpec *cs = &js_CodeSpec[op];
|
|
if (cs->format & (JOF_INC | JOF_DEC)) {
|
|
switch (op) {
|
|
case JSOP_INCGNAME:
|
|
case JSOP_DECGNAME:
|
|
case JSOP_GNAMEINC:
|
|
case JSOP_GNAMEDEC: {
|
|
jsid id = GetAtomId(cx, script, pc, 0);
|
|
TypeObject *global = script->getGlobalType();
|
|
if (!global->unknownProperties()) {
|
|
TypeSet *types = global->getProperty(cx, id, true);
|
|
if (!types)
|
|
break;
|
|
types->addType(cx, type);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JSOP_INCLOCAL:
|
|
case JSOP_DECLOCAL:
|
|
case JSOP_LOCALINC:
|
|
case JSOP_LOCALDEC:
|
|
case JSOP_INCARG:
|
|
case JSOP_DECARG:
|
|
case JSOP_ARGINC:
|
|
case JSOP_ARGDEC: {
|
|
/*
|
|
* Just mark the slot's type as holding the new type. This captures
|
|
* the effect if the slot is not being tracked, and if the slot
|
|
* doesn't escape we will update the pushed types below to capture
|
|
* the slot's value after this write.
|
|
*/
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
if (slot < TotalSlots(script)) {
|
|
TypeSet *types = script->slotTypes(slot);
|
|
types->addType(cx, type);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:;
|
|
}
|
|
}
|
|
|
|
if (script->hasAnalysis() && script->analysis(cx)->ranInference()) {
|
|
/*
|
|
* If the pushed set already has this type, we don't need to ensure
|
|
* there is a TypeIntermediate. Either there already is one, or the
|
|
* type could be determined from the script's other input type sets.
|
|
*/
|
|
TypeSet *pushed = script->analysis(cx)->pushedTypes(offset, 0);
|
|
if (pushed->hasType(type))
|
|
return;
|
|
} else {
|
|
/* Scan all intermediate types on the script to check for a dupe. */
|
|
TypeIntermediate *result, **presult = &script->intermediateTypes;
|
|
while (*presult) {
|
|
result = *presult;
|
|
if (result->hasDynamicResult(offset, type)) {
|
|
if (presult != &script->intermediateTypes) {
|
|
/* Move to the head of the list, maintain LRU order. */
|
|
*presult = result->next;
|
|
result->next = script->intermediateTypes;
|
|
script->intermediateTypes = result;
|
|
}
|
|
return;
|
|
}
|
|
presult = &result->next;
|
|
}
|
|
}
|
|
|
|
InferSpew(ISpewOps, "externalType: monitorResult #%u:%05u: %s",
|
|
script->id(), offset, TypeString(type));
|
|
|
|
TypeIntermediatePushed *result = cx->new_<TypeIntermediatePushed>(offset, type);
|
|
if (!result) {
|
|
setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
script->addIntermediateType(result);
|
|
|
|
if (script->hasAnalysis() && script->analysis(cx)->ranInference()) {
|
|
TypeSet *pushed = script->analysis(cx)->pushedTypes(offset, 0);
|
|
pushed->addType(cx, type);
|
|
} else if (script->ranInference) {
|
|
/* Any new dynamic result triggers reanalysis and recompilation. */
|
|
ScriptAnalysis *analysis = script->analysis(cx);
|
|
if (!analysis) {
|
|
setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
analysis->analyzeTypes(cx);
|
|
}
|
|
|
|
/* Trigger recompilation of any inline callers. */
|
|
if (script->fun)
|
|
ObjectStateChange(cx, script->fun->getType(), false, true);
|
|
}
|
|
|
|
void
|
|
TypeCompartment::processPendingRecompiles(JSContext *cx)
|
|
{
|
|
/* Steal the list of scripts to recompile, else we will try to recursively recompile them. */
|
|
Vector<JSScript*> *pending = pendingRecompiles;
|
|
pendingRecompiles = NULL;
|
|
|
|
JS_ASSERT(!pending->empty());
|
|
|
|
#ifdef JS_METHODJIT
|
|
|
|
mjit::ExpandInlineFrames(cx, true);
|
|
|
|
for (unsigned i = 0; i < pending->length(); i++) {
|
|
JSScript *script = (*pending)[i];
|
|
mjit::Recompiler recompiler(cx, script);
|
|
if (script->hasJITCode())
|
|
recompiler.recompile();
|
|
}
|
|
|
|
#endif /* JS_METHODJIT */
|
|
|
|
cx->delete_(pending);
|
|
}
|
|
|
|
void
|
|
TypeCompartment::setPendingNukeTypes(JSContext *cx)
|
|
{
|
|
JS_ASSERT(cx->compartment->activeInference);
|
|
if (!pendingNukeTypes) {
|
|
js_ReportOutOfMemory(cx);
|
|
pendingNukeTypes = true;
|
|
}
|
|
}
|
|
|
|
void
|
|
TypeCompartment::nukeTypes(JSContext *cx)
|
|
{
|
|
JSCompartment *compartment = cx->compartment;
|
|
JS_ASSERT(this == &compartment->types);
|
|
|
|
/*
|
|
* This is the usual response if we encounter an OOM while adding a type
|
|
* or resolving type constraints. Reset the compartment to not use type
|
|
* inference, and recompile all scripts.
|
|
*
|
|
* Because of the nature of constraint-based analysis (add constraints, and
|
|
* iterate them until reaching a fixpoint), we can't undo an add of a type set,
|
|
* and merely aborting the operation which triggered the add will not be
|
|
* sufficient for correct behavior as we will be leaving the types in an
|
|
* inconsistent state.
|
|
*/
|
|
JS_ASSERT(pendingNukeTypes);
|
|
if (pendingRecompiles) {
|
|
cx->free_(pendingRecompiles);
|
|
pendingRecompiles = NULL;
|
|
}
|
|
|
|
/*
|
|
* We may or may not be under the GC. In either case don't allocate, and
|
|
* acquire the GC lock so we can update inferenceEnabled for all contexts.
|
|
*/
|
|
|
|
#ifdef JS_THREADSAFE
|
|
Maybe<AutoLockGC> maybeLock;
|
|
if (!cx->runtime->gcMarkAndSweep)
|
|
maybeLock.construct(cx->runtime);
|
|
#endif
|
|
|
|
inferenceEnabled = false;
|
|
|
|
/* Update the cached inferenceEnabled bit in all contexts. */
|
|
for (JSCList *cl = cx->runtime->contextList.next;
|
|
cl != &cx->runtime->contextList;
|
|
cl = cl->next) {
|
|
JSContext *cx = js_ContextFromLinkField(cl);
|
|
cx->setCompartment(cx->compartment);
|
|
}
|
|
|
|
#ifdef JS_METHODJIT
|
|
|
|
mjit::ExpandInlineFrames(cx, true);
|
|
|
|
/* Throw away all JIT code in the compartment, but leave everything else alone. */
|
|
for (JSCList *cursor = compartment->scripts.next;
|
|
cursor != &compartment->scripts;
|
|
cursor = cursor->next) {
|
|
JSScript *script = reinterpret_cast<JSScript *>(cursor);
|
|
if (script->hasJITCode()) {
|
|
mjit::Recompiler recompiler(cx, script);
|
|
recompiler.recompile();
|
|
}
|
|
}
|
|
|
|
#endif /* JS_METHODJIT */
|
|
|
|
}
|
|
|
|
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 = cx->new_< Vector<JSScript*> >(cx);
|
|
if (!pendingRecompiles) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (unsigned i = 0; i < pendingRecompiles->length(); i++) {
|
|
if (script == (*pendingRecompiles)[i])
|
|
return;
|
|
}
|
|
|
|
if (!pendingRecompiles->append(script)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static inline bool
|
|
MonitorResultUnknown(JSOp op)
|
|
{
|
|
/*
|
|
* Opcodes which can be monitored and whose result should be marked as
|
|
* unknown when doing so. :XXX: should use type barriers at calls.
|
|
*/
|
|
switch (op) {
|
|
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_EVAL:
|
|
case JSOP_FUNCALL:
|
|
case JSOP_FUNAPPLY:
|
|
case JSOP_NEW:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void
|
|
TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32 offset)
|
|
{
|
|
ScriptAnalysis *analysis = script->analysis(cx);
|
|
|
|
if (analysis->getCode(offset).monitoredTypes)
|
|
return;
|
|
|
|
jsbytecode *pc = script->code + offset;
|
|
UntrapOpcode untrap(cx, script, pc);
|
|
|
|
/*
|
|
* We may end up monitoring opcodes before even analyzing them, as we can
|
|
* peek forward in CALLPROP and CALLELEM ops. Don't add the unknown result
|
|
* yet in this case, we will do so when analyzing the opcode.
|
|
*/
|
|
if (MonitorResultUnknown(JSOp(*pc)) && analysis->hasPushedTypes(pc))
|
|
analysis->addPushedType(cx, offset, 0, TYPE_UNKNOWN);
|
|
|
|
InferSpew(ISpewOps, "addMonitorNeeded: #%u:%05u", script->id(), offset);
|
|
|
|
script->analysis(cx)->getCode(offset).monitoredTypes = true;
|
|
|
|
if (script->hasJITCode())
|
|
cx->compartment->types.addPendingRecompile(cx, script);
|
|
|
|
/* Trigger recompilation of any inline callers. */
|
|
if (script->fun)
|
|
ObjectStateChange(cx, script->fun->getType(), false, true);
|
|
}
|
|
|
|
void
|
|
ScriptAnalysis::addTypeBarrier(JSContext *cx, const jsbytecode *pc, TypeSet *target, jstype type)
|
|
{
|
|
Bytecode &code = getCode(pc);
|
|
|
|
if (TypeIsObject(type) && target->getObjectCount() >= BARRIER_OBJECT_LIMIT) {
|
|
/* Ignore this barrier, just add the type to the target. */
|
|
target->addType(cx, type);
|
|
return;
|
|
}
|
|
|
|
if (!code.typeBarriers) {
|
|
/*
|
|
* Adding type barriers at a bytecode which did not have them before
|
|
* will trigger recompilation. If there were already type barriers,
|
|
* however, do not trigger recompilation (the script will be recompiled
|
|
* if any of the barriers is ever violated).
|
|
*/
|
|
cx->compartment->types.addPendingRecompile(cx, script);
|
|
|
|
/* Trigger recompilation of any inline callers. */
|
|
if (script->fun)
|
|
ObjectStateChange(cx, script->fun->getType(), false, true);
|
|
}
|
|
|
|
InferSpew(ISpewOps, "typeBarrier: #%u:%05u: T%p %s",
|
|
script->id(), pc - script->code, target, TypeString(type));
|
|
|
|
TypeBarrier *barrier = ArenaNew<TypeBarrier>(cx->compartment->pool);
|
|
barrier->target = target;
|
|
barrier->type = type;
|
|
|
|
barrier->next = code.typeBarriers;
|
|
code.typeBarriers = barrier;
|
|
}
|
|
|
|
void
|
|
TypeCompartment::print(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->hasAnalysis() && script->analysis(cx)->ranInference())
|
|
script->analysis(cx)->printTypes(cx);
|
|
TypeObject *object = script->typeObjects;
|
|
while (object) {
|
|
object->print(cx);
|
|
object = object->next;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
TypeObject *object = objects;
|
|
while (object) {
|
|
object->print(cx);
|
|
object = object->next;
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// TypeCompartment tables
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* The arrayTypeTable and objectTypeTable are per-compartment tables for making
|
|
* common type objects to model the contents of large script singletons and
|
|
* JSON objects. These are vanilla Arrays and native Objects, so we distinguish
|
|
* the types of different ones by looking at the types of their properties.
|
|
*
|
|
* All singleton/JSON arrays which have the same prototype, are homogenous and
|
|
* of the same element type will share a type object. All singleton/JSON
|
|
* objects which have the same shape and property types will also share a type
|
|
* object. We don't try to collate arrays or objects that have type mismatches.
|
|
*/
|
|
|
|
static inline bool
|
|
NumberTypes(jstype a, jstype b)
|
|
{
|
|
return (a == TYPE_INT32 || a == TYPE_DOUBLE) && (b == TYPE_INT32 || b == TYPE_DOUBLE);
|
|
}
|
|
|
|
struct types::ArrayTableKey
|
|
{
|
|
jstype type;
|
|
JSObject *proto;
|
|
|
|
typedef ArrayTableKey Lookup;
|
|
|
|
static inline uint32 hash(const ArrayTableKey &v) {
|
|
return (uint32) (v.type ^ ((uint32)(size_t)v.proto >> 2));
|
|
}
|
|
|
|
static inline bool match(const ArrayTableKey &v1, const ArrayTableKey &v2) {
|
|
return v1.type == v2.type && v1.proto == v2.proto;
|
|
}
|
|
};
|
|
|
|
void
|
|
TypeCompartment::fixArrayType(JSContext *cx, JSObject *obj)
|
|
{
|
|
AutoEnterTypeInference enter(cx);
|
|
|
|
if (!arrayTypeTable) {
|
|
arrayTypeTable = cx->new_<ArrayTypeTable>();
|
|
if (!arrayTypeTable || !arrayTypeTable->init()) {
|
|
arrayTypeTable = NULL;
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If the array is of homogenous type, pick a type object which will be
|
|
* shared with all other singleton/JSON arrays of the same type.
|
|
* If the array is heterogenous, keep the existing type object, which has
|
|
* unknown properties.
|
|
*/
|
|
JS_ASSERT(obj->isPackedDenseArray());
|
|
|
|
unsigned len = obj->getDenseArrayInitializedLength();
|
|
if (len == 0)
|
|
return;
|
|
|
|
jstype type = GetValueType(cx, obj->getDenseArrayElement(0));
|
|
|
|
for (unsigned i = 1; i < len; i++) {
|
|
jstype ntype = GetValueType(cx, obj->getDenseArrayElement(i));
|
|
if (ntype != type) {
|
|
if (NumberTypes(type, ntype))
|
|
type = TYPE_DOUBLE;
|
|
else
|
|
return;
|
|
}
|
|
}
|
|
|
|
ArrayTableKey key;
|
|
key.type = type;
|
|
key.proto = obj->getProto();
|
|
ArrayTypeTable::AddPtr p = arrayTypeTable->lookupForAdd(key);
|
|
|
|
if (p) {
|
|
obj->setType(p->value);
|
|
} else {
|
|
char *name = NULL;
|
|
#ifdef DEBUG
|
|
static unsigned count = 0;
|
|
name = (char *) alloca(20);
|
|
JS_snprintf(name, 20, "TableArray:%u", ++count);
|
|
#endif
|
|
|
|
TypeObject *objType = newTypeObject(cx, NULL, name, "", false, true, obj->getProto());
|
|
if (!objType) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
obj->setType(objType);
|
|
|
|
cx->addTypePropertyId(objType, JSID_VOID, type);
|
|
|
|
if (!arrayTypeTable->relookupOrAdd(p, key, objType)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* N.B. We could also use the initial shape of the object (before its type is
|
|
* fixed) as the key in the object table, but since all references in the table
|
|
* are weak the hash entries would usually be collected on GC even if objects
|
|
* with the new type/shape are still live.
|
|
*/
|
|
struct types::ObjectTableKey
|
|
{
|
|
jsid *ids;
|
|
uint32 nslots;
|
|
uint32 nfixed;
|
|
JSObject *proto;
|
|
|
|
typedef JSObject * Lookup;
|
|
|
|
static inline uint32 hash(JSObject *obj) {
|
|
return (uint32) (JSID_BITS(obj->lastProperty()->propid) ^
|
|
obj->slotSpan() ^ obj->numFixedSlots() ^
|
|
((uint32)(size_t)obj->getProto() >> 2));
|
|
}
|
|
|
|
static inline bool match(const ObjectTableKey &v, JSObject *obj) {
|
|
if (obj->slotSpan() != v.nslots ||
|
|
obj->numFixedSlots() != v.nfixed ||
|
|
obj->getProto() != v.proto) {
|
|
return false;
|
|
}
|
|
const Shape *shape = obj->lastProperty();
|
|
while (!JSID_IS_EMPTY(shape->propid)) {
|
|
if (shape->propid != v.ids[shape->slot])
|
|
return false;
|
|
shape = shape->previous();
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
struct types::ObjectTableEntry
|
|
{
|
|
TypeObject *object;
|
|
Shape *newShape;
|
|
jstype *types;
|
|
};
|
|
|
|
void
|
|
TypeCompartment::fixObjectType(JSContext *cx, JSObject *obj)
|
|
{
|
|
AutoEnterTypeInference enter(cx);
|
|
|
|
if (!objectTypeTable) {
|
|
objectTypeTable = cx->new_<ObjectTypeTable>();
|
|
if (!objectTypeTable || !objectTypeTable->init()) {
|
|
objectTypeTable = NULL;
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Use the same type object for all singleton/JSON arrays with the same
|
|
* base shape, i.e. the same fields written in the same order. If there
|
|
* is a type mismatch with previous objects of the same shape, use the
|
|
* generic unknown type.
|
|
*/
|
|
JS_ASSERT(obj->isObject());
|
|
|
|
if (obj->slotSpan() == 0 || obj->inDictionaryMode())
|
|
return;
|
|
|
|
ObjectTypeTable::AddPtr p = objectTypeTable->lookupForAdd(obj);
|
|
const Shape *baseShape = obj->lastProperty();
|
|
|
|
if (p) {
|
|
/* The lookup ensures the shape matches, now check that the types match. */
|
|
jstype *types = p->value.types;
|
|
for (unsigned i = 0; i < obj->slotSpan(); i++) {
|
|
jstype ntype = GetValueType(cx, obj->getSlot(i));
|
|
if (ntype != types[i]) {
|
|
if (NumberTypes(ntype, types[i])) {
|
|
if (types[i] == TYPE_INT32) {
|
|
types[i] = TYPE_DOUBLE;
|
|
const Shape *shape = baseShape;
|
|
while (!JSID_IS_EMPTY(shape->propid)) {
|
|
if (shape->slot == i) {
|
|
cx->addTypePropertyId(p->value.object, shape->propid, TYPE_DOUBLE);
|
|
break;
|
|
}
|
|
shape = shape->previous();
|
|
}
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
obj->setTypeAndShape(p->value.object, p->value.newShape);
|
|
} else {
|
|
/*
|
|
* Make a new type to use, and regenerate a new shape to go with it.
|
|
* Shapes are rooted at the empty shape for the object's type, so we
|
|
* can't change the type without changing the shape.
|
|
*/
|
|
JSObject *xobj = NewBuiltinClassInstance(cx, &js_ObjectClass,
|
|
(gc::FinalizeKind) obj->finalizeKind());
|
|
if (!xobj) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
AutoObjectRooter xvr(cx, xobj);
|
|
|
|
char *name = NULL;
|
|
#ifdef DEBUG
|
|
static unsigned count = 0;
|
|
name = (char *) alloca(20);
|
|
JS_snprintf(name, 20, "TableObject:%u", ++count);
|
|
#endif
|
|
|
|
TypeObject *objType = newTypeObject(cx, NULL, name, "", false, false, obj->getProto());
|
|
if (!objType) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
xobj->setType(objType);
|
|
|
|
jsid *ids = (jsid *) cx->calloc_(obj->slotSpan() * sizeof(jsid));
|
|
if (!ids) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
|
|
jstype *types = (jstype *) cx->calloc_(obj->slotSpan() * sizeof(jstype));
|
|
if (!types) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
|
|
const Shape *shape = baseShape;
|
|
while (!JSID_IS_EMPTY(shape->propid)) {
|
|
ids[shape->slot] = shape->propid;
|
|
types[shape->slot] = GetValueType(cx, obj->getSlot(shape->slot));
|
|
cx->addTypePropertyId(objType, shape->propid, types[shape->slot]);
|
|
shape = shape->previous();
|
|
}
|
|
|
|
/* Construct the new shape. */
|
|
for (unsigned i = 0; i < obj->slotSpan(); i++) {
|
|
if (!DefineNativeProperty(cx, xobj, ids[i], UndefinedValue(), NULL, NULL,
|
|
JSPROP_ENUMERATE, 0, 0, DNP_SKIP_TYPE)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
}
|
|
JS_ASSERT(!xobj->inDictionaryMode());
|
|
const Shape *newShape = xobj->lastProperty();
|
|
|
|
if (!objType->addDefiniteProperties(cx, xobj))
|
|
return;
|
|
|
|
ObjectTableKey key;
|
|
key.ids = ids;
|
|
key.nslots = obj->slotSpan();
|
|
key.nfixed = obj->numFixedSlots();
|
|
key.proto = obj->getProto();
|
|
JS_ASSERT(ObjectTableKey::match(key, obj));
|
|
|
|
ObjectTableEntry entry;
|
|
entry.object = objType;
|
|
entry.newShape = (Shape *) newShape;
|
|
entry.types = types;
|
|
|
|
p = objectTypeTable->lookupForAdd(obj);
|
|
if (!objectTypeTable->add(p, key, entry)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
|
|
obj->setTypeAndShape(objType, newShape);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// TypeObject
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
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->types.addBaseSubset(cx, object, &p->types);
|
|
if (object->instanceList)
|
|
object->storeToInstances(cx, base);
|
|
object = object->instanceNext;
|
|
}
|
|
}
|
|
|
|
void
|
|
TypeObject::getFromPrototypes(JSContext *cx, Property *base)
|
|
{
|
|
JSObject *obj = proto;
|
|
while (obj) {
|
|
TypeObject *object = obj->getType();
|
|
Property *p =
|
|
HashSetLookup<jsid,Property,Property>(object->propertySet, object->propertyCount, base->id);
|
|
if (p)
|
|
p->types.addBaseSubset(cx, this, &base->types);
|
|
obj = obj->getProto();
|
|
}
|
|
}
|
|
|
|
void
|
|
TypeObject::splicePrototype(JSContext *cx, JSObject *proto)
|
|
{
|
|
/*
|
|
* For singleton types representing only a single JSObject, the proto
|
|
* can be rearranged as needed without destroying type information for
|
|
* the old or new types. Note that type constraints propagating properties
|
|
* from the old prototype are not removed.
|
|
*/
|
|
JS_ASSERT(singleton);
|
|
|
|
if (this->proto) {
|
|
/* Unlink from existing proto. */
|
|
TypeObject **plist = &this->proto->getType()->instanceList;
|
|
while (*plist != this)
|
|
plist = &(*plist)->instanceNext;
|
|
*plist = this->instanceNext;
|
|
}
|
|
|
|
this->proto = proto;
|
|
|
|
/* Link with the new proto. */
|
|
this->instanceNext = proto->getType()->instanceList;
|
|
proto->getType()->instanceList = this;
|
|
|
|
if (!cx->typeInferenceEnabled())
|
|
return;
|
|
|
|
AutoEnterTypeInference enter(cx);
|
|
|
|
if (proto->getType()->unknownProperties()) {
|
|
markUnknown(cx);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Update properties on this type with any shared with the prototype.
|
|
* :FIXME: do this for instances of this object too.
|
|
*/
|
|
unsigned count = getPropertyCount();
|
|
for (unsigned i = 0; i < count; i++) {
|
|
Property *prop = getProperty(i);
|
|
if (prop && !JSID_IS_EMPTY(prop->id))
|
|
getFromPrototypes(cx, prop);
|
|
}
|
|
}
|
|
|
|
bool
|
|
TypeObject::addProperty(JSContext *cx, jsid id, Property **pprop)
|
|
{
|
|
JS_ASSERT(!*pprop);
|
|
Property *base = cx->new_<Property>(id);
|
|
if (!base) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return false;
|
|
}
|
|
|
|
*pprop = base;
|
|
|
|
InferSpew(ISpewOps, "typeSet: T%p property %s %s",
|
|
&base->types, name(), TypeIdString(id));
|
|
|
|
if (!JSID_IS_EMPTY(id)) {
|
|
/* Check all transitive instances for this property. */
|
|
if (instanceList)
|
|
storeToInstances(cx, base);
|
|
|
|
/* Pull in this property from all prototypes up the chain. */
|
|
getFromPrototypes(cx, base);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
TypeObject::addDefiniteProperties(JSContext *cx, JSObject *obj)
|
|
{
|
|
if (unknownProperties())
|
|
return true;
|
|
|
|
/*
|
|
* Mark all properties of obj as definite properties of this type. Return
|
|
* false if there is a setter/getter for any of the properties in the
|
|
* type's prototype.
|
|
*/
|
|
AutoEnterTypeInference enter(cx);
|
|
|
|
const Shape *shape = obj->lastProperty();
|
|
while (!JSID_IS_EMPTY(shape->propid)) {
|
|
jsid id = MakeTypeId(cx, shape->propid);
|
|
if (!JSID_IS_VOID(id) && obj->isFixedSlot(shape->slot) &&
|
|
shape->slot <= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) {
|
|
TypeSet *types = getProperty(cx, id, true);
|
|
if (!types)
|
|
return false;
|
|
types->setDefinite(shape->slot);
|
|
}
|
|
shape = shape->previous();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
TypeObject::setFlags(JSContext *cx, TypeObjectFlags flags)
|
|
{
|
|
JS_ASSERT(cx->compartment->activeInference);
|
|
JS_ASSERT((this->flags & flags) != flags);
|
|
|
|
JSScript *fixArgsScript = NULL;
|
|
if ((flags & ~this->flags & OBJECT_FLAG_CREATED_ARGUMENTS) && isFunction) {
|
|
TypeFunction *fun = asFunction();
|
|
if (fun->script && fun->script->usedLazyArgs)
|
|
fixArgsScript = fun->script;
|
|
}
|
|
|
|
this->flags |= flags;
|
|
|
|
if (fixArgsScript)
|
|
FixLazyArguments(cx, fixArgsScript);
|
|
|
|
InferSpew(ISpewOps, "%s: setFlags %u", name(), flags);
|
|
|
|
ObjectStateChange(cx, this, false, false);
|
|
}
|
|
|
|
void
|
|
TypeObject::markUnknown(JSContext *cx)
|
|
{
|
|
JS_ASSERT(cx->compartment->activeInference);
|
|
JS_ASSERT(!unknownProperties());
|
|
|
|
InferSpew(ISpewOps, "UnknownProperties: %s", name());
|
|
|
|
ObjectStateChange(cx, this, true, true);
|
|
|
|
/* Mark existing instances as unknown. */
|
|
|
|
TypeObject *instance = instanceList;
|
|
while (instance) {
|
|
if (!instance->unknownProperties())
|
|
instance->markUnknown(cx);
|
|
instance = instance->instanceNext;
|
|
}
|
|
|
|
/*
|
|
* Existing constraints may have already been added to this object, which we need
|
|
* to do the right thing for. We can't ensure that we will mark all unknown
|
|
* objects before they have been accessed, as the __proto__ of a known object
|
|
* could be dynamically set to an unknown object, and we can decide to ignore
|
|
* properties of an object during analysis (i.e. hashmaps). Adding unknown for
|
|
* any properties accessed already accounts for possible values read from them.
|
|
*/
|
|
|
|
unsigned count = getPropertyCount();
|
|
for (unsigned i = 0; i < count; i++) {
|
|
Property *prop = getProperty(i);
|
|
if (prop) {
|
|
prop->types.addType(cx, TYPE_UNKNOWN);
|
|
prop->types.setOwnProperty(cx, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
TypeObject::clearNewScript(JSContext *cx)
|
|
{
|
|
JS_ASSERT(!newScriptCleared);
|
|
newScriptCleared = true;
|
|
|
|
/*
|
|
* It is possible for the object to not have a new script yet but to have
|
|
* one added in the future. When analyzing properties of new scripts we mix
|
|
* in adding constraints to trigger clearNewScript with changes to the
|
|
* type sets themselves (from breakTypeBarriers). It is possible that we
|
|
* could trigger one of these constraints before AnalyzeNewScriptProperties
|
|
* has finished, in which case we want to make sure that call fails.
|
|
*/
|
|
if (!newScript)
|
|
return;
|
|
|
|
AutoEnterTypeInference enter(cx);
|
|
|
|
/*
|
|
* Any definite properties we added due to analysis of the new script when
|
|
* the type object was created are now invalid: objects with the same type
|
|
* can be created by using 'new' on a different script or through some
|
|
* other mechanism (e.g. Object.create). Rather than clear out the definite
|
|
* bits on the object's properties, just mark such properties as having
|
|
* been deleted/reconfigured, which will have the same effect on JITs
|
|
* wanting to use the definite bits to optimize property accesses.
|
|
*/
|
|
for (unsigned i = 0; i < getPropertyCount(); i++) {
|
|
Property *prop = getProperty(i);
|
|
if (!prop)
|
|
continue;
|
|
if (prop->types.isDefiniteProperty())
|
|
prop->types.setOwnProperty(cx, true);
|
|
}
|
|
|
|
#ifdef JS_METHODJIT
|
|
mjit::ExpandInlineFrames(cx, true);
|
|
#endif
|
|
|
|
/*
|
|
* If we cleared the new script while in the middle of initializing an
|
|
* object, it will still have the new script's shape and reflect the no
|
|
* longer correct state of the object once its initialization is completed.
|
|
* We can't really detect the possibility of this statically, but the new
|
|
* script keeps track of where each property is initialized so we can walk
|
|
* the stack and fix up any such objects.
|
|
*/
|
|
for (AllFramesIter iter(cx); !iter.done(); ++iter) {
|
|
StackFrame *fp = iter.fp();
|
|
if (fp->isScriptFrame() && fp->isConstructing() &&
|
|
fp->script() == newScript->script && fp->thisValue().isObject() &&
|
|
fp->thisValue().toObject().type == this) {
|
|
JSObject *obj = &fp->thisValue().toObject();
|
|
JSInlinedSite *inline_;
|
|
jsbytecode *pc = fp->pc(cx, NULL, &inline_);
|
|
JS_ASSERT(!inline_);
|
|
|
|
/* Whether all identified 'new' properties have been initialized. */
|
|
bool finished = false;
|
|
|
|
/* If not finished, number of properties that have been added. */
|
|
uint32 numProperties = 0;
|
|
|
|
/*
|
|
* If non-zero, we are scanning initializers in a call which has
|
|
* already finished.
|
|
*/
|
|
size_t depth = 0;
|
|
|
|
for (TypeNewScript::Initializer *init = newScript->initializerList;; init++) {
|
|
uint32 offset = uint32(pc - fp->script()->code);
|
|
if (init->kind == TypeNewScript::Initializer::SETPROP) {
|
|
if (!depth && init->offset > offset) {
|
|
/* Advanced past all properties which have been initialized. */
|
|
break;
|
|
}
|
|
numProperties++;
|
|
} else if (init->kind == TypeNewScript::Initializer::FRAME_PUSH) {
|
|
if (depth) {
|
|
depth++;
|
|
} else if (init->offset > offset) {
|
|
/* Advanced past all properties which have been initialized. */
|
|
break;
|
|
} else if (init->offset == offset) {
|
|
StackSegment &seg = cx->stack.space().containingSegment(fp);
|
|
if (seg.currentFrame() == fp)
|
|
break;
|
|
fp = seg.computeNextFrame(fp);
|
|
pc = fp->pc(cx, NULL, &inline_);
|
|
JS_ASSERT(!inline_);
|
|
} else {
|
|
/* This call has already finished. */
|
|
depth = 1;
|
|
}
|
|
} else if (init->kind == TypeNewScript::Initializer::FRAME_POP) {
|
|
if (depth) {
|
|
depth--;
|
|
} else {
|
|
/* This call has not finished yet. */
|
|
break;
|
|
}
|
|
} else {
|
|
JS_ASSERT(init->kind == TypeNewScript::Initializer::DONE);
|
|
finished = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!finished)
|
|
obj->rollbackProperties(numProperties);
|
|
}
|
|
}
|
|
|
|
cx->free_(newScript);
|
|
newScript = NULL;
|
|
}
|
|
|
|
void
|
|
TypeObject::print(JSContext *cx)
|
|
{
|
|
printf("%s : %s", name(), proto ? proto->getType()->name() : "(null)");
|
|
|
|
if (unknownProperties()) {
|
|
printf(" unknown");
|
|
} else {
|
|
if (!hasAnyFlags(OBJECT_FLAG_NON_PACKED_ARRAY))
|
|
printf(" packed");
|
|
if (!hasAnyFlags(OBJECT_FLAG_NON_DENSE_ARRAY))
|
|
printf(" dense");
|
|
if (hasAnyFlags(OBJECT_FLAG_UNINLINEABLE))
|
|
printf(" uninlineable");
|
|
if (hasAnyFlags(OBJECT_FLAG_SPECIAL_EQUALITY))
|
|
printf(" specialEquality");
|
|
if (hasAnyFlags(OBJECT_FLAG_ITERATED))
|
|
printf(" iterated");
|
|
}
|
|
|
|
if (propertyCount == 0) {
|
|
printf(" {}\n");
|
|
return;
|
|
}
|
|
|
|
printf(" {");
|
|
|
|
unsigned count = getPropertyCount();
|
|
for (unsigned i = 0; i < count; i++) {
|
|
Property *prop = getProperty(i);
|
|
if (prop) {
|
|
printf("\n %s:", TypeIdString(prop->id));
|
|
prop->types.print(cx);
|
|
}
|
|
}
|
|
|
|
printf("\n}\n");
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Type Analysis
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* If the bytecode immediately following code/pc is a test of the value
|
|
* pushed by code, that value should be marked as possibly void.
|
|
*/
|
|
static inline bool
|
|
CheckNextTest(jsbytecode *pc)
|
|
{
|
|
jsbytecode *next = pc + GetBytecodeLength(pc);
|
|
switch ((JSOp)*next) {
|
|
case JSOP_IFEQ:
|
|
case JSOP_IFNE:
|
|
case JSOP_NOT:
|
|
case JSOP_OR:
|
|
case JSOP_ORX:
|
|
case JSOP_AND:
|
|
case JSOP_ANDX:
|
|
case JSOP_TYPEOF:
|
|
case JSOP_TYPEOFEXPR:
|
|
return true;
|
|
default:
|
|
/* TRAP ok here */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static inline TypeObject *
|
|
GetInitializerType(JSContext *cx, JSScript *script, jsbytecode *pc)
|
|
{
|
|
if (!script->global)
|
|
return NULL;
|
|
|
|
UntrapOpcode untrap(cx, script, pc);
|
|
|
|
JSOp op = JSOp(*pc);
|
|
JS_ASSERT(op == JSOP_NEWARRAY || op == JSOP_NEWOBJECT || op == JSOP_NEWINIT);
|
|
|
|
bool isArray = (op == JSOP_NEWARRAY || (op == JSOP_NEWINIT && pc[1] == JSProto_Array));
|
|
return script->getTypeInitObject(cx, pc, isArray);
|
|
}
|
|
|
|
inline void
|
|
ScriptAnalysis::setForTypes(JSContext *cx, jsbytecode *pc, TypeSet *types)
|
|
{
|
|
/* Find the initial ITER opcode which constructed the active iterator. */
|
|
const SSAValue &iterv = poppedValue(pc, 0);
|
|
jsbytecode *iterpc = script->code + iterv.pushedOffset();
|
|
JS_ASSERT(JSOp(*iterpc) == JSOP_ITER || JSOp(*iterpc) == JSOP_TRAP);
|
|
|
|
uintN flags = iterpc[1];
|
|
if (flags & JSITER_FOREACH) {
|
|
types->addType(cx, TYPE_UNKNOWN);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This is a plain 'for in' loop. The value bound is a string, unless the
|
|
* iterated object is a generator or has an __iterator__ hook, which we'll
|
|
* detect dynamically.
|
|
*/
|
|
types->addType(cx, TYPE_STRING);
|
|
|
|
pushedTypes(iterpc, 0)->add(cx,
|
|
ArenaNew<TypeConstraintGenerator>(cx->compartment->pool, script, types));
|
|
}
|
|
|
|
/* Analyze type information for a single bytecode. */
|
|
bool
|
|
ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
|
|
TypeInferenceState &state)
|
|
{
|
|
jsbytecode *pc = script->code + offset;
|
|
JSOp op = (JSOp)*pc;
|
|
|
|
Bytecode &code = getCode(offset);
|
|
JS_ASSERT(!code.pushedTypes);
|
|
|
|
InferSpew(ISpewOps, "analyze: #%u:%05u", script->id(), offset);
|
|
|
|
unsigned defCount = GetDefCount(script, offset);
|
|
if (ExtendedDef(pc))
|
|
defCount++;
|
|
|
|
TypeSet *pushed = ArenaArray<TypeSet>(cx->compartment->pool, defCount);
|
|
if (!pushed)
|
|
return false;
|
|
PodZero(pushed, defCount);
|
|
code.pushedTypes = pushed;
|
|
|
|
/*
|
|
* Add phi nodes introduced at this point to the list of all phi nodes in
|
|
* the script. Types for these are not generated until after the script has
|
|
* been processed, as types can flow backwards into phi nodes and the
|
|
* source sets may not exist if we try to process these eagerly.
|
|
*/
|
|
if (code.newValues) {
|
|
SlotValue *newv = code.newValues;
|
|
while (newv->slot) {
|
|
if (newv->value.kind() != SSAValue::PHI || newv->value.phiOffset() != offset) {
|
|
newv++;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* The phi nodes at join points should all be unique, and every phi
|
|
* node created should be in the phiValues list on some bytecode.
|
|
*/
|
|
if (!state.phiNodes.append(newv->value.phiNode()))
|
|
return false;
|
|
TypeSet &types = newv->value.phiNode()->types;
|
|
types.setIntermediate();
|
|
InferSpew(ISpewOps, "typeSet: T%p phi #%u:%05u:%u", &types,
|
|
script->id(), offset, newv->slot);
|
|
newv++;
|
|
}
|
|
}
|
|
|
|
for (unsigned i = 0; i < defCount; i++) {
|
|
pushed[i].setIntermediate();
|
|
InferSpew(ISpewOps, "typeSet: T%p pushed%u #%u:%05u", &pushed[i], i, script->id(), offset);
|
|
}
|
|
|
|
/* Add unknown result for opcodes which were monitored before being analyzed. */
|
|
if (code.monitoredTypes && MonitorResultUnknown(op))
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
|
|
/* Add type constraints for the various opcodes. */
|
|
switch (op) {
|
|
|
|
/* Nop bytecodes. */
|
|
case JSOP_POP:
|
|
case JSOP_NOP:
|
|
case JSOP_TRACE:
|
|
case JSOP_NOTRACE:
|
|
case JSOP_GOTO:
|
|
case JSOP_GOTOX:
|
|
case JSOP_IFEQ:
|
|
case JSOP_IFEQX:
|
|
case JSOP_IFNE:
|
|
case JSOP_IFNEX:
|
|
case JSOP_LINENO:
|
|
case JSOP_DEFCONST:
|
|
case JSOP_LEAVEWITH:
|
|
case JSOP_LEAVEBLOCK:
|
|
case JSOP_RETRVAL:
|
|
case JSOP_ENDITER:
|
|
case JSOP_THROWING:
|
|
case JSOP_GOSUB:
|
|
case JSOP_GOSUBX:
|
|
case JSOP_RETSUB:
|
|
case JSOP_CONDSWITCH:
|
|
case JSOP_DEFAULT:
|
|
case JSOP_DEFAULTX:
|
|
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_DEBUGGER:
|
|
case JSOP_SETCALL:
|
|
case JSOP_TABLESWITCH:
|
|
case JSOP_TABLESWITCHX:
|
|
case JSOP_LOOKUPSWITCH:
|
|
case JSOP_LOOKUPSWITCHX:
|
|
case JSOP_TRY:
|
|
break;
|
|
|
|
/* Bytecodes pushing values of known type. */
|
|
case JSOP_VOID:
|
|
case JSOP_PUSH:
|
|
pushed[0].addType(cx, 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. */
|
|
pushed[0].addType(cx, 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:
|
|
pushed[0].addType(cx, TYPE_BOOLEAN);
|
|
break;
|
|
case JSOP_DOUBLE:
|
|
pushed[0].addType(cx, 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:
|
|
pushed[0].addType(cx, TYPE_STRING);
|
|
break;
|
|
case JSOP_NULL:
|
|
pushed[0].addType(cx, TYPE_NULL);
|
|
break;
|
|
|
|
case JSOP_REGEXP:
|
|
if (script->global) {
|
|
TypeObject *object = script->getTypeNewObject(cx, JSProto_RegExp);
|
|
if (!object)
|
|
return false;
|
|
pushed[0].addType(cx, (jstype) object);
|
|
} else {
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
}
|
|
break;
|
|
|
|
case JSOP_OBJECT: {
|
|
JSObject *obj = GetScriptObject(cx, script, pc, 0);
|
|
pushed[0].addType(cx, (jstype) obj->getType());
|
|
break;
|
|
}
|
|
|
|
case JSOP_STOP:
|
|
/* If a stop is reachable then the return type may be void. */
|
|
if (script->fun)
|
|
script->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. */
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
|
|
case JSOP_DUP:
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[1]);
|
|
break;
|
|
|
|
case JSOP_DUP2:
|
|
poppedTypes(pc, 1)->addSubset(cx, script, &pushed[0]);
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[1]);
|
|
poppedTypes(pc, 1)->addSubset(cx, script, &pushed[2]);
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[3]);
|
|
break;
|
|
|
|
case JSOP_GETGLOBAL:
|
|
case JSOP_CALLGLOBAL:
|
|
case JSOP_GETGNAME:
|
|
case JSOP_CALLGNAME: {
|
|
jsid id;
|
|
if (op == JSOP_GETGLOBAL || op == JSOP_CALLGLOBAL)
|
|
id = GetGlobalId(cx, script, pc);
|
|
else
|
|
id = GetAtomId(cx, script, pc, 0);
|
|
|
|
TypeSet *seen = script->bytecodeTypes(pc);
|
|
seen->addSubset(cx, script, &pushed[0]);
|
|
|
|
/*
|
|
* Normally we rely on lazy standard class initialization to fill in
|
|
* the types of global properties the script can access. In a few cases
|
|
* the method JIT will bypass this, and we need to add the types direclty.
|
|
*/
|
|
if (id == ATOM_TO_JSID(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]))
|
|
seen->addType(cx, TYPE_UNDEFINED);
|
|
if (id == ATOM_TO_JSID(cx->runtime->atomState.NaNAtom))
|
|
seen->addType(cx, TYPE_DOUBLE);
|
|
if (id == ATOM_TO_JSID(cx->runtime->atomState.InfinityAtom))
|
|
seen->addType(cx, TYPE_DOUBLE);
|
|
|
|
/* Handle as a property access. */
|
|
PropertyAccess(cx, script, pc, script->getGlobalType(), false, seen, id);
|
|
|
|
if (op == JSOP_CALLGLOBAL || op == JSOP_CALLGNAME) {
|
|
pushed[1].addType(cx, TYPE_UNKNOWN);
|
|
pushed[0].addPropagateThis(cx, script, pc, TYPE_UNKNOWN);
|
|
}
|
|
|
|
if (CheckNextTest(pc))
|
|
pushed[0].addType(cx, TYPE_UNDEFINED);
|
|
|
|
/*
|
|
* GETGNAME can refer to non-global names if EvaluateInStackFrame
|
|
* introduces new bindings.
|
|
*/
|
|
if (cx->compartment->debugMode)
|
|
seen->addType(cx, TYPE_UNKNOWN);
|
|
break;
|
|
}
|
|
|
|
case JSOP_INCGNAME:
|
|
case JSOP_DECGNAME:
|
|
case JSOP_GNAMEINC:
|
|
case JSOP_GNAMEDEC: {
|
|
jsid id = GetAtomId(cx, script, pc, 0);
|
|
PropertyAccess(cx, script, pc, script->getGlobalType(), true, NULL, id);
|
|
PropertyAccess(cx, script, pc, script->getGlobalType(), false, &pushed[0], id);
|
|
|
|
if (cx->compartment->debugMode)
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
break;
|
|
}
|
|
|
|
case JSOP_NAME:
|
|
case JSOP_CALLNAME: {
|
|
/*
|
|
* The first value pushed by NAME/CALLNAME must always be added to the
|
|
* bytecode types, we don't model these opcodes with inference.
|
|
*/
|
|
TypeSet *seen = script->bytecodeTypes(pc);
|
|
seen->addSubset(cx, script, &pushed[0]);
|
|
if (op == JSOP_CALLNAME) {
|
|
pushed[1].addType(cx, TYPE_UNKNOWN);
|
|
pushed[0].addPropagateThis(cx, script, pc, TYPE_UNKNOWN);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JSOP_BINDGNAME:
|
|
case JSOP_BINDNAME:
|
|
break;
|
|
|
|
case JSOP_SETGNAME: {
|
|
jsid id = GetAtomId(cx, script, pc, 0);
|
|
PropertyAccess(cx, script, pc, script->getGlobalType(),
|
|
true, poppedTypes(pc, 0), id);
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
}
|
|
|
|
case JSOP_SETNAME:
|
|
case JSOP_SETCONST:
|
|
cx->compartment->types.monitorBytecode(cx, script, offset);
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
|
|
case JSOP_INCNAME:
|
|
case JSOP_DECNAME:
|
|
case JSOP_NAMEINC:
|
|
case JSOP_NAMEDEC:
|
|
cx->compartment->types.monitorBytecode(cx, script, offset);
|
|
break;
|
|
|
|
case JSOP_GETXPROP: {
|
|
TypeSet *seen = script->bytecodeTypes(pc);
|
|
seen->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
}
|
|
|
|
case JSOP_GETFCSLOT:
|
|
case JSOP_CALLFCSLOT: {
|
|
unsigned index = GET_UINT16(pc);
|
|
TypeSet *types = script->upvarTypes(index);
|
|
types->addSubset(cx, script, &pushed[0]);
|
|
if (op == JSOP_CALLFCSLOT) {
|
|
pushed[1].addType(cx, TYPE_UNDEFINED);
|
|
pushed[0].addPropagateThis(cx, script, pc, TYPE_UNDEFINED);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JSOP_GETUPVAR_DBG:
|
|
case JSOP_CALLUPVAR_DBG:
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
if (op == JSOP_CALLUPVAR_DBG) {
|
|
pushed[1].addType(cx, TYPE_UNDEFINED);
|
|
pushed[0].addPropagateThis(cx, script, pc, TYPE_UNDEFINED);
|
|
}
|
|
break;
|
|
|
|
case JSOP_GETARG:
|
|
case JSOP_CALLARG:
|
|
case JSOP_GETLOCAL:
|
|
case JSOP_CALLLOCAL: {
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
if (trackSlot(slot)) {
|
|
/*
|
|
* Normally these opcodes don't pop anything, but they are given
|
|
* an extended use holding the variable's SSA value before the
|
|
* access. Use the types from here.
|
|
*/
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
} else if (slot < TotalSlots(script)) {
|
|
TypeSet *types = script->slotTypes(slot);
|
|
types->addSubset(cx, script, &pushed[0]);
|
|
} else {
|
|
/* Local 'let' variable. Punt on types for these, for now. */
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
}
|
|
if (op == JSOP_CALLARG || op == JSOP_CALLLOCAL) {
|
|
pushed[1].addType(cx, TYPE_UNDEFINED);
|
|
pushed[0].addPropagateThis(cx, script, pc, TYPE_UNDEFINED);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JSOP_SETARG:
|
|
case JSOP_SETLOCAL:
|
|
case JSOP_SETLOCALPOP: {
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
if (!trackSlot(slot) && slot < TotalSlots(script)) {
|
|
TypeSet *types = script->slotTypes(slot);
|
|
poppedTypes(pc, 0)->addSubset(cx, script, types);
|
|
}
|
|
|
|
/*
|
|
* For assignments to non-escaping locals/args, we don't need to update
|
|
* the possible types of the var, as for each read of the var SSA gives
|
|
* us the writes that could have produced that read.
|
|
*/
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
}
|
|
|
|
case JSOP_INCARG:
|
|
case JSOP_DECARG:
|
|
case JSOP_ARGINC:
|
|
case JSOP_ARGDEC:
|
|
case JSOP_INCLOCAL:
|
|
case JSOP_DECLOCAL:
|
|
case JSOP_LOCALINC:
|
|
case JSOP_LOCALDEC: {
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
if (trackSlot(slot)) {
|
|
poppedTypes(pc, 0)->addArith(cx, script, &pushed[0]);
|
|
} else if (slot < TotalSlots(script)) {
|
|
TypeSet *types = script->slotTypes(slot);
|
|
types->addArith(cx, script, types);
|
|
types->addSubset(cx, script, &pushed[0]);
|
|
} else {
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JSOP_ARGUMENTS: {
|
|
/* Compute a precise type only when we know the arguments won't escape. */
|
|
TypeObject *funType = script->fun->getType();
|
|
if (funType->unknownProperties() || funType->hasAnyFlags(OBJECT_FLAG_CREATED_ARGUMENTS)) {
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
break;
|
|
}
|
|
TypeSet *prop = funType->getProperty(cx, JSID_VOID, false);
|
|
if (!prop)
|
|
break;
|
|
prop->addLazyArguments(cx, script, &pushed[0]);
|
|
pushed[0].addType(cx, TYPE_LAZYARGS);
|
|
break;
|
|
}
|
|
|
|
case JSOP_SETPROP:
|
|
case JSOP_SETMETHOD: {
|
|
jsid id = GetAtomId(cx, script, pc, 0);
|
|
poppedTypes(pc, 1)->addSetProperty(cx, script, pc, poppedTypes(pc, 0), id);
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
}
|
|
|
|
case JSOP_LENGTH:
|
|
case JSOP_GETPROP:
|
|
case JSOP_CALLPROP: {
|
|
jsid id = GetAtomId(cx, script, pc, 0);
|
|
TypeSet *seen = script->bytecodeTypes(pc);
|
|
|
|
poppedTypes(pc, 0)->addGetProperty(cx, script, pc, seen, id);
|
|
if (op == JSOP_CALLPROP)
|
|
poppedTypes(pc, 0)->addCallProperty(cx, script, pc, id);
|
|
|
|
seen->addSubset(cx, script, &pushed[0]);
|
|
if (op == JSOP_CALLPROP)
|
|
poppedTypes(pc, 0)->addFilterPrimitives(cx, script, &pushed[1], true);
|
|
if (CheckNextTest(pc))
|
|
pushed[0].addType(cx, TYPE_UNDEFINED);
|
|
break;
|
|
}
|
|
|
|
case JSOP_INCPROP:
|
|
case JSOP_DECPROP:
|
|
case JSOP_PROPINC:
|
|
case JSOP_PROPDEC: {
|
|
jsid id = GetAtomId(cx, script, pc, 0);
|
|
poppedTypes(pc, 0)->addGetProperty(cx, script, pc, &pushed[0], id);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We only consider ELEM accesses on integers below. Any element access
|
|
* which is accessing a non-integer property must be monitored.
|
|
*/
|
|
|
|
case JSOP_GETELEM:
|
|
case JSOP_CALLELEM: {
|
|
TypeSet *seen = script->bytecodeTypes(pc);
|
|
|
|
poppedTypes(pc, 1)->addGetProperty(cx, script, pc, seen, JSID_VOID);
|
|
if (op == JSOP_CALLELEM)
|
|
poppedTypes(pc, 1)->addCallProperty(cx, script, pc, JSID_VOID);
|
|
|
|
seen->addSubset(cx, script, &pushed[0]);
|
|
if (op == JSOP_CALLELEM)
|
|
poppedTypes(pc, 1)->addFilterPrimitives(cx, script, &pushed[1], true);
|
|
if (CheckNextTest(pc))
|
|
pushed[0].addType(cx, TYPE_UNDEFINED);
|
|
break;
|
|
}
|
|
|
|
case JSOP_INCELEM:
|
|
case JSOP_DECELEM:
|
|
case JSOP_ELEMINC:
|
|
case JSOP_ELEMDEC:
|
|
poppedTypes(pc, 1)->addGetProperty(cx, script, pc, &pushed[0], JSID_VOID);
|
|
break;
|
|
|
|
case JSOP_SETELEM:
|
|
case JSOP_SETHOLE:
|
|
poppedTypes(pc, 2)->addSetProperty(cx, script, pc, poppedTypes(pc, 0), JSID_VOID);
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
|
|
case JSOP_THIS:
|
|
script->thisTypes()->addTransformThis(cx, script, &pushed[0]);
|
|
break;
|
|
|
|
case JSOP_RETURN:
|
|
case JSOP_SETRVAL:
|
|
if (script->fun)
|
|
poppedTypes(pc, 0)->addSubset(cx, script, script->returnTypes());
|
|
break;
|
|
|
|
case JSOP_ADD:
|
|
poppedTypes(pc, 0)->addArith(cx, script, &pushed[0], poppedTypes(pc, 1));
|
|
poppedTypes(pc, 1)->addArith(cx, script, &pushed[0], poppedTypes(pc, 0));
|
|
break;
|
|
|
|
case JSOP_SUB:
|
|
case JSOP_MUL:
|
|
case JSOP_MOD:
|
|
case JSOP_DIV:
|
|
poppedTypes(pc, 0)->addArith(cx, script, &pushed[0]);
|
|
poppedTypes(pc, 1)->addArith(cx, script, &pushed[0]);
|
|
break;
|
|
|
|
case JSOP_NEG:
|
|
case JSOP_POS:
|
|
poppedTypes(pc, 0)->addArith(cx, script, &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 off = (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC) ? SLOTNO_LEN : 0;
|
|
JSObject *obj = GetScriptObject(cx, script, pc, off);
|
|
|
|
TypeSet *res = NULL;
|
|
if (op == JSOP_LAMBDA || op == JSOP_LAMBDA_FC) {
|
|
res = &pushed[0];
|
|
} else if (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC) {
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
if (trackSlot(slot)) {
|
|
res = &pushed[0];
|
|
} else {
|
|
/* Should not see 'let' vars here. */
|
|
JS_ASSERT(slot < TotalSlots(script));
|
|
res = script->slotTypes(slot);
|
|
}
|
|
}
|
|
|
|
if (res) {
|
|
if (script->global)
|
|
res->addType(cx, (jstype) obj->getType());
|
|
else
|
|
res->addType(cx, TYPE_UNKNOWN);
|
|
} else {
|
|
cx->compartment->types.monitorBytecode(cx, script, offset);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JSOP_DEFVAR:
|
|
break;
|
|
|
|
case JSOP_CALL:
|
|
case JSOP_EVAL:
|
|
case JSOP_FUNCALL:
|
|
case JSOP_FUNAPPLY:
|
|
case JSOP_NEW: {
|
|
/* Construct the base call information about this site. */
|
|
unsigned argCount = GetUseCount(script, offset) - 2;
|
|
TypeCallsite *callsite = ArenaNew<TypeCallsite>(cx->compartment->pool,
|
|
cx, script, pc, op == JSOP_NEW, argCount);
|
|
if (!callsite || (argCount && !callsite->argumentTypes)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
break;
|
|
}
|
|
callsite->thisTypes = poppedTypes(pc, argCount);
|
|
callsite->returnTypes = &pushed[0];
|
|
|
|
for (unsigned i = 0; i < argCount; i++)
|
|
callsite->argumentTypes[i] = poppedTypes(pc, argCount - 1 - i);
|
|
|
|
/*
|
|
* Mark FUNCALL and FUNAPPLY sites as monitored. The method JIT may
|
|
* lower these into normal calls, and we need to make sure the
|
|
* callee's argument types are checked on entry.
|
|
*/
|
|
if (op == JSOP_FUNCALL || op == JSOP_FUNAPPLY)
|
|
cx->compartment->types.monitorBytecode(cx, script, pc - script->code);
|
|
|
|
poppedTypes(pc, argCount + 1)->addCall(cx, callsite);
|
|
break;
|
|
}
|
|
|
|
case JSOP_NEWINIT:
|
|
case JSOP_NEWARRAY:
|
|
case JSOP_NEWOBJECT: {
|
|
TypeObject *initializer = GetInitializerType(cx, script, pc);
|
|
if (script->global) {
|
|
if (!initializer)
|
|
return false;
|
|
pushed[0].addType(cx, (jstype) initializer);
|
|
} else {
|
|
JS_ASSERT(!initializer);
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JSOP_ENDINIT:
|
|
break;
|
|
|
|
case JSOP_INITELEM: {
|
|
const SSAValue &objv = poppedValue(pc, 2);
|
|
jsbytecode *initpc = script->code + objv.pushedOffset();
|
|
TypeObject *initializer = GetInitializerType(cx, script, initpc);
|
|
|
|
if (initializer) {
|
|
pushed[0].addType(cx, (jstype) initializer);
|
|
if (!initializer->unknownProperties()) {
|
|
/*
|
|
* Assume the initialized element is an integer. INITELEM can be used
|
|
* for doubles which don't map to the JSID_VOID property, which must
|
|
* be caught with dynamic monitoring.
|
|
*/
|
|
TypeSet *types = initializer->getProperty(cx, JSID_VOID, true);
|
|
if (!types)
|
|
return false;
|
|
if (state.hasGetSet)
|
|
types->addType(cx, TYPE_UNKNOWN);
|
|
else if (state.hasHole)
|
|
cx->markTypeObjectFlags(initializer, js::types::OBJECT_FLAG_NON_PACKED_ARRAY);
|
|
else
|
|
poppedTypes(pc, 0)->addSubset(cx, script, types);
|
|
}
|
|
} else {
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
}
|
|
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: {
|
|
const SSAValue &objv = poppedValue(pc, 1);
|
|
jsbytecode *initpc = script->code + objv.pushedOffset();
|
|
TypeObject *initializer = GetInitializerType(cx, script, initpc);
|
|
|
|
if (initializer) {
|
|
pushed[0].addType(cx, (jstype) initializer);
|
|
if (!initializer->unknownProperties()) {
|
|
jsid id = GetAtomId(cx, script, pc, 0);
|
|
TypeSet *types = initializer->getProperty(cx, id, true);
|
|
if (!types)
|
|
return false;
|
|
if (id == id___proto__(cx) || id == id_prototype(cx))
|
|
cx->compartment->types.monitorBytecode(cx, script, offset);
|
|
else if (state.hasGetSet)
|
|
types->addType(cx, TYPE_UNKNOWN);
|
|
else
|
|
poppedTypes(pc, 0)->addSubset(cx, script, types);
|
|
}
|
|
} else {
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
}
|
|
state.hasGetSet = false;
|
|
JS_ASSERT(!state.hasHole);
|
|
break;
|
|
}
|
|
|
|
case JSOP_ENTERWITH:
|
|
case JSOP_ENTERBLOCK:
|
|
/*
|
|
* Scope lookups can occur on the values being pushed here. We don't track
|
|
* the value or its properties, and just monitor all name opcodes in the
|
|
* script.
|
|
*/
|
|
break;
|
|
|
|
case JSOP_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.
|
|
*/
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
|
|
case JSOP_MOREITER:
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
pushed[1].addType(cx, TYPE_BOOLEAN);
|
|
break;
|
|
|
|
case JSOP_FORGNAME: {
|
|
jsid id = GetAtomId(cx, script, pc, 0);
|
|
TypeObject *global = script->getGlobalType();
|
|
if (!global->unknownProperties()) {
|
|
TypeSet *types = global->getProperty(cx, id, true);
|
|
if (!types)
|
|
return false;
|
|
setForTypes(cx, pc, types);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JSOP_FORNAME:
|
|
cx->compartment->types.monitorBytecode(cx, script, offset);
|
|
break;
|
|
|
|
case JSOP_FORARG:
|
|
case JSOP_FORLOCAL: {
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
if (trackSlot(slot)) {
|
|
setForTypes(cx, pc, &pushed[1]);
|
|
} else {
|
|
if (slot < TotalSlots(script))
|
|
setForTypes(cx, pc, script->slotTypes(slot));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JSOP_FORELEM:
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
pushed[1].addType(cx, TYPE_UNKNOWN);
|
|
break;
|
|
|
|
case JSOP_FORPROP:
|
|
case JSOP_ENUMELEM:
|
|
case JSOP_ENUMCONSTELEM:
|
|
case JSOP_ARRAYPUSH:
|
|
cx->compartment->types.monitorBytecode(cx, script, offset);
|
|
break;
|
|
|
|
case JSOP_THROW:
|
|
/* There will be a monitor on the bytecode catching the exception. */
|
|
break;
|
|
|
|
case JSOP_FINALLY:
|
|
/* Pushes information about whether an exception was thrown. */
|
|
break;
|
|
|
|
case JSOP_EXCEPTION:
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
break;
|
|
|
|
case JSOP_DELPROP:
|
|
case JSOP_DELELEM:
|
|
case JSOP_DELNAME:
|
|
pushed[0].addType(cx, TYPE_BOOLEAN);
|
|
break;
|
|
|
|
case JSOP_LEAVEBLOCKEXPR:
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
|
|
case JSOP_CASE:
|
|
case JSOP_CASEX:
|
|
poppedTypes(pc, 1)->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
|
|
case JSOP_UNBRAND:
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
|
|
case JSOP_GENERATOR:
|
|
if (script->fun) {
|
|
if (script->global) {
|
|
TypeObject *object = script->getTypeNewObject(cx, JSProto_Generator);
|
|
if (!object)
|
|
return false;
|
|
script->returnTypes()->addType(cx, (jstype) object);
|
|
} else {
|
|
script->returnTypes()->addType(cx, TYPE_UNKNOWN);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case JSOP_YIELD:
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
break;
|
|
|
|
case JSOP_CALLXMLNAME:
|
|
pushed[1].addType(cx, TYPE_UNKNOWN);
|
|
/* FALLTHROUGH */
|
|
case JSOP_XMLNAME:
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
break;
|
|
|
|
case JSOP_SETXMLNAME:
|
|
cx->compartment->types.monitorBytecode(cx, script, offset);
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
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:
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
break;
|
|
|
|
case JSOP_FILTER:
|
|
/* Note: the second value pushed by filter is a hole, and not modelled. */
|
|
poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
|
|
case JSOP_ENDFILTER:
|
|
poppedTypes(pc, 1)->addSubset(cx, script, &pushed[0]);
|
|
break;
|
|
|
|
case JSOP_DEFSHARP:
|
|
break;
|
|
|
|
case JSOP_USESHARP:
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
break;
|
|
|
|
case JSOP_CALLEE:
|
|
if (script->global)
|
|
pushed[0].addType(cx, (jstype) script->fun->getType());
|
|
else
|
|
pushed[0].addType(cx, TYPE_UNKNOWN);
|
|
break;
|
|
|
|
default:
|
|
TypeFailure(cx, "Unknown bytecode at #%u:%05u", script->id(), offset);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ScriptAnalysis::analyzeTypes(JSContext *cx)
|
|
{
|
|
JS_ASSERT(!ranInference());
|
|
|
|
if (OOM()) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Refuse to analyze the types in a script which is compileAndGo but is
|
|
* running against a global with a cleared scope. Per GlobalObject::clear,
|
|
* we won't be running anymore compileAndGo code against the global
|
|
* (moreover, after clearing our analysis results will be wrong for the
|
|
* script and trying to reanalyze here can cause reentrance problems if we
|
|
* try to reinitialize standard classes that were cleared).
|
|
*/
|
|
if (script->global && script->global->isCleared())
|
|
return;
|
|
|
|
if (!ranSSA()) {
|
|
analyzeSSA(cx);
|
|
if (failed())
|
|
return;
|
|
}
|
|
|
|
if (!script->ensureTypeArray(cx)) {
|
|
setOOM(cx);
|
|
return;
|
|
}
|
|
|
|
if (script->ranInference) {
|
|
/*
|
|
* Reanalyzing this script after discarding from GC.
|
|
* Discard/recompile any JIT code for this script,
|
|
* to preserve invariant in TypeConstraintCondensed.
|
|
*/
|
|
cx->compartment->types.addPendingRecompile(cx, script);
|
|
}
|
|
|
|
/* Future OOM failures need to setPendingNukeTypes. */
|
|
script->ranInference = true;
|
|
|
|
/*
|
|
* Set this early to avoid reentrance. Any failures are OOMs, and will nuke
|
|
* all types in the compartment.
|
|
*/
|
|
ranInference_ = true;
|
|
|
|
if (script->calledWithNew)
|
|
analyzeTypesNew(cx);
|
|
|
|
/* Make sure the initial type set of all local vars includes void. */
|
|
for (unsigned i = 0; i < script->nfixed; i++)
|
|
script->localTypes(i)->addType(cx, TYPE_UNDEFINED);
|
|
|
|
TypeInferenceState state(cx);
|
|
|
|
unsigned offset = 0;
|
|
while (offset < script->length) {
|
|
Bytecode *code = maybeCode(offset);
|
|
|
|
jsbytecode *pc = script->code + offset;
|
|
UntrapOpcode untrap(cx, script, pc);
|
|
|
|
if (code && !analyzeTypesBytecode(cx, offset, state)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
|
|
offset += GetBytecodeLength(pc);
|
|
}
|
|
|
|
for (unsigned i = 0; i < state.phiNodes.length(); i++) {
|
|
SSAPhiNode *node = state.phiNodes[i];
|
|
for (unsigned j = 0; j < node->length; j++) {
|
|
const SSAValue &v = node->options[j];
|
|
getValueTypes(v)->addSubset(cx, script, &node->types);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Replay any intermediate type information which has been generated for
|
|
* the script either because we ran the interpreter some before analyzing
|
|
* or because we are reanalyzing after a GC.
|
|
*/
|
|
TypeIntermediate *result = script->intermediateTypes;
|
|
while (result) {
|
|
result->replay(cx, script);
|
|
result = result->next;
|
|
}
|
|
|
|
if (!script->usesArguments)
|
|
return;
|
|
|
|
/*
|
|
* Do additional analysis to determine whether the arguments object in the
|
|
* script can escape.
|
|
*/
|
|
|
|
if (script->fun->getType()->hasAnyFlags(types::OBJECT_FLAG_CREATED_ARGUMENTS))
|
|
return;
|
|
|
|
/*
|
|
* Note: don't check for strict mode code here, even though arguments
|
|
* accesses in such scripts will always be deoptimized. These scripts can
|
|
* have a JSOP_ARGUMENTS in their prologue which the usesArguments check
|
|
* above does not account for. We filter in the interpreter and JITs
|
|
* themselves.
|
|
*/
|
|
if (script->fun->isHeavyweight() || cx->compartment->debugMode) {
|
|
cx->markTypeObjectFlags(script->fun->getType(),
|
|
types::OBJECT_FLAG_CREATED_ARGUMENTS);
|
|
return;
|
|
}
|
|
|
|
offset = 0;
|
|
while (offset < script->length) {
|
|
Bytecode *code = maybeCode(offset);
|
|
jsbytecode *pc = script->code + offset;
|
|
|
|
if (code && JSOp(*pc) == JSOP_ARGUMENTS) {
|
|
Vector<SSAValue> seen(cx);
|
|
if (!followEscapingArguments(cx, SSAValue::PushedValue(offset, 0), &seen)) {
|
|
cx->markTypeObjectFlags(script->fun->getType(),
|
|
types::OBJECT_FLAG_CREATED_ARGUMENTS);
|
|
return;
|
|
}
|
|
}
|
|
|
|
offset += GetBytecodeLength(pc);
|
|
}
|
|
|
|
/*
|
|
* The VM is now free to use the arguments in this script lazily. If we end
|
|
* up creating an arguments object for the script in the future or regard
|
|
* the arguments as escaping, we need to walk the stack and replace lazy
|
|
* arguments objects with actual arguments objects.
|
|
*/
|
|
script->usedLazyArgs = true;
|
|
}
|
|
|
|
bool
|
|
ScriptAnalysis::followEscapingArguments(JSContext *cx, const SSAValue &v, Vector<SSAValue> *seen)
|
|
{
|
|
/*
|
|
* trackUseChain is false for initial values of variables, which
|
|
* cannot hold the script's arguments object.
|
|
*/
|
|
if (!trackUseChain(v))
|
|
return true;
|
|
|
|
for (unsigned i = 0; i < seen->length(); i++) {
|
|
if (v.equals((*seen)[i]))
|
|
return true;
|
|
}
|
|
if (!seen->append(v)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return false;
|
|
}
|
|
|
|
SSAUseChain *use = useChain(v);
|
|
while (use) {
|
|
if (!followEscapingArguments(cx, use, seen))
|
|
return false;
|
|
use = use->next;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ScriptAnalysis::followEscapingArguments(JSContext *cx, SSAUseChain *use, Vector<SSAValue> *seen)
|
|
{
|
|
if (!use->popped)
|
|
return followEscapingArguments(cx, SSAValue::PhiValue(use->offset, use->u.phi), seen);
|
|
|
|
jsbytecode *pc = script->code + use->offset;
|
|
uint32 which = use->u.which;
|
|
|
|
JSOp op = JSOp(*pc);
|
|
JS_ASSERT(op != JSOP_TRAP);
|
|
|
|
if (op == JSOP_POP || op == JSOP_POPN)
|
|
return true;
|
|
|
|
/* Allow GETELEM and LENGTH on arguments objects that don't escape. */
|
|
|
|
/*
|
|
* Note: if the element index is not an integer we will mark the arguments
|
|
* as escaping at the access site.
|
|
*/
|
|
if (op == JSOP_GETELEM && which == 1)
|
|
return true;
|
|
|
|
if (op == JSOP_LENGTH)
|
|
return true;
|
|
|
|
/* Allow assignments to non-closed locals (but not arguments). */
|
|
|
|
if (op == JSOP_SETLOCAL) {
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
if (!trackSlot(slot))
|
|
return false;
|
|
if (!followEscapingArguments(cx, SSAValue::PushedValue(use->offset, 0), seen))
|
|
return false;
|
|
return followEscapingArguments(cx, SSAValue::WrittenVar(slot, use->offset), seen);
|
|
}
|
|
|
|
if (op == JSOP_GETLOCAL)
|
|
return followEscapingArguments(cx, SSAValue::PushedValue(use->offset, 0), seen);
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
ScriptAnalysis::analyzeTypesNew(JSContext *cx)
|
|
{
|
|
JS_ASSERT(script->calledWithNew && script->fun);
|
|
|
|
/*
|
|
* Compute the 'this' type when called with 'new'. We do not distinguish regular
|
|
* from 'new' calls to the function.
|
|
*/
|
|
|
|
if (script->fun->getType()->unknownProperties() ||
|
|
script->fun->isFunctionPrototype() ||
|
|
!script->global) {
|
|
script->thisTypes()->addType(cx, TYPE_UNKNOWN);
|
|
return;
|
|
}
|
|
|
|
TypeFunction *funType = script->fun->getType()->asFunction();
|
|
TypeSet *prototypeTypes = funType->getProperty(cx, id_prototype(cx), false);
|
|
if (!prototypeTypes)
|
|
return;
|
|
prototypeTypes->addNewObject(cx, script, funType, script->thisTypes());
|
|
}
|
|
|
|
/*
|
|
* Persistent constraint clearing out newScript and definite properties from
|
|
* an object should a property on another object get a setter.
|
|
*/
|
|
class TypeConstraintClearDefiniteSetter : public TypeConstraint
|
|
{
|
|
public:
|
|
TypeObject *object;
|
|
|
|
TypeConstraintClearDefiniteSetter(TypeObject *object)
|
|
: TypeConstraint("baseClearDefinite", (JSScript *) 0x1), object(object)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type) {
|
|
if (!object->newScript)
|
|
return;
|
|
/*
|
|
* Clear out the newScript shape and definite property information from
|
|
* an object if the source type set could be a setter (its type set
|
|
* becomes unknown).
|
|
*/
|
|
if (!object->newScriptCleared && type == TYPE_UNKNOWN)
|
|
object->clearNewScript(cx);
|
|
}
|
|
|
|
TypeObject * persistentObject() { return object; }
|
|
};
|
|
|
|
/*
|
|
* Constraint which clears definite properties on an object should a type set
|
|
* contain any types other than a single object.
|
|
*/
|
|
class TypeConstraintClearDefiniteSingle : public TypeConstraint
|
|
{
|
|
public:
|
|
TypeObject *object;
|
|
|
|
TypeConstraintClearDefiniteSingle(JSScript *script, TypeObject *object)
|
|
: TypeConstraint("baseClearDefinite", script), object(object)
|
|
{}
|
|
|
|
void newType(JSContext *cx, TypeSet *source, jstype type) {
|
|
if (!object->newScriptCleared && !source->getSingleObject())
|
|
object->clearNewScript(cx);
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Mark an intermediate type set such that changes will clear the definite
|
|
* properties on a type object.
|
|
*/
|
|
class TypeIntermediateClearDefinite : public TypeIntermediate
|
|
{
|
|
uint32 offset;
|
|
uint32 which;
|
|
TypeObject *object;
|
|
|
|
public:
|
|
TypeIntermediateClearDefinite(uint32 offset, uint32 which, TypeObject *object)
|
|
: offset(offset), which(which), object(object)
|
|
{}
|
|
|
|
void replay(JSContext *cx, JSScript *script)
|
|
{
|
|
TypeSet *pushed = script->analysis(cx)->pushedTypes(offset, which);
|
|
pushed->add(cx, ArenaNew<TypeConstraintClearDefiniteSingle>(cx->compartment->pool, script, object));
|
|
}
|
|
|
|
bool sweep(JSContext *cx, JSCompartment *compartment)
|
|
{
|
|
return object->marked;
|
|
}
|
|
};
|
|
|
|
static bool
|
|
AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, JSScript *script, JSObject **pbaseobj,
|
|
Vector<TypeNewScript::Initializer> *initializerList)
|
|
{
|
|
/*
|
|
* When invoking 'new' on the specified script, try to find some properties
|
|
* which will definitely be added to the created object before it has a
|
|
* chance to escape and be accessed elsewhere.
|
|
*
|
|
* Returns true if the entire script was analyzed (pbaseobj has been
|
|
* preserved), false if we had to bail out part way through (pbaseobj may
|
|
* have been cleared).
|
|
*/
|
|
|
|
if (initializerList->length() > 50) {
|
|
/*
|
|
* Bail out on really long initializer lists (far longer than maximum
|
|
* number of properties we can track), we may be recursing.
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
ScriptAnalysis *analysis = script->analysis(cx);
|
|
if (analysis && !analysis->ranInference())
|
|
analysis->analyzeTypes(cx);
|
|
if (!analysis || analysis->OOM()) {
|
|
*pbaseobj = NULL;
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Offset of the last bytecode which popped 'this' and which we have
|
|
* processed. For simplicity, we scan for places where 'this' is pushed
|
|
* and immediately analyze the place where that pushed value is popped.
|
|
* This runs the risk of doing things out of order, if the script looks
|
|
* something like 'this.f = (this.g = ...)', so we watch and bail out if
|
|
* a 'this' is pushed before the previous 'this' value was popped.
|
|
*/
|
|
uint32 lastThisPopped = 0;
|
|
|
|
unsigned nextOffset = 0;
|
|
while (nextOffset < script->length) {
|
|
unsigned offset = nextOffset;
|
|
jsbytecode *pc = script->code + offset;
|
|
UntrapOpcode untrap(cx, script, pc);
|
|
|
|
JSOp op = JSOp(*pc);
|
|
|
|
nextOffset += GetBytecodeLength(pc);
|
|
|
|
Bytecode *code = analysis->maybeCode(pc);
|
|
if (!code)
|
|
continue;
|
|
|
|
/*
|
|
* End analysis after the first return statement from the script,
|
|
* returning success if the return is unconditional.
|
|
*/
|
|
if (op == JSOP_RETURN || op == JSOP_STOP || op == JSOP_RETRVAL) {
|
|
if (offset < lastThisPopped) {
|
|
*pbaseobj = NULL;
|
|
return false;
|
|
}
|
|
return code->unconditional;
|
|
}
|
|
|
|
/* 'this' can escape through a call to eval. */
|
|
if (op == JSOP_EVAL) {
|
|
if (offset < lastThisPopped)
|
|
*pbaseobj = NULL;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* We are only interested in places where 'this' is popped. The new
|
|
* 'this' value cannot escape and be accessed except through such uses.
|
|
*/
|
|
if (op != JSOP_THIS)
|
|
continue;
|
|
|
|
SSAValue thisv = SSAValue::PushedValue(offset, 0);
|
|
SSAUseChain *uses = analysis->useChain(thisv);
|
|
|
|
JS_ASSERT(uses);
|
|
if (uses->next || !uses->popped) {
|
|
/* 'this' value popped in more than one place. */
|
|
return false;
|
|
}
|
|
|
|
/* Maintain ordering property on how 'this' is used, as described above. */
|
|
if (offset < lastThisPopped) {
|
|
*pbaseobj = NULL;
|
|
return false;
|
|
}
|
|
lastThisPopped = uses->offset;
|
|
|
|
/* Only handle 'this' values popped in unconditional code. */
|
|
Bytecode *poppedCode = analysis->maybeCode(uses->offset);
|
|
if (!poppedCode || !poppedCode->unconditional)
|
|
return false;
|
|
|
|
pc = script->code + uses->offset;
|
|
UntrapOpcode untrapUse(cx, script, pc);
|
|
|
|
op = JSOp(*pc);
|
|
|
|
JSObject *obj = *pbaseobj;
|
|
|
|
if (op == JSOP_SETPROP && uses->u.which == 1) {
|
|
/*
|
|
* Don't use GetAtomId here, we need to watch for SETPROP on
|
|
* integer properties and bail out. We can't mark the aggregate
|
|
* JSID_VOID type property as being in a definite slot.
|
|
*/
|
|
unsigned index = js_GetIndexFromBytecode(cx, script, pc, 0);
|
|
jsid id = ATOM_TO_JSID(script->getAtom(index));
|
|
if (MakeTypeId(cx, id) != id)
|
|
return false;
|
|
if (id == id_prototype(cx) || id == id___proto__(cx) || id == id_constructor(cx))
|
|
return false;
|
|
|
|
unsigned slotSpan = obj->slotSpan();
|
|
if (!DefineNativeProperty(cx, obj, id, UndefinedValue(), NULL, NULL,
|
|
JSPROP_ENUMERATE, 0, 0, DNP_SKIP_TYPE)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
*pbaseobj = NULL;
|
|
return false;
|
|
}
|
|
|
|
if (obj->inDictionaryMode()) {
|
|
*pbaseobj = NULL;
|
|
return false;
|
|
}
|
|
|
|
if (obj->slotSpan() == slotSpan) {
|
|
/* Set a duplicate property. */
|
|
return false;
|
|
}
|
|
|
|
TypeNewScript::Initializer setprop(TypeNewScript::Initializer::SETPROP, uses->offset);
|
|
if (!initializerList->append(setprop)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
*pbaseobj = NULL;
|
|
return false;
|
|
}
|
|
|
|
if (obj->slotSpan() >= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) {
|
|
/* Maximum number of definite properties added. */
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Ensure that if the properties named here could have a setter in
|
|
* the direct prototype (and thus its transitive prototypes), the
|
|
* definite properties get cleared from the shape.
|
|
*/
|
|
TypeSet *parentTypes = type->proto->getType()->getProperty(cx, id, false);
|
|
if (!parentTypes || parentTypes->unknown())
|
|
return false;
|
|
parentTypes->add(cx, cx->new_<TypeConstraintClearDefiniteSetter>(type));
|
|
} else if (op == JSOP_FUNCALL && uses->u.which == GET_ARGC(pc) - 1) {
|
|
/*
|
|
* Passed as the first parameter to Function.call. Follow control
|
|
* into the callee, and add any definite properties it assigns to
|
|
* the object as well. :TODO: This is narrow pattern matching on
|
|
* the inheritance patterns seen in the v8-deltablue benchmark, and
|
|
* needs robustness against other ways initialization can cross
|
|
* script boundaries.
|
|
*
|
|
* Add constraints ensuring we are calling Function.call on a
|
|
* particular script, removing definite properties from the result
|
|
*/
|
|
|
|
/* Callee/this must have been pushed by a CALLPROP. */
|
|
SSAValue calleev = analysis->poppedValue(pc, GET_ARGC(pc) + 1);
|
|
if (calleev.kind() != SSAValue::PUSHED)
|
|
return false;
|
|
jsbytecode *calleepc = script->code + calleev.pushedOffset();
|
|
UntrapOpcode untrapCallee(cx, script, calleepc);
|
|
if (JSOp(*calleepc) != JSOP_CALLPROP || calleev.pushedIndex() != 0)
|
|
return false;
|
|
|
|
/*
|
|
* This code may not have run yet, break any type barriers involved
|
|
* in performing the call (for the greater good!).
|
|
*/
|
|
analysis->breakTypeBarriersSSA(cx, analysis->poppedValue(calleepc, 0));
|
|
analysis->breakTypeBarriers(cx, calleepc - script->code, true);
|
|
|
|
TypeSet *funcallTypes = analysis->pushedTypes(calleepc, 0);
|
|
TypeSet *scriptTypes = analysis->pushedTypes(calleepc, 1);
|
|
|
|
/* Need to definitely be calling Function.call on a specific script. */
|
|
TypeObject *funcallObj = funcallTypes->getSingleObject();
|
|
if (!funcallObj || !funcallObj->singleton ||
|
|
!funcallObj->singleton->isFunction() ||
|
|
funcallObj->singleton->getFunctionPrivate()->maybeNative() != js_fun_call) {
|
|
return false;
|
|
}
|
|
TypeObject *scriptObj = scriptTypes->getSingleObject();
|
|
if (!scriptObj || !scriptObj->isFunction || !scriptObj->asFunction()->script)
|
|
return false;
|
|
|
|
/*
|
|
* Generate constraints to clear definite properties from the type
|
|
* should the Function.call or callee itself change in the future.
|
|
*/
|
|
TypeIntermediateClearDefinite *funcallTrap =
|
|
cx->new_<TypeIntermediateClearDefinite>(calleev.pushedOffset(), 0, type);
|
|
TypeIntermediateClearDefinite *calleeTrap =
|
|
cx->new_<TypeIntermediateClearDefinite>(calleev.pushedOffset(), 1, type);
|
|
if (!funcallTrap || !calleeTrap) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
*pbaseobj = NULL;
|
|
return false;
|
|
}
|
|
script->addIntermediateType(funcallTrap);
|
|
script->addIntermediateType(calleeTrap);
|
|
funcallTrap->replay(cx, script);
|
|
calleeTrap->replay(cx, script);
|
|
|
|
TypeNewScript::Initializer pushframe(TypeNewScript::Initializer::FRAME_PUSH, uses->offset);
|
|
if (!initializerList->append(pushframe)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
*pbaseobj = NULL;
|
|
return false;
|
|
}
|
|
|
|
if (!AnalyzeNewScriptProperties(cx, type, scriptObj->asFunction()->script,
|
|
pbaseobj, initializerList)) {
|
|
return false;
|
|
}
|
|
|
|
TypeNewScript::Initializer popframe(TypeNewScript::Initializer::FRAME_POP, 0);
|
|
if (!initializerList->append(popframe)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
*pbaseobj = NULL;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* The callee never lets the 'this' value escape, continue looking
|
|
* for definite properties in the remainder of this script.
|
|
*/
|
|
} else {
|
|
/* Unhandled use of 'this'. */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Will have hit a STOP or similar, unless the script always throws. */
|
|
return true;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Printing
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
ScriptAnalysis::printTypes(JSContext *cx)
|
|
{
|
|
AutoEnterAnalysis enter(cx);
|
|
TypeCompartment *compartment = &script->compartment->types;
|
|
|
|
/*
|
|
* Check if there are warnings for used values with unknown types, and build
|
|
* statistics about the size of type sets found for stack values.
|
|
*/
|
|
for (unsigned offset = 0; offset < script->length; offset++) {
|
|
if (!maybeCode(offset))
|
|
continue;
|
|
|
|
jsbytecode *pc = script->code + offset;
|
|
UntrapOpcode untrap(cx, script, pc);
|
|
|
|
unsigned defCount = GetDefCount(script, offset);
|
|
if (!defCount)
|
|
continue;
|
|
|
|
for (unsigned i = 0; i < defCount; i++) {
|
|
TypeSet *types = pushedTypes(offset, i);
|
|
|
|
if (types->unknown()) {
|
|
compartment->typeCountOver++;
|
|
continue;
|
|
}
|
|
|
|
unsigned typeCount = types->getObjectCount() ? 1 : 0;
|
|
for (jstype type = TYPE_UNDEFINED; type < TYPE_UNKNOWN; type++) {
|
|
if (types->hasAnyFlag(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->hasAnyFlag(TYPE_FLAG_DOUBLE)) {
|
|
JS_ASSERT(types->hasAnyFlag(TYPE_FLAG_INT32));
|
|
typeCount--;
|
|
}
|
|
|
|
if (typeCount > TypeCompartment::TYPE_COUNT_LIMIT) {
|
|
compartment->typeCountOver++;
|
|
} else if (typeCount == 0) {
|
|
/* Ignore values without types, this may be unreached code. */
|
|
} else {
|
|
compartment->typeCounts[typeCount-1]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
if (script->fun)
|
|
printf("Function");
|
|
else if (script->isCachedEval || script->isUncachedEval)
|
|
printf("Eval");
|
|
else
|
|
printf("Main");
|
|
printf(" #%u %s (line %d):\n", script->id(), script->filename, script->lineno);
|
|
|
|
printf("locals:");
|
|
printf("\n return:");
|
|
script->returnTypes()->print(cx);
|
|
printf("\n this:");
|
|
script->thisTypes()->print(cx);
|
|
|
|
for (unsigned i = 0; script->fun && i < script->fun->nargs; i++) {
|
|
printf("\n arg%u:", i);
|
|
script->argTypes(i)->print(cx);
|
|
}
|
|
for (unsigned i = 0; i < script->nfixed; i++) {
|
|
if (!trackSlot(LocalSlot(script, i))) {
|
|
printf("\n local%u:", i);
|
|
script->localTypes(i)->print(cx);
|
|
}
|
|
}
|
|
for (unsigned i = 0; i < script->bindings.countUpvars(); i++) {
|
|
printf("\n upvar%u:", i);
|
|
script->upvarTypes(i)->print(cx);
|
|
}
|
|
printf("\n");
|
|
|
|
for (unsigned offset = 0; offset < script->length; offset++) {
|
|
if (!maybeCode(offset))
|
|
continue;
|
|
|
|
jsbytecode *pc = script->code + offset;
|
|
UntrapOpcode untrap(cx, script, pc);
|
|
|
|
PrintBytecode(cx, script, pc);
|
|
|
|
if (js_CodeSpec[*pc].format & JOF_TYPESET) {
|
|
TypeSet *types = script->bytecodeTypes(pc);
|
|
printf(" typeset %d:", (int) (types - script->typeArray));
|
|
types->print(cx);
|
|
printf("\n");
|
|
}
|
|
|
|
unsigned defCount = GetDefCount(script, offset);
|
|
for (unsigned i = 0; i < defCount; i++) {
|
|
printf(" type %d:", i);
|
|
pushedTypes(offset, i)->print(cx);
|
|
printf("\n");
|
|
}
|
|
|
|
if (getCode(offset).monitoredTypes)
|
|
printf(" monitored\n");
|
|
|
|
TypeBarrier *barrier = getCode(offset).typeBarriers;
|
|
if (barrier != NULL) {
|
|
printf(" barrier:");
|
|
while (barrier) {
|
|
printf(" %s", TypeString(barrier->type));
|
|
barrier = barrier->next;
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
#endif /* DEBUG */
|
|
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// JSScript
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* Returns true if we don't expect to compute the correct types for some value
|
|
* pushed by the specified bytecode.
|
|
*/
|
|
static inline bool
|
|
IgnorePushed(const jsbytecode *pc, unsigned index)
|
|
{
|
|
JS_ASSERT(JSOp(*pc) != JSOP_TRAP);
|
|
|
|
switch (JSOp(*pc)) {
|
|
/* We keep track of the scopes pushed by BINDNAME separately. */
|
|
case JSOP_BINDNAME:
|
|
case JSOP_BINDGNAME:
|
|
case JSOP_BINDXMLNAME:
|
|
return true;
|
|
|
|
/* Stack not consistent in TRY_BRANCH_AFTER_COND. */
|
|
case JSOP_IN:
|
|
case JSOP_EQ:
|
|
case JSOP_NE:
|
|
case JSOP_LT:
|
|
case JSOP_LE:
|
|
case JSOP_GT:
|
|
case JSOP_GE:
|
|
return (index == 0);
|
|
|
|
/* Value not determining result is not pushed by OR/AND. */
|
|
case JSOP_OR:
|
|
case JSOP_ORX:
|
|
case JSOP_AND:
|
|
case JSOP_ANDX:
|
|
return (index == 0);
|
|
|
|
/* Holes tracked separately. */
|
|
case JSOP_HOLE:
|
|
return (index == 0);
|
|
case JSOP_FILTER:
|
|
return (index == 1);
|
|
|
|
/* Storage for 'with' and 'let' blocks not monitored. */
|
|
case JSOP_ENTERWITH:
|
|
case JSOP_ENTERBLOCK:
|
|
return true;
|
|
|
|
/* We don't keep track of the iteration state for 'for in' or 'for each in' loops. */
|
|
case JSOP_FORNAME:
|
|
case JSOP_FORGNAME:
|
|
case JSOP_FORLOCAL:
|
|
case JSOP_FORARG:
|
|
case JSOP_FORPROP:
|
|
case JSOP_FORELEM:
|
|
case JSOP_ITER:
|
|
case JSOP_MOREITER:
|
|
case JSOP_ENDITER:
|
|
return true;
|
|
|
|
/* DUP can be applied to values pushed by other opcodes we don't model. */
|
|
case JSOP_DUP:
|
|
case JSOP_DUP2:
|
|
return true;
|
|
|
|
/* We don't keep track of state indicating whether there is a pending exception. */
|
|
case JSOP_FINALLY:
|
|
return true;
|
|
|
|
/*
|
|
* We don't treat GETLOCAL immediately followed by a pop as a use-before-def,
|
|
* and while the type will have been inferred correctly the method JIT
|
|
* may not have written the local's initial undefined value to the stack,
|
|
* leaving a stale value.
|
|
*/
|
|
case JSOP_GETLOCAL:
|
|
return JSOp(pc[JSOP_GETLOCAL_LENGTH]) == JSOP_POP;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
JSScript::makeTypeArray(JSContext *cx)
|
|
{
|
|
JS_ASSERT(!typeArray);
|
|
|
|
AutoEnterTypeInference enter(cx);
|
|
|
|
unsigned count = nTypeSets + TotalSlots(this) + bindings.countUpvars();
|
|
typeArray = (TypeSet *) cx->calloc_(sizeof(TypeSet) * count);
|
|
if (!typeArray) {
|
|
compartment->types.setPendingNukeTypes(cx);
|
|
return false;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
for (unsigned i = 0; i < nTypeSets; i++)
|
|
InferSpew(ISpewOps, "typeSet: T%p bytecode%u #%u", &typeArray[i], i, id());
|
|
InferSpew(ISpewOps, "typeSet: T%p return #%u", returnTypes(), id());
|
|
InferSpew(ISpewOps, "typeSet: T%p this #%u", thisTypes(), id());
|
|
unsigned nargs = fun ? fun->nargs : 0;
|
|
for (unsigned i = 0; i < nargs; i++)
|
|
InferSpew(ISpewOps, "typeSet: T%p arg%u #%u", argTypes(i), i, id());
|
|
for (unsigned i = 0; i < nfixed; i++)
|
|
InferSpew(ISpewOps, "typeSet: T%p local%u #%u", localTypes(i), i, id());
|
|
for (unsigned i = 0; i < bindings.countUpvars(); i++)
|
|
InferSpew(ISpewOps, "typeSet: T%p upvar%u #%u", upvarTypes(i), i, id());
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JSScript::typeSetFunction(JSContext *cx, JSFunction *fun)
|
|
{
|
|
this->fun = fun;
|
|
|
|
if (!cx->typeInferenceEnabled())
|
|
return true;
|
|
|
|
char *name = NULL;
|
|
#ifdef DEBUG
|
|
name = (char *) alloca(10);
|
|
JS_snprintf(name, 10, "#%u", id());
|
|
#endif
|
|
|
|
TypeObject *type = cx->compartment->types.newTypeObject(cx, this, name, "",
|
|
true, false, fun->getProto());
|
|
if (!type)
|
|
return false;
|
|
|
|
if (!fun->setTypeAndUniqueShape(cx, type))
|
|
return false;
|
|
type->asFunction()->script = this;
|
|
this->fun = fun;
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
void
|
|
JSScript::typeCheckBytecode(JSContext *cx, const jsbytecode *pc, const js::Value *sp)
|
|
{
|
|
AutoEnterTypeInference enter(cx);
|
|
|
|
if (!(analysis_ && analysis_->ranInference()))
|
|
return;
|
|
|
|
int defCount = GetDefCount(this, pc - code);
|
|
|
|
for (int i = 0; i < defCount; i++) {
|
|
const js::Value &val = sp[-defCount + i];
|
|
TypeSet *types = analysis_->pushedTypes(pc, i);
|
|
if (IgnorePushed(pc, i))
|
|
continue;
|
|
|
|
jstype type = GetValueType(cx, val);
|
|
|
|
if (!types::TypeMatches(cx, types, type)) {
|
|
TypeFailure(cx, "Missing type at #%u:%05u pushed %u: %s",
|
|
id(), pc - code, i, TypeString(type));
|
|
}
|
|
|
|
if (TypeIsObject(type)) {
|
|
JS_ASSERT(val.isObject());
|
|
JSObject *obj = &val.toObject();
|
|
TypeObject *object = (TypeObject *) type;
|
|
|
|
if (object->unknownProperties())
|
|
continue;
|
|
|
|
/* Make sure information about the array status of this object is right. */
|
|
bool dense = !object->hasAnyFlags(OBJECT_FLAG_NON_DENSE_ARRAY);
|
|
bool packed = !object->hasAnyFlags(OBJECT_FLAG_NON_PACKED_ARRAY);
|
|
JS_ASSERT_IF(packed, dense);
|
|
if (dense) {
|
|
if (!obj->isDenseArray() || (packed && !obj->isPackedDenseArray())) {
|
|
TypeFailure(cx, "Object not %s array at #%u:%05u popped %u: %s",
|
|
packed ? "packed" : "dense",
|
|
id(), pc - code, i, object->name());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// JSObject
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
JSObject::makeNewType(JSContext *cx, JSScript *newScript)
|
|
{
|
|
JS_ASSERT(!newType);
|
|
|
|
TypeObject *type = cx->compartment->types.newTypeObject(cx, NULL, getType()->name(), "new",
|
|
false, false, this);
|
|
if (!type)
|
|
return;
|
|
|
|
if (!cx->typeInferenceEnabled()) {
|
|
newType = type;
|
|
setDelegate();
|
|
return;
|
|
}
|
|
|
|
AutoEnterTypeInference enter(cx);
|
|
|
|
if (!getType()->unknownProperties()) {
|
|
/* Update the possible 'new' types for all prototype objects sharing the same type object. */
|
|
TypeSet *types = getType()->getProperty(cx, JSID_EMPTY, true);
|
|
if (types)
|
|
types->addType(cx, (jstype) type);
|
|
}
|
|
|
|
if (newScript && !type->unknownProperties()) {
|
|
/* Strawman object to add properties to and watch for duplicates. */
|
|
JSObject *baseobj = NewBuiltinClassInstance(cx, &js_ObjectClass, gc::FINALIZE_OBJECT16);
|
|
if (!baseobj)
|
|
return;
|
|
|
|
Vector<TypeNewScript::Initializer> initializerList(cx);
|
|
AnalyzeNewScriptProperties(cx, type, newScript, &baseobj, &initializerList);
|
|
if (baseobj && baseobj->slotSpan() > 0 && !type->newScriptCleared) {
|
|
js::gc::FinalizeKind kind = js::gc::GetGCObjectKind(baseobj->slotSpan());
|
|
|
|
/* We should not have overflowed the maximum number of fixed slots for an object. */
|
|
JS_ASSERT(js::gc::GetGCKindSlots(kind) >= baseobj->slotSpan());
|
|
|
|
TypeNewScript::Initializer done(TypeNewScript::Initializer::DONE, 0);
|
|
|
|
/*
|
|
* The base object was created with a different type and
|
|
* finalize kind than we will use for subsequent new objects.
|
|
* Generate an object with the appropriate final shape.
|
|
*/
|
|
baseobj = NewReshapedObject(cx, type, baseobj->getParent(), kind,
|
|
baseobj->lastProperty());
|
|
if (!baseobj ||
|
|
!type->addDefiniteProperties(cx, baseobj) ||
|
|
!initializerList.append(done)) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
|
|
size_t numBytes = sizeof(TypeNewScript)
|
|
+ (initializerList.length() * sizeof(TypeNewScript::Initializer));
|
|
type->newScript = (TypeNewScript *) cx->calloc_(numBytes);
|
|
if (!type->newScript) {
|
|
cx->compartment->types.setPendingNukeTypes(cx);
|
|
return;
|
|
}
|
|
|
|
type->newScript->script = newScript;
|
|
type->newScript->finalizeKind = unsigned(kind);
|
|
type->newScript->shape = baseobj->lastProperty();
|
|
|
|
type->newScript->initializerList = (TypeNewScript::Initializer *)
|
|
((char *) type->newScript + sizeof(TypeNewScript));
|
|
PodCopy(type->newScript->initializerList, initializerList.begin(), initializerList.length());
|
|
}
|
|
}
|
|
|
|
newType = type;
|
|
setDelegate();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Tracing
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
types::TypeObject::trace(JSTracer *trc)
|
|
{
|
|
JS_ASSERT(!marked);
|
|
|
|
/*
|
|
* Only mark types if the Mark/Sweep GC is running; the bit won't be cleared
|
|
* by the cycle collector.
|
|
*/
|
|
if (trc->context->runtime->gcMarkAndSweep)
|
|
marked = true;
|
|
|
|
#ifdef DEBUG
|
|
gc::MarkId(trc, name_, "type_name");
|
|
#endif
|
|
|
|
unsigned count = getPropertyCount();
|
|
for (unsigned i = 0; i < count; i++) {
|
|
Property *prop = getProperty(i);
|
|
if (prop)
|
|
gc::MarkId(trc, prop->id, "type_prop");
|
|
}
|
|
|
|
if (emptyShapes) {
|
|
int count = gc::FINALIZE_OBJECT_LAST - gc::FINALIZE_OBJECT0 + 1;
|
|
for (int i = 0; i < count; i++) {
|
|
if (emptyShapes[i])
|
|
MarkShape(trc, emptyShapes[i], "empty_shape");
|
|
}
|
|
}
|
|
|
|
if (proto)
|
|
gc::MarkObject(trc, *proto, "type_proto");
|
|
|
|
if (singleton)
|
|
gc::MarkObject(trc, *singleton, "type_singleton");
|
|
|
|
if (newScript) {
|
|
js_TraceScript(trc, newScript->script);
|
|
gc::MarkShape(trc, newScript->shape, "new_shape");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Condense any constraints on a type set which were generated during analysis
|
|
* of a script, and sweep all type objects and references to type objects
|
|
* which no longer exist.
|
|
*/
|
|
bool
|
|
TypeSet::CondenseSweepTypeSet(JSContext *cx, JSCompartment *compartment,
|
|
ScriptSet &condensed, TypeSet *types)
|
|
{
|
|
/*
|
|
* This function is called from GC, and cannot malloc any data that could
|
|
* trigger a reentrant GC. The only allocation that can happen here is
|
|
* the construction of condensed constraints and tables for hash sets.
|
|
* Both of these use off-the-books malloc rather than cx->malloc, and thus
|
|
* do not contribute towards the runtime's overall malloc bytes.
|
|
*/
|
|
JS_ASSERT(!types->intermediate());
|
|
|
|
if (types->objectCount >= 2) {
|
|
bool removed = false;
|
|
unsigned objectCapacity = HashSetCapacity(types->objectCount);
|
|
for (unsigned i = 0; i < objectCapacity; i++) {
|
|
TypeObject *object = types->objectSet[i];
|
|
if (object && !object->marked) {
|
|
/*
|
|
* If the object has unknown properties, instead of removing it
|
|
* replace it with the compartment's empty type object. This is
|
|
* needed to handle mutable __proto__ --- the type object in
|
|
* the set may no longer be used but there could be a JSObject
|
|
* which originally had the type and was changed to a different
|
|
* type object with unknown properties.
|
|
*/
|
|
if (object->unknownProperties())
|
|
types->objectSet[i] = &compartment->types.typeEmpty;
|
|
else
|
|
types->objectSet[i] = NULL;
|
|
removed = true;
|
|
}
|
|
}
|
|
if (removed) {
|
|
/* Reconstruct the type set to re-resolve hash collisions. */
|
|
TypeObject **oldArray = types->objectSet;
|
|
types->objectSet = NULL;
|
|
types->objectCount = 0;
|
|
for (unsigned i = 0; i < objectCapacity; i++) {
|
|
TypeObject *object = oldArray[i];
|
|
if (object) {
|
|
TypeObject **pentry = HashSetInsert<TypeObject *,TypeObject,TypeObjectKey>
|
|
(cx, types->objectSet, types->objectCount, object, false);
|
|
if (pentry)
|
|
*pentry = object;
|
|
}
|
|
}
|
|
cx->free_(oldArray);
|
|
}
|
|
} else if (types->objectCount == 1) {
|
|
TypeObject *object = (TypeObject*) types->objectSet;
|
|
if (!object->marked) {
|
|
if (object->unknownProperties()) {
|
|
types->objectSet = (TypeObject**) &compartment->types.typeEmpty;
|
|
} else {
|
|
types->objectSet = NULL;
|
|
types->objectCount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
TypeConstraint *constraint = types->constraintList;
|
|
types->constraintList = NULL;
|
|
|
|
/*
|
|
* Keep track of all the scripts we have found or generated
|
|
* condensed constraints for, in the condensed table. We reuse the
|
|
* same table for each type set to avoid extra initialization cost,
|
|
* but the table is emptied after each set is processed.
|
|
*/
|
|
|
|
while (constraint) {
|
|
TypeConstraint *next = constraint->next;
|
|
|
|
TypeObject *object = constraint->persistentObject();
|
|
if (object) {
|
|
/*
|
|
* Constraint propagating data between objects. If the target
|
|
* is not being collected (these are weak references) then
|
|
* keep the constraint.
|
|
*/
|
|
if (object->marked) {
|
|
constraint->next = types->constraintList;
|
|
types->constraintList = constraint;
|
|
} else {
|
|
cx->delete_(constraint);
|
|
}
|
|
constraint = next;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Throw away constraints propagating types into scripts which are
|
|
* about to be destroyed.
|
|
*/
|
|
JSScript *script = constraint->script;
|
|
if (script->isCachedEval ||
|
|
(script->u.object && IsAboutToBeFinalized(cx, script->u.object)) ||
|
|
(script->fun && IsAboutToBeFinalized(cx, script->fun))) {
|
|
if (constraint->condensed())
|
|
cx->delete_(constraint);
|
|
constraint = next;
|
|
continue;
|
|
}
|
|
|
|
ScriptSet::AddPtr p = condensed.lookupForAdd(script);
|
|
if (!p) {
|
|
if (!condensed.add(p, script) || !types->addCondensed(cx, script)) {
|
|
SwitchToCompartment enterCompartment(cx, compartment);
|
|
AutoEnterTypeInference enter(cx);
|
|
compartment->types.setPendingNukeTypes(cx);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (constraint->condensed())
|
|
cx->free_(constraint);
|
|
constraint = next;
|
|
}
|
|
|
|
condensed.clear();
|
|
return true;
|
|
}
|
|
|
|
/* Remove to-be-destroyed objects from the list of instances of a type object. */
|
|
static inline void
|
|
PruneInstanceObjects(TypeObject *object)
|
|
{
|
|
TypeObject **pinstance = &object->instanceList;
|
|
while (*pinstance) {
|
|
if ((*pinstance)->marked)
|
|
pinstance = &(*pinstance)->instanceNext;
|
|
else
|
|
*pinstance = (*pinstance)->instanceNext;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
CondenseTypeObjectList(JSContext *cx, JSCompartment *compartment, TypeObject *objects)
|
|
{
|
|
TypeSet::ScriptSet condensed;
|
|
if (!condensed.init()) {
|
|
SwitchToCompartment enterCompartment(cx, compartment);
|
|
AutoEnterTypeInference enter(cx);
|
|
compartment->types.setPendingNukeTypes(cx);
|
|
return false;
|
|
}
|
|
|
|
TypeObject *object = objects;
|
|
while (object) {
|
|
if (!object->marked) {
|
|
/*
|
|
* Leave all constraints and references to to-be-destroyed objects in.
|
|
* We will release all memory when sweeping the object.
|
|
*/
|
|
object = object->next;
|
|
continue;
|
|
}
|
|
|
|
PruneInstanceObjects(object);
|
|
|
|
/* Condense type sets for all properties of the object. */
|
|
unsigned count = object->getPropertyCount();
|
|
for (unsigned i = 0; i < count; i++) {
|
|
Property *prop = object->getProperty(i);
|
|
if (prop && !TypeSet::CondenseSweepTypeSet(cx, compartment, condensed, &prop->types))
|
|
return false;
|
|
}
|
|
|
|
object = object->next;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JSCompartment::condenseTypes(JSContext *cx)
|
|
{
|
|
PruneInstanceObjects(&types.typeEmpty);
|
|
|
|
return CondenseTypeObjectList(cx, this, types.objects);
|
|
}
|
|
|
|
static void
|
|
DestroyProperty(JSContext *cx, Property *prop)
|
|
{
|
|
prop->types.destroy(cx);
|
|
cx->delete_(prop);
|
|
}
|
|
|
|
static void
|
|
SweepTypeObjectList(JSContext *cx, TypeObject *&objects)
|
|
{
|
|
TypeObject **pobject = &objects;
|
|
while (*pobject) {
|
|
TypeObject *object = *pobject;
|
|
if (object->marked) {
|
|
object->marked = false;
|
|
pobject = &object->next;
|
|
} else {
|
|
if (object->emptyShapes)
|
|
cx->free_(object->emptyShapes);
|
|
*pobject = object->next;
|
|
|
|
unsigned count = object->getPropertyCount();
|
|
for (unsigned i = 0; i < count; i++) {
|
|
Property *prop = object->getProperty(i);
|
|
if (prop)
|
|
DestroyProperty(cx, prop);
|
|
}
|
|
if (count >= 2)
|
|
cx->free_(object->propertySet);
|
|
|
|
cx->delete_(object);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
TypeCompartment::sweep(JSContext *cx)
|
|
{
|
|
if (typeEmpty.marked) {
|
|
typeEmpty.marked = false;
|
|
} else if (typeEmpty.emptyShapes) {
|
|
cx->free_(typeEmpty.emptyShapes);
|
|
typeEmpty.emptyShapes = NULL;
|
|
}
|
|
|
|
/*
|
|
* Iterate through the array/object type tables and remove all entries
|
|
* referencing collected data. These tables only hold weak references.
|
|
*/
|
|
|
|
if (arrayTypeTable) {
|
|
for (ArrayTypeTable::Enum e(*arrayTypeTable); !e.empty(); e.popFront()) {
|
|
const ArrayTableKey &key = e.front().key;
|
|
TypeObject *obj = e.front().value;
|
|
JS_ASSERT(obj->proto == key.proto);
|
|
|
|
bool remove = false;
|
|
if (TypeIsObject(key.type) && !((TypeObject *)key.type)->marked)
|
|
remove = true;
|
|
if (!obj->marked)
|
|
remove = true;
|
|
|
|
if (remove)
|
|
e.removeFront();
|
|
}
|
|
}
|
|
|
|
if (objectTypeTable) {
|
|
for (ObjectTypeTable::Enum e(*objectTypeTable); !e.empty(); e.popFront()) {
|
|
const ObjectTableKey &key = e.front().key;
|
|
const ObjectTableEntry &entry = e.front().value;
|
|
JS_ASSERT(entry.object->proto == key.proto);
|
|
|
|
bool remove = false;
|
|
if (!entry.object->marked || !entry.newShape->isMarked())
|
|
remove = true;
|
|
for (unsigned i = 0; !remove && i < key.nslots; i++) {
|
|
if (JSID_IS_STRING(key.ids[i])) {
|
|
JSString *str = JSID_TO_STRING(key.ids[i]);
|
|
if (!str->isStaticAtom() && !str->isMarked())
|
|
remove = true;
|
|
}
|
|
if (TypeIsObject(entry.types[i]) && !((TypeObject *)entry.types[i])->marked)
|
|
remove = true;
|
|
}
|
|
|
|
if (remove) {
|
|
cx->free_(key.ids);
|
|
cx->free_(entry.types);
|
|
e.removeFront();
|
|
}
|
|
}
|
|
}
|
|
|
|
SweepTypeObjectList(cx, objects);
|
|
}
|
|
|
|
TypeCompartment::~TypeCompartment()
|
|
{
|
|
if (pendingArray)
|
|
Foreground::free_(pendingArray);
|
|
|
|
if (arrayTypeTable)
|
|
Foreground::delete_(arrayTypeTable);
|
|
|
|
if (objectTypeTable)
|
|
Foreground::delete_(objectTypeTable);
|
|
}
|
|
|
|
bool
|
|
JSScript::condenseTypes(JSContext *cx)
|
|
{
|
|
if (!CondenseTypeObjectList(cx, compartment, typeObjects))
|
|
return false;
|
|
|
|
if (typeArray) {
|
|
TypeSet::ScriptSet condensed;
|
|
if (!condensed.init()) {
|
|
SwitchToCompartment enterCompartment(cx, compartment);
|
|
AutoEnterTypeInference enter(cx);
|
|
compartment->types.setPendingNukeTypes(cx);
|
|
return false;
|
|
}
|
|
|
|
unsigned num = nTypeSets + TotalSlots(this) + bindings.countUpvars();
|
|
|
|
if (isCachedEval ||
|
|
(u.object && IsAboutToBeFinalized(cx, u.object)) ||
|
|
(fun && IsAboutToBeFinalized(cx, fun))) {
|
|
for (unsigned i = 0; i < num; i++)
|
|
typeArray[i].destroy(cx);
|
|
cx->free_(typeArray);
|
|
typeArray = NULL;
|
|
} else {
|
|
for (unsigned i = 0; i < num; i++) {
|
|
if (!TypeSet::CondenseSweepTypeSet(cx, compartment, condensed, &typeArray[i]))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
TypeIntermediate **presult = &intermediateTypes;
|
|
while (*presult) {
|
|
TypeIntermediate *result = *presult;
|
|
if (result->sweep(cx, compartment)) {
|
|
presult = &result->next;
|
|
} else {
|
|
*presult = result->next;
|
|
cx->delete_(result);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
JSScript::sweepAnalysis(JSContext *cx)
|
|
{
|
|
SweepTypeObjectList(cx, typeObjects);
|
|
|
|
if (analysis_ && !compartment->activeAnalysis) {
|
|
/*
|
|
* The analysis and everything in it is allocated using the analysis
|
|
* pool in the compartment (to be cleared shortly).
|
|
*/
|
|
analysis_ = NULL;
|
|
}
|
|
}
|