--- a/js/src/jsanalyze.h
+++ b/js/src/jsanalyze.h
@@ -966,26 +966,37 @@ class ScriptAnalysis
return array + which;
}
types::TypeSet *pushedTypes(const jsbytecode *pc, uint32 which) {
return pushedTypes(pc - script->code, which);
}
types::TypeBarrier *typeBarriers(uint32 offset) {
if (getCode(offset).typeBarriers)
- pruneTypeBarriers(NULL, offset);
+ pruneTypeBarriers(offset);
return getCode(offset).typeBarriers;
}
types::TypeBarrier *typeBarriers(const jsbytecode *pc) {
return typeBarriers(pc - script->code);
}
void addTypeBarrier(JSContext *cx, const jsbytecode *pc,
types::TypeSet *target, types::jstype type);
- void pruneTypeBarriers(JSContext *removecx, uint32 offset);
+ /* Remove obsolete type barriers at the given offset. */
+ void pruneTypeBarriers(uint32 offset);
+
+ /*
+ * Remove still-active type barriers at the given offset. If 'all' is set,
+ * then all barriers are removed, otherwise only those deemed excessive
+ * are removed.
+ */
+ void breakTypeBarriers(JSContext *cx, uint32 offset, bool all);
+
+ /* Break all type barriers used in computing v. */
+ void breakTypeBarriersSSA(JSContext *cx, const SSAValue &v);
inline void addPushedType(JSContext *cx, uint32 offset, uint32 which, types::jstype type);
types::TypeSet *getValueTypes(const SSAValue &v) {
switch (v.kind()) {
case SSAValue::PUSHED:
return pushedTypes(v.pushedOffset(), v.pushedIndex());
case SSAValue::VAR:
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4332,115 +4332,94 @@ JS_CloneFunctionObject(JSContext *cx, JS
return clone;
}
JS_PUBLIC_API(void)
JS_TypeHandlerDynamic(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (site->returnTypes)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
}
JS_PUBLIC_API(void)
JS_TypeHandlerVoid(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (site->returnTypes) {
- if (site->isNew)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
- site->returnTypes->addType(cx, TYPE_UNDEFINED);
- }
+ if (site->isNew)
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->returnTypes->addType(cx, TYPE_UNDEFINED);
}
JS_PUBLIC_API(void)
JS_TypeHandlerNull(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (site->returnTypes) {
- if (site->isNew)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
- site->returnTypes->addType(cx, TYPE_NULL);
- }
+ if (site->isNew)
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->returnTypes->addType(cx, TYPE_NULL);
}
JS_PUBLIC_API(void)
JS_TypeHandlerBool(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (site->returnTypes) {
- if (site->isNew)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
- site->returnTypes->addType(cx, TYPE_BOOLEAN);
- }
+ if (site->isNew)
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->returnTypes->addType(cx, TYPE_BOOLEAN);
}
JS_PUBLIC_API(void)
JS_TypeHandlerInt(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (site->returnTypes) {
- if (site->isNew)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
- site->returnTypes->addType(cx, TYPE_INT32);
- }
+ if (site->isNew)
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->returnTypes->addType(cx, TYPE_INT32);
}
JS_PUBLIC_API(void)
JS_TypeHandlerFloat(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (site->returnTypes) {
- if (site->isNew)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
- site->returnTypes->addType(cx, TYPE_DOUBLE);
- }
+ if (site->isNew)
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->returnTypes->addType(cx, TYPE_DOUBLE);
}
JS_PUBLIC_API(void)
JS_TypeHandlerString(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (site->returnTypes) {
- if (site->isNew)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
- site->returnTypes->addType(cx, TYPE_STRING);
- }
+ if (site->isNew)
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->returnTypes->addType(cx, TYPE_STRING);
}
JS_PUBLIC_API(void)
JS_TypeHandlerNew(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeFunction *fun = Valueify(jsfun);
TypeCallsite *site = Valueify(jssite);
- if (!site->returnTypes)
- return;
-
TypeSet *prototypeTypes =
fun->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom), true);
if (!prototypeTypes)
return;
prototypeTypes->addNewObject(cx, site->script, fun, site->returnTypes);
}
JS_PUBLIC_API(void)
JS_TypeHandlerThis(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (site->returnTypes) {
- if (site->isNew)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
- if (site->thisTypes)
- site->thisTypes->addSubset(cx, site->script, site->returnTypes);
- else
- site->returnTypes->addType(cx, site->thisType);
- }
+ if (site->isNew)
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->thisTypes->addSubset(cx, site->script, site->returnTypes);
}
JS_PUBLIC_API(JSObject *)
JS_GetFunctionObject(JSFunction *fun)
{
return FUN_OBJECT(fun);
}
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3217,131 +3217,96 @@ array_every(JSContext *cx, uintN argc, V
* on arrays are both handled by write barriers within the natives.
*/
static void
array_TypeSort(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (!site->forceThisTypes(cx))
- return;
-
- if (site->returnTypes) {
- if (site->isNew)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
- site->thisTypes->addSubset(cx, site->script, site->returnTypes);
- }
+ if (site->isNew)
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->thisTypes->addSubset(cx, site->script, site->returnTypes);
}
static void
array_TypeInsert(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (site->returnTypes) {
- /* The return type is an integer (array length). */
- if (site->isNew)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
- site->returnTypes->addType(cx, TYPE_INT32);
- }
-
- if (!site->forceThisTypes(cx))
- return;
+ /* The return type is an integer (array length). */
+ if (site->isNew)
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->returnTypes->addType(cx, TYPE_INT32);
for (size_t ind = 0; ind < site->argumentCount; ind++) {
site->thisTypes->addSetProperty(cx, site->script, site->pc,
site->argumentTypes[ind], JSID_VOID);
}
}
static void
array_TypeRemove(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (!site->returnTypes)
- return;
-
if (site->isNew)
site->returnTypes->addType(cx, TYPE_UNKNOWN);
-
- if (!site->forceThisTypes(cx))
- return;
site->thisTypes->addGetProperty(cx, site->script, site->pc, site->returnTypes, JSID_VOID);
}
static void
array_TypeSplice(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (!site->forceThisTypes(cx))
- return;
-
- if (site->returnTypes) {
- /* Treat the returned array the same as the 'this' array. */
- if (site->isNew)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
- site->thisTypes->addSubset(cx, site->script, site->returnTypes);
- }
+ /* Treat the returned array the same as the 'this' array. */
+ if (site->isNew)
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->thisTypes->addSubset(cx, site->script, site->returnTypes);
/* All arguments beyond the first two are new array elements. */
for (size_t ind = 2; ind < site->argumentCount; ind++) {
site->thisTypes->addSetProperty(cx, site->script, site->pc,
site->argumentTypes[ind], JSID_VOID);
}
}
static void
array_TypeConcat(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
if (!site->hasGlobal()) {
- if (site->returnTypes)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
return;
}
- if (!site->forceThisTypes(cx))
- return;
-
- if (site->returnTypes) {
- if (site->isNew)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
- site->thisTypes->addSubset(cx, site->script, site->returnTypes);
- }
+ if (site->isNew)
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->thisTypes->addSubset(cx, site->script, site->returnTypes);
}
static void
array_TypeSlice(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (!site->forceThisTypes(cx))
- return;
-
- if (site->returnTypes) {
- if (site->isNew)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
- site->thisTypes->addFilterPrimitives(cx, site->script, site->returnTypes, false);
- }
+ if (site->isNew)
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->thisTypes->addFilterPrimitives(cx, site->script, site->returnTypes, false);
}
/* Handler for all higher order array builtins. */
static void
array_TypeExtra(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite,
ArrayExtraMode mode)
{
TypeCallsite *site = Valueify(jssite);
- if (!site->returnTypes)
- return;
-
if (site->isNew)
site->returnTypes->addType(cx, TYPE_UNKNOWN);
switch (mode) {
case FOREACH:
site->returnTypes->addType(cx, TYPE_UNDEFINED);
break;
@@ -3499,26 +3464,24 @@ js_Array(JSContext *cx, uintN argc, Valu
* the resulting array.
*/
static void
array_TypeNew(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
if (!site->hasGlobal()) {
- if (site->returnTypes)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
return;
}
TypeObject *object = site->getInitObject(cx, true);
if (!object)
return;
- if (site->returnTypes)
- site->returnTypes->addType(cx, (jstype) object);
+ site->returnTypes->addType(cx, (jstype) object);
if (object->unknownProperties())
return;
TypeSet *indexTypes = object->getProperty(cx, JSID_VOID, true);
if (!indexTypes)
return;
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -494,51 +494,51 @@ TypeSet::addCondensed(JSContext *cx, JSS
add(cx, constraint, false);
return true;
}
/* Constraints for reads/writes on object properties. */
class TypeConstraintProp : public TypeConstraint
{
public:
- const jsbytecode *pc;
+ 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, const jsbytecode *pc,
+ 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, const jsbytecode *pc,
+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, const jsbytecode *pc,
+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 determining the 'this' object at sites invoked using 'new'. */
class TypeConstraintNewObject : public TypeConstraint
{
@@ -622,16 +622,53 @@ public:
};
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:
+ jstype type;
+
+ TypeConstraintPropagateThis(JSScript *script, jstype type)
+ : TypeConstraint("propagatethis", script), type(type)
+ {}
+
+ void newType(JSContext *cx, TypeSet *source, jstype type);
+};
+
+void
+TypeSet::addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, jstype type)
+{
+ /*
+ * If this will definitely be popped by a JSOP_NEW, don't add a constraint
+ * to modify the 'this' types of callees. The initial 'this' value will be
+ * outright ignored.
+ */
+ SSAValue calleev = SSAValue::PushedValue(pc - script->code, 0);
+ SSAUseChain *uses = script->analysis(cx)->useChain(calleev);
+
+ if (uses && !uses->next && uses->popped) {
+ jsbytecode *callpc = script->code + uses->offset;
+ UntrapOpcode untrap(cx, script, callpc);
+ if (JSOp(*callpc) == JSOP_NEW)
+ return;
+ }
+
+ add(cx, ArenaNew<TypeConstraintPropagateThis>(cx->compartment->pool, script, 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;
@@ -655,54 +692,80 @@ public:
void
TypeSet::addFilterPrimitives(JSContext *cx, JSScript *script, TypeSet *target, bool onlyNullVoid)
{
add(cx, ArenaNew<TypeConstraintFilterPrimitive>(cx->compartment->pool,
script, target, onlyNullVoid));
}
-/*
- * 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::pruneTypeBarriers(JSContext *removecx, uint32 offset)
+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 if (removecx && TypeIsObject(barrier->type) &&
- barrier->target->getObjectCount() >= BARRIER_OBJECT_LIMIT) {
+ } 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(removecx, barrier->type);
+ 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:
- const jsbytecode *pc;
+ jsbytecode *pc;
TypeSet *target;
- TypeConstraintSubsetBarrier(JSScript *script, const 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)) {
@@ -710,17 +773,17 @@ public:
return;
}
target->addType(cx, type);
}
};
void
-TypeSet::addSubsetBarrier(JSContext *cx, JSScript *script, const jsbytecode *pc, TypeSet *target)
+TypeSet::addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target)
{
add(cx, ArenaNew<TypeConstraintSubsetBarrier>(cx->compartment->pool, script, pc, target));
}
/*
* Type constraint which marks the result of 'for in' loops as unknown if the
* iterated value could be a generator.
*/
@@ -797,17 +860,17 @@ GetPropertyObject(JSContext *cx, JSScrip
return object;
}
/*
* 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, const jsbytecode *pc, TypeObject *object,
+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;
@@ -862,18 +925,29 @@ TypeConstraintProp::newType(JSContext *c
if (assign)
cx->compartment->types.monitorBytecode(cx, script, pc - script->code);
else
target->addType(cx, TYPE_UNKNOWN);
return;
}
TypeObject *object = GetPropertyObject(cx, script, type);
- if (object)
+ if (object) {
PropertyAccess(cx, script, pc, object, assign, target, id);
+
+ UntrapOpcode untrap(cx, script, pc);
+ if (!object->unknownProperties() &&
+ (JSOp(*pc) == JSOP_CALLPROP || JSOp(*pc) == JSOP_CALLELEM)) {
+ JS_ASSERT(!assign);
+ TypeSet *types = object->getProperty(cx, id, false);
+ if (!types)
+ return;
+ types->addPropagateThis(cx, script, pc, type);
+ }
+ }
}
void
TypeConstraintNewObject::newType(JSContext *cx, TypeSet *source, jstype type)
{
if (type == TYPE_UNKNOWN) {
target->addType(cx, TYPE_UNKNOWN);
return;
@@ -906,17 +980,17 @@ TypeConstraintNewObject::newType(JSConte
target->addType(cx, (jstype) object);
}
}
void
TypeConstraintCall::newType(JSContext *cx, TypeSet *source, jstype type)
{
JSScript *script = callsite->script;
- const jsbytecode *pc = callsite->pc;
+ 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))
@@ -1004,45 +1078,66 @@ TypeConstraintCall::newType(JSContext *c
}
/* 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);
}
- /* Add a binding for the receiver object of the call. */
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).
*/
- if (callsite->returnTypes) {
- callee->thisTypes()->addSubset(cx, script, callsite->returnTypes);
- callee->returnTypes()->addFilterPrimitives(cx, script,
- callsite->returnTypes, false);
- }
+ callee->thisTypes()->addSubset(cx, script, callsite->returnTypes);
+ callee->returnTypes()->addFilterPrimitives(cx, script, callsite->returnTypes, false);
} else {
- if (callsite->thisTypes) {
- /* Add a binding for the receiver object of the call. */
- callsite->thisTypes->addSubset(cx, script, callee->thisTypes());
- } else {
- JS_ASSERT(callsite->thisType != TYPE_NULL);
- callee->thisTypes()->addType(cx, callsite->thisType);
- }
-
- /* Add a binding for the return value of the call. */
- if (callsite->returnTypes)
- callee->returnTypes()->addSubset(cx, script, callsite->returnTypes);
+ /*
+ * 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)
+{
+ /*
+ * Ignore callees that are calling natives or where the callee is unknown;
+ * the latter will be marked as monitored by a TypeConstraintCall.
+ */
+ if (type == TYPE_UNKNOWN || !TypeIsObject(type))
+ return;
+
+ 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).
@@ -2691,17 +2786,29 @@ TypeObject::markUnknown(JSContext *cx)
prop->types.setOwnProperty(cx, true);
}
}
}
void
TypeObject::clearNewScript(JSContext *cx)
{
- JS_ASSERT(newScript);
+ 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
@@ -3131,18 +3238,20 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
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)
+ 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.
*/
@@ -3167,18 +3276,20 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
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)
+ 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: {
@@ -3208,26 +3319,30 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
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)
+ 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)
+ 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)) {
@@ -3239,18 +3354,20 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
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)
+ 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)) {
@@ -3305,17 +3422,24 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
break;
}
case JSOP_LENGTH:
case JSOP_GETPROP:
case JSOP_CALLPROP: {
jsid id = GetAtomId(cx, script, pc, 0);
TypeSet *seen = script->bytecodeTypes(pc);
+
+ /*
+ * For JSOP_CALLPROP, this will inspect the pc and add PropagateThis
+ * constraints. Different types for the receiver may be correlated with
+ * different callee scripts, and we want to retain such correlations.
+ */
poppedTypes(pc, 0)->addGetProperty(cx, script, pc, seen, 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;
}
@@ -3331,17 +3455,20 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
/*
* 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);
+
+ /* Ditto the JSOP_CALLPROP case for propagating 'this'. */
poppedTypes(pc, 1)->addGetProperty(cx, script, pc, seen, 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;
}
@@ -3859,17 +3986,17 @@ class TypeConstraintClearDefiniteSingle
public:
TypeObject *object;
TypeConstraintClearDefiniteSingle(JSScript *script, TypeObject *object)
: TypeConstraint("baseClearDefinite", script), object(object)
{}
void newType(JSContext *cx, TypeSet *source, jstype type) {
- if (object->newScript && !source->getSingleObject())
+ 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.
*/
@@ -4068,32 +4195,39 @@ AnalyzeNewScriptProperties(JSContext *cx
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;
/*
- * Add constraints to clear definite properties from the type
+ * 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);
@@ -4495,17 +4629,17 @@ JSObject::makeNewType(JSContext *cx, JSS
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) {
+ 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);
/*
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -352,28 +352,29 @@ class TypeSet
void setDefinite(unsigned slot) {
JS_ASSERT(slot <= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT));
typeFlags |= TYPE_FLAG_DEFINITE_PROPERTY | (slot << TYPE_FLAG_DEFINITE_SHIFT);
}
/* Add specific kinds of constraints to this set. */
inline void add(JSContext *cx, TypeConstraint *constraint, bool callExisting = true);
void addSubset(JSContext *cx, JSScript *script, TypeSet *target);
- void addGetProperty(JSContext *cx, JSScript *script, const jsbytecode *pc,
+ void addGetProperty(JSContext *cx, JSScript *script, jsbytecode *pc,
TypeSet *target, jsid id);
- void addSetProperty(JSContext *cx, JSScript *script, const jsbytecode *pc,
+ void addSetProperty(JSContext *cx, JSScript *script, jsbytecode *pc,
TypeSet *target, jsid id);
void addNewObject(JSContext *cx, JSScript *script, TypeFunction *fun, TypeSet *target);
void addCall(JSContext *cx, TypeCallsite *site);
void addArith(JSContext *cx, JSScript *script,
TypeSet *target, TypeSet *other = NULL);
void addTransformThis(JSContext *cx, JSScript *script, TypeSet *target);
+ void addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, jstype type);
void addFilterPrimitives(JSContext *cx, JSScript *script,
TypeSet *target, bool onlyNullVoid);
- void addSubsetBarrier(JSContext *cx, JSScript *script, const jsbytecode *pc, TypeSet *target);
+ void addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target);
void addBaseSubset(JSContext *cx, TypeObject *object, TypeSet *target);
bool addCondensed(JSContext *cx, JSScript *script);
/*
* Make an intermediate type set with the specified debugging name,
* not embedded in another structure.
*/
@@ -570,16 +571,19 @@ struct TypeObject
TypeObjectFlags flags;
/* Whether this is a function object, and may be cast into TypeFunction. */
bool isFunction;
/* Mark bit for GC. */
bool marked;
+ /* If set, newScript information should not be installed on this object. */
+ bool newScriptCleared;
+
/*
* If non-NULL, objects of this type have always been constructed using
* 'new' on the specified script, which adds some number of properties to
* the object in a definite order before the object escapes.
*/
TypeNewScript *newScript;
/*
@@ -728,40 +732,34 @@ struct TypeFunction : public TypeObject
/*
* Type information about a callsite. this is separated from the bytecode
* information itself so we can handle higher order functions not called
* directly via a bytecode.
*/
struct TypeCallsite
{
JSScript *script;
- const jsbytecode *pc;
+ jsbytecode *pc;
/* Whether this is a 'NEW' call. */
bool isNew;
/* Types of each argument to the call. */
TypeSet **argumentTypes;
unsigned argumentCount;
/* Types of the this variable. */
TypeSet *thisTypes;
- /* Any definite type for 'this'. */
- jstype thisType;
-
/* Type set receiving the return value of this call. */
TypeSet *returnTypes;
- inline TypeCallsite(JSContext *cx, JSScript *script, const jsbytecode *pc,
+ inline TypeCallsite(JSContext *cx, JSScript *script, jsbytecode *pc,
bool isNew, unsigned argumentCount);
- /* Force creation of thisTypes. */
- inline bool forceThisTypes(JSContext *cx);
-
/* Get the new object at this callsite. */
inline TypeObject* getInitObject(JSContext *cx, bool isArray);
inline bool hasGlobal();
};
struct ArrayTableKey;
typedef HashMap<ArrayTableKey,TypeObject*,ArrayTableKey,SystemAllocPolicy> ArrayTypeTable;
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -87,16 +87,18 @@ GetValueType(JSContext *cx, const Value
/*
* Get the canonical representation of an id to use when doing inference. This
* maintains the constraint that if two different jsids map to the same property
* in JS (e.g. 3 and "3"), they have the same type representation.
*/
inline jsid
MakeTypeId(JSContext *cx, jsid id)
{
+ JS_ASSERT(!JSID_IS_EMPTY(id));
+
/*
* All integers must map to the aggregate property for index types, including
* negative integers.
*/
if (JSID_IS_INT(id))
return JSID_VOID;
/*
@@ -1232,36 +1234,25 @@ TypeSet::make(JSContext *cx, const char
return res;
}
/////////////////////////////////////////////////////////////////////
// TypeCallsite
/////////////////////////////////////////////////////////////////////
inline
-TypeCallsite::TypeCallsite(JSContext *cx, JSScript *script, const jsbytecode *pc,
+TypeCallsite::TypeCallsite(JSContext *cx, JSScript *script, jsbytecode *pc,
bool isNew, unsigned argumentCount)
: script(script), pc(pc), isNew(isNew), argumentCount(argumentCount),
- thisTypes(NULL), thisType(0), returnTypes(NULL)
+ thisTypes(NULL), returnTypes(NULL)
{
/* Caller must check for failure. */
argumentTypes = ArenaArray<TypeSet*>(cx->compartment->pool, argumentCount);
}
-inline bool
-TypeCallsite::forceThisTypes(JSContext *cx)
-{
- if (thisTypes)
- return true;
- thisTypes = TypeSet::make(cx, "site_this");
- if (thisTypes)
- thisTypes->addType(cx, thisType);
- return thisTypes != NULL;
-}
-
inline TypeObject *
TypeCallsite::getInitObject(JSContext *cx, bool isArray)
{
TypeObject *type = script->getTypeInitObject(cx, pc, isArray);
if (!type)
cx->compartment->types.setPendingNukeTypes(cx);
return type;
}
@@ -1276,16 +1267,17 @@ TypeCallsite::hasGlobal()
// TypeObject
/////////////////////////////////////////////////////////////////////
inline TypeSet *
TypeObject::getProperty(JSContext *cx, jsid id, bool assign)
{
JS_ASSERT(cx->compartment->activeInference);
JS_ASSERT(JSID_IS_VOID(id) || JSID_IS_EMPTY(id) || JSID_IS_STRING(id));
+ JS_ASSERT_IF(!JSID_IS_EMPTY(id), id == MakeTypeId(cx, id));
JS_ASSERT_IF(JSID_IS_STRING(id), JSID_TO_STRING(id) != NULL);
JS_ASSERT(!unknownProperties());
Property **pprop = HashSetInsert<jsid,Property,Property>
(cx, propertySet, propertyCount, id, false);
if (!pprop || (!*pprop && !addProperty(cx, id, pprop)))
return NULL;
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -862,19 +862,16 @@ JS_DEFINE_TRCINFO_1(js_math_ceil,
(1, (static, DOUBLE, math_ceil_tn, DOUBLE, 1, nanojit::ACCSET_NONE)))
#endif /* JS_TRACER */
static void math_TypeArith(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
types::TypeCallsite *site = Valueify(jssite);
- if (!site->returnTypes)
- return;
-
if (site->isNew)
site->returnTypes->addType(cx, types::TYPE_UNKNOWN);
/* The zero-argument case will be handled as an overflow in the actual natives. */
for (size_t ind = 0; ind < site->argumentCount; ind++)
site->argumentTypes[ind]->addArith(cx, site->script, site->returnTypes);
}
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3899,22 +3899,20 @@ Class js_BlockClass = {
static void object_TypeNew(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
if (site->argumentCount == 0) {
TypeObject *object = site->getInitObject(cx, false);
if (!object)
return;
- if (site->returnTypes)
- site->returnTypes->addType(cx, (jstype) object);
+ site->returnTypes->addType(cx, (jstype) object);
} else {
/* The value is converted to an object, don't keep track of the return type. */
- if (site->returnTypes)
- site->returnTypes->addType(cx, TYPE_UNKNOWN);
+ site->returnTypes->addType(cx, TYPE_UNKNOWN);
}
}
JSObject *
js_InitObjectClass(JSContext *cx, JSObject *obj)
{
JSObject *proto = js_InitClass(cx, obj, NULL, &js_ObjectClass, js_Object, 1,
object_TypeNew,
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -3198,19 +3198,16 @@ js_String_getelem(JSContext* cx, JSStrin
JS_DEFINE_TRCINFO_1(str_concat,
(3, (extern, STRING_RETRY, js_ConcatStrings, CONTEXT, THIS_STRING, STRING,
1, nanojit::ACCSET_NONE)))
static void type_StringSplit(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
{
TypeCallsite *site = Valueify(jssite);
- if (!site->returnTypes)
- return;
-
if (!site->hasGlobal()) {
site->returnTypes->addType(cx, TYPE_UNKNOWN);
return;
}
if (site->isNew)
site->returnTypes->addType(cx, TYPE_UNKNOWN);
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -4205,22 +4205,23 @@ mjit::Compiler::jsop_getprop(JSAtom *ato
}
/*
* Check if we are accessing a known type which always has the property
* in a particular inline slot. Get the property directly in this case,
* without using an IC.
*/
JSOp op = JSOp(*PC);
+ jsid id = ATOM_TO_JSID(atom);
types::TypeSet *types = frame.extra(top).types;
if (op == JSOP_GETPROP && types && !types->unknown() && types->getObjectCount() == 1 &&
- !types->getObject(0)->unknownProperties()) {
+ !types->getObject(0)->unknownProperties() && id == types::MakeTypeId(cx, id)) {
JS_ASSERT(usePropCache);
types::TypeObject *object = types->getObject(0);
- types::TypeSet *propertyTypes = object->getProperty(cx, ATOM_TO_JSID(atom), false);
+ types::TypeSet *propertyTypes = object->getProperty(cx, id, false);
if (!propertyTypes)
return false;
if (propertyTypes->isDefiniteProperty() && !propertyTypes->isOwnProperty(cx, true)) {
types->addFreeze(cx);
uint32 slot = propertyTypes->definiteSlot();
bool isObject = top->isTypeKnown();
if (!isObject) {
Jump notObject = frame.testObject(Assembler::NotEqual, top);
@@ -4700,20 +4701,165 @@ mjit::Compiler::testSingletonPropertyTyp
JSObject *proto;
if (!js_GetClassPrototype(cx, globalObj, key, &proto, NULL))
return NULL;
return testSingletonProperty(proto, id);
}
bool
+mjit::Compiler::jsop_callprop_dispatch(JSAtom *atom)
+{
+ /*
+ * Check for a CALLPROP which is a dynamic dispatch: every value it can
+ * push is a singleton, and the pushed value is determined by the type of
+ * the object being accessed. Return true if the CALLPROP has been fully
+ * processed, false if no code was generated.
+ */
+ FrameEntry *top = frame.peek(-1);
+ if (top->isNotType(JSVAL_TYPE_OBJECT))
+ return false;
+
+ jsid id = ATOM_TO_JSID(atom);
+ if (id != types::MakeTypeId(cx, id))
+ return false;
+
+ types::TypeSet *pushedTypes = pushedTypeSet(0);
+ if (pushedTypes->unknown() || pushedTypes->baseFlags() != 0)
+ return false;
+
+ /* Check every pushed value is a singleton. */
+ for (unsigned i = 0; i < pushedTypes->getObjectCount(); i++) {
+ types::TypeObject *object = pushedTypes->getObject(i);
+ if (object && !object->singleton)
+ return false;
+ }
+
+ types::TypeSet *objTypes = analysis->poppedTypes(PC, 0);
+ if (objTypes->unknown() || objTypes->getObjectCount() == 0)
+ return false;
+
+ pushedTypes->addFreeze(cx);
+
+ /* Map each type in the object to the resulting pushed value. */
+ Vector<JSObject *> results(CompilerAllocPolicy(cx, *this));
+
+ /*
+ * For each type of the base object, check it has no 'own' property for the
+ * accessed id and that its prototype does have such a property.
+ */
+ uint32 last = 0;
+ for (unsigned i = 0; i < objTypes->getObjectCount(); i++) {
+ types::TypeObject *object = objTypes->getObject(i);
+ if (!object) {
+ results.append(NULL);
+ continue;
+ }
+ if (object->unknownProperties() || !object->proto)
+ return false;
+ types::TypeSet *ownTypes = object->getProperty(cx, id, false);
+ if (ownTypes->isOwnProperty(cx, false))
+ return false;
+
+ if (!testSingletonProperty(object->proto, id))
+ return false;
+
+ types::TypeSet *protoTypes = object->proto->getType()->getProperty(cx, id, false);
+ JSObject *singleton = protoTypes->getSingleton(cx);
+ if (!singleton)
+ return false;
+
+ results.append(singleton);
+ last = i;
+ }
+
+ if (oomInVector)
+ return false;
+
+ objTypes->addFreeze(cx);
+
+ /* Done filtering, now generate code which dispatches on the type. */
+
+ if (!top->isType(JSVAL_TYPE_OBJECT)) {
+ Jump notObject = frame.testObject(Assembler::NotEqual, top);
+ stubcc.linkExit(notObject, Uses(1));
+ }
+
+ RegisterID reg = frame.tempRegForData(top);
+ frame.pinReg(reg);
+ RegisterID pushreg = frame.allocReg();
+ frame.unpinReg(reg);
+
+ Address typeAddress(reg, offsetof(JSObject, type));
+
+ Vector<Jump> rejoins(CompilerAllocPolicy(cx, *this));
+ MaybeJump lastMiss;
+
+ for (unsigned i = 0; i < objTypes->getObjectCount(); i++) {
+ types::TypeObject *object = objTypes->getObject(i);
+ if (!object) {
+ JS_ASSERT(results[i] == NULL);
+ continue;
+ }
+ if (lastMiss.isSet())
+ lastMiss.get().linkTo(masm.label(), &masm);
+
+ /*
+ * Check that the pushed result is actually in the known pushed types
+ * for the bytecode; this bytecode may have type barriers. Redirect to
+ * the stub to update said pushed types.
+ */
+ if (!pushedTypes->hasType((types::jstype) results[i]->getType())) {
+ JS_ASSERT(hasTypeBarriers(PC));
+ if (i == last) {
+ stubcc.linkExit(masm.jump(), Uses(1));
+ break;
+ } else {
+ lastMiss.setJump(masm.branchPtr(Assembler::NotEqual, typeAddress, ImmPtr(object)));
+ stubcc.linkExit(masm.jump(), Uses(1));
+ continue;
+ }
+ }
+
+ if (i == last) {
+ masm.move(ImmPtr(results[i]), pushreg);
+ break;
+ } else {
+ lastMiss.setJump(masm.branchPtr(Assembler::NotEqual, typeAddress, ImmPtr(object)));
+ masm.move(ImmPtr(results[i]), pushreg);
+ rejoins.append(masm.jump());
+ }
+ }
+
+ for (unsigned i = 0; i < rejoins.length(); i++)
+ rejoins[i].linkTo(masm.label(), &masm);
+
+ stubcc.leave();
+ stubcc.masm.move(ImmPtr(atom), Registers::ArgReg1);
+ OOL_STUBCALL(stubs::CallProp, REJOIN_FALLTHROUGH);
+
+ frame.dup();
+ // THIS THIS
+
+ frame.pushTypedPayload(JSVAL_TYPE_OBJECT, pushreg);
+ // THIS THIS FUN
+
+ frame.shift(-2);
+ // FUN THIS
+
+ stubcc.rejoin(Changes(2));
+ return true;
+}
+
+bool
mjit::Compiler::jsop_callprop(JSAtom *atom)
{
FrameEntry *top = frame.peek(-1);
+ /* If the CALLPROP will definitely be fetching a particular value, nop it. */
bool testObject;
JSObject *singleton = pushedSingleton(0);
if (singleton && singleton->isFunction() && !hasTypeBarriers(PC) &&
testSingletonPropertyTypes(top, ATOM_TO_JSID(atom), &testObject)) {
if (testObject) {
Jump notObject = frame.testObject(Assembler::NotEqual, top);
stubcc.linkExit(notObject, Uses(1));
stubcc.leave();
@@ -4733,16 +4879,22 @@ mjit::Compiler::jsop_callprop(JSAtom *at
// FUN THIS
if (testObject)
stubcc.rejoin(Changes(2));
return true;
}
+ /* Check for a dynamic dispatch. */
+ if (cx->typeInferenceEnabled()) {
+ if (jsop_callprop_dispatch(atom))
+ return true;
+ }
+
/* If the incoming type will never PIC, take slow path. */
if (top->isTypeKnown() && top->getKnownType() != JSVAL_TYPE_OBJECT) {
if (top->getKnownType() == JSVAL_TYPE_STRING)
return jsop_callprop_str(atom);
return jsop_callprop_slow(atom);
}
if (top->isTypeKnown())
@@ -4761,23 +4913,24 @@ mjit::Compiler::jsop_setprop(JSAtom *ato
jsop_setprop_slow(atom, usePropCache);
return true;
}
/*
* Set the property directly if we are accessing a known object which
* always has the property in a particular inline slot.
*/
+ jsid id = ATOM_TO_JSID(atom);
types::TypeSet *types = frame.extra(lhs).types;
- if (JSOp(*PC) == JSOP_SETPROP &&
+ if (JSOp(*PC) == JSOP_SETPROP && id == types::MakeTypeId(cx, id) &&
types && !types->unknown() && types->getObjectCount() == 1 &&
!types->getObject(0)->unknownProperties()) {
JS_ASSERT(usePropCache);
types::TypeObject *object = types->getObject(0);
- types::TypeSet *propertyTypes = object->getProperty(cx, ATOM_TO_JSID(atom), false);
+ types::TypeSet *propertyTypes = object->getProperty(cx, id, false);
if (!propertyTypes)
return false;
if (propertyTypes->isDefiniteProperty() && !propertyTypes->isOwnProperty(cx, true)) {
types->addFreeze(cx);
uint32 slot = propertyTypes->definiteSlot();
bool isObject = lhs->isTypeKnown();
if (!isObject) {
Jump notObject = frame.testObject(Assembler::NotEqual, lhs);
@@ -5674,30 +5827,31 @@ mjit::Compiler::jsop_getgname(uint32 ind
/* Optimize singletons like Math for JSOP_CALLPROP. */
JSObject *obj = pushedSingleton(0);
if (obj && !hasTypeBarriers(PC) && testSingletonProperty(globalObj, ATOM_TO_JSID(atom))) {
frame.push(ObjectValue(*obj));
return;
}
/* Get the type of the global. */
+ jsid id = ATOM_TO_JSID(atom);
JSValueType type = JSVAL_TYPE_UNKNOWN;
- if (cx->typeInferenceEnabled() && globalObj->isGlobal() &&
+ if (cx->typeInferenceEnabled() && globalObj->isGlobal() && id == types::MakeTypeId(cx, id) &&
!globalObj->getType()->unknownProperties()) {
/*
* Get the type tag for the global either straight off it (in case this
* is an INCGNAME op, and the pushed type set is wrong) or from the
* pushed type set (if this is a GETGNAME op, and the property may have
* a type barrier on it).
*/
types::TypeSet *types;
if (JSOp(*PC) == JSOP_GETGNAME || JSOp(*PC) == JSOP_CALLGNAME)
types = pushedTypeSet(0);
else
- types = globalObj->getType()->getProperty(cx, ATOM_TO_JSID(atom), false);
+ types = globalObj->getType()->getProperty(cx, id, false);
if (!types)
return;
type = types->getKnownTypeTag(cx);
const js::Shape *shape = globalObj->nativeLookup(ATOM_TO_JSID(atom));
if (shape && shape->hasDefaultGetterOrIsMethod() && shape->hasSlot()) {
Value *value = &globalObj->getSlotRef(shape->slot);
if (!value->isUndefined() && !types->isOwnProperty(cx, true)) {
@@ -5890,25 +6044,26 @@ void
mjit::Compiler::jsop_setgname(JSAtom *atom, bool usePropertyCache, bool popGuaranteed)
{
if (monitored(PC)) {
/* Global accesses are monitored only for a few names like __proto__. */
jsop_setgname_slow(atom, usePropertyCache);
return;
}
- if (cx->typeInferenceEnabled() && globalObj->isGlobal() &&
+ jsid id = ATOM_TO_JSID(atom);
+ if (cx->typeInferenceEnabled() && globalObj->isGlobal() && id == types::MakeTypeId(cx, id) &&
!globalObj->getType()->unknownProperties()) {
/*
* Note: object branding is disabled when inference is enabled. With
* branding there is no way to ensure that a non-function property
* can't get a function later and cause the global object to become
* branded, requiring a shape change if it changes again.
*/
- types::TypeSet *types = globalObj->getType()->getProperty(cx, ATOM_TO_JSID(atom), false);
+ types::TypeSet *types = globalObj->getType()->getProperty(cx, id, false);
if (!types)
return;
const js::Shape *shape = globalObj->nativeLookup(ATOM_TO_JSID(atom));
if (shape && !shape->isMethod() && shape->hasDefaultSetter() &&
shape->writable() && shape->hasSlot() && !types->isOwnProperty(cx, true)) {
watchGlobalReallocation();
Value *value = &globalObj->getSlotRef(shape->slot);
RegisterID reg = frame.allocReg();
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -641,16 +641,17 @@ class Compiler : public BaseCompiler
bool typeCheck = true, bool usePropCache = true);
bool jsop_setprop(JSAtom *atom, bool usePropCache, bool popGuaranteed);
void jsop_setprop_slow(JSAtom *atom, bool usePropCache = true);
bool jsop_callprop_slow(JSAtom *atom);
bool jsop_callprop(JSAtom *atom);
bool jsop_callprop_obj(JSAtom *atom);
bool jsop_callprop_str(JSAtom *atom);
bool jsop_callprop_generic(JSAtom *atom);
+ bool jsop_callprop_dispatch(JSAtom *atom);
bool jsop_instanceof();
void jsop_name(JSAtom *atom, JSValueType type);
bool jsop_xname(JSAtom *atom);
void enterBlock(JSObject *obj);
void leaveBlock();
void emitEval(uint32 argc);
void jsop_arguments();
bool jsop_tableswitch(jsbytecode *pc);
--- a/js/src/methodjit/FrameState.cpp
+++ b/js/src/methodjit/FrameState.cpp
@@ -707,32 +707,42 @@ FrameState::syncForAllocation(RegisterAl
if (inlineReturn)
topEntry = a->parent->sp - (GET_ARGC(a->parent->PC) + 2);
for (uint32 i = tracker.nentries - 1; i < tracker.nentries; i--) {
FrameEntry *fe = tracker[i];
if (deadEntry(fe, uses.nuses))
continue;
- if (topEntry && fe >= topEntry && !isTemporary(fe)) {
+ if (inlineReturn && fe >= topEntry && !isTemporary(fe)) {
/*
* The return value has already been stored, so there is no need to
* keep any of the entries for this frame or for values popped once
* the call returns intact. Forcibly evict any registers for these,
* so that we don't emit sync code for them if we need a register
* in syncFe below.
*/
forgetAllRegs(fe);
fe->resetSynced();
continue;
}
/* Force syncs for locals which are dead at the current PC. */
if (isLocal(fe) && !a->analysis->slotEscapes(entrySlot(fe))) {
- Lifetime *lifetime = variableLive(fe, a->PC);
+ Lifetime *lifetime = a->analysis->liveness(entrySlot(fe)).live(a->PC - a->script->code);
+ if (!lifetime)
+ fakeSync(fe);
+ }
+
+ /* If returning from a script, fake syncs for dead locals in the immediate parent. */
+ if (inlineReturn && fe >= a->parent->locals &&
+ fe - a->parent->locals < a->parent->script->nfixed &&
+ !a->parent->analysis->slotEscapes(frameSlot(a->parent, fe))) {
+ const LifetimeVariable &var = a->parent->analysis->liveness(frameSlot(a->parent, fe));
+ Lifetime *lifetime = var.live(a->parent->PC - a->parent->script->code);
if (!lifetime)
fakeSync(fe);
}
if (!fe->isCopy() && alloc->hasAnyReg(fe - entries)) {
/* Types are always synced, except for known doubles. */
if (!fe->isType(JSVAL_TYPE_DOUBLE))
syncType(fe);
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -2747,24 +2747,24 @@ stubs::TypeBarrierHelper(VMFrame &f, uin
{
JS_ASSERT(which == 0 || which == 1);
/* The actual pushed value is at sp[0], fix up the stack. See finishBarrier. */
Value &result = f.regs.sp[-1 - (int)which];
result = f.regs.sp[0];
/*
- * Prune type barriers at this bytecode if we have added many objects to
+ * Break type barriers at this bytecode if we have added many objects to
* the target already. This isn't needed if inference results for the
* script have been destroyed, as we will reanalyze and prune type barriers
* as they are regenerated.
*/
if (f.script()->hasAnalysis() && f.script()->analysis(f.cx)->ranInference()) {
AutoEnterTypeInference enter(f.cx);
- f.script()->analysis(f.cx)->pruneTypeBarriers(f.cx, f.pc() - f.script()->code);
+ f.script()->analysis(f.cx)->breakTypeBarriers(f.cx, f.pc() - f.script()->code, false);
}
f.script()->typeMonitor(f.cx, f.pc(), result);
}
void JS_FASTCALL
stubs::NegZeroHelper(VMFrame &f)
{