[INFER] Analysis and compiler improvements for polymorphic call sites, bug 653962.

This commit is contained in:
Brian Hackett 2011-05-20 19:33:06 -07:00
parent d14970c9fe
commit f1b91c8773
13 changed files with 430 additions and 195 deletions

View File

@ -971,7 +971,7 @@ class ScriptAnalysis
types::TypeBarrier *typeBarriers(uint32 offset) {
if (getCode(offset).typeBarriers)
pruneTypeBarriers(NULL, offset);
pruneTypeBarriers(offset);
return getCode(offset).typeBarriers;
}
types::TypeBarrier *typeBarriers(const jsbytecode *pc) {
@ -980,7 +980,18 @@ class ScriptAnalysis
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);

View File

@ -4337,74 +4337,61 @@ 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)
@ -4413,9 +4400,6 @@ 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)
@ -4428,14 +4412,9 @@ 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 *)

View File

@ -3222,14 +3222,9 @@ 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
@ -3237,15 +3232,10 @@ 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,
@ -3258,14 +3248,8 @@ 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);
}
@ -3274,15 +3258,10 @@ 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++) {
@ -3297,19 +3276,13 @@ 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
@ -3317,14 +3290,9 @@ 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. */
@ -3334,9 +3302,6 @@ array_TypeExtra(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite,
{
TypeCallsite *site = Valueify(jssite);
if (!site->returnTypes)
return;
if (site->isNew)
site->returnTypes->addType(cx, TYPE_UNKNOWN);
@ -3504,16 +3469,14 @@ 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;

View File

@ -499,7 +499,7 @@ TypeSet::addCondensed(JSContext *cx, JSScript *script)
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.
@ -511,7 +511,7 @@ public:
/* 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)
@ -526,14 +526,14 @@ public:
};
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));
@ -627,6 +627,43 @@ 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
{
@ -660,15 +697,8 @@ TypeSet::addFilterPrimitives(JSContext *cx, JSScript *script, TypeSet *target, b
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) {
@ -676,10 +706,31 @@ ScriptAnalysis::pruneTypeBarriers(JSContext *removecx, uint32 offset)
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;
@ -687,6 +738,18 @@ ScriptAnalysis::pruneTypeBarriers(JSContext *removecx, uint32 offset)
}
}
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.
@ -694,10 +757,10 @@ ScriptAnalysis::pruneTypeBarriers(JSContext *removecx, uint32 offset)
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());
@ -715,7 +778,7 @@ public:
};
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));
}
@ -802,7 +865,7 @@ GetPropertyObject(JSContext *cx, JSScript *script, jstype type)
* 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);
@ -867,8 +930,19 @@ TypeConstraintProp::newType(JSContext *cx, TypeSet *source, jstype type)
}
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
@ -911,7 +985,7 @@ 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. */
@ -1009,34 +1083,55 @@ TypeConstraintCall::newType(JSContext *cx, TypeSet *source, jstype type)
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)
{
@ -2696,7 +2791,19 @@ TypeObject::markUnknown(JSContext *cx)
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);
@ -3136,8 +3243,10 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
/* 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);
@ -3172,8 +3281,10 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
*/
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;
}
@ -3213,16 +3324,20 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
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:
@ -3244,8 +3359,10 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
/* 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;
}
@ -3310,7 +3427,14 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
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);
@ -3336,7 +3460,10 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
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);
@ -3864,7 +3991,7 @@ public:
{}
void newType(JSContext *cx, TypeSet *source, jstype type) {
if (object->newScript && !source->getSingleObject())
if (!object->newScriptCleared && !source->getSingleObject())
object->clearNewScript(cx);
}
};
@ -4073,6 +4200,13 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, JSScript *script, JS
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);
@ -4088,7 +4222,7 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, JSScript *script, JS
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 =
@ -4500,7 +4634,7 @@ JSObject::makeNewType(JSContext *cx, JSScript *newScript)
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. */

View File

@ -357,18 +357,19 @@ class TypeSet
/* 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);
@ -575,6 +576,9 @@ struct TypeObject
/* 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
@ -733,7 +737,7 @@ struct TypeFunction : public TypeObject
struct TypeCallsite
{
JSScript *script;
const jsbytecode *pc;
jsbytecode *pc;
/* Whether this is a 'NEW' call. */
bool isNew;
@ -745,18 +749,12 @@ struct TypeCallsite
/* 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);

View File

@ -92,6 +92,8 @@ GetValueType(JSContext *cx, const Value &val)
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.
@ -1237,26 +1239,15 @@ TypeSet::make(JSContext *cx, const char *name)
/////////////////////////////////////////////////////////////////////
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)
{
@ -1281,6 +1272,7 @@ 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());

View File

@ -867,9 +867,6 @@ static void math_TypeArith(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite
{
types::TypeCallsite *site = Valueify(jssite);
if (!site->returnTypes)
return;
if (site->isNew)
site->returnTypes->addType(cx, types::TYPE_UNKNOWN);

View File

@ -3904,12 +3904,10 @@ static void object_TypeNew(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite
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);
}
}

View File

@ -3203,9 +3203,6 @@ static void type_StringSplit(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsit
{
TypeCallsite *site = Valueify(jssite);
if (!site->returnTypes)
return;
if (!site->hasGlobal()) {
site->returnTypes->addType(cx, TYPE_UNKNOWN);
return;

View File

@ -4210,12 +4210,13 @@ mjit::Compiler::jsop_getprop(JSAtom *atom, JSValueType knownType,
* 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)) {
@ -4704,11 +4705,156 @@ mjit::Compiler::testSingletonPropertyTypes(FrameEntry *top, jsid id, bool *testO
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) &&
@ -4738,6 +4884,12 @@ mjit::Compiler::jsop_callprop(JSAtom *atom)
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)
@ -4766,13 +4918,14 @@ mjit::Compiler::jsop_setprop(JSAtom *atom, bool usePropCache, bool popGuaranteed
* 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)) {
@ -5679,8 +5832,9 @@ mjit::Compiler::jsop_getgname(uint32 index)
}
/* 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
@ -5692,7 +5846,7 @@ mjit::Compiler::jsop_getgname(uint32 index)
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);
@ -5895,7 +6049,8 @@ mjit::Compiler::jsop_setgname(JSAtom *atom, bool usePropertyCache, bool popGuara
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
@ -5903,7 +6058,7 @@ mjit::Compiler::jsop_setgname(JSAtom *atom, bool usePropertyCache, bool popGuara
* 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));

View File

@ -646,6 +646,7 @@ class Compiler : public BaseCompiler
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);

View File

@ -712,7 +712,7 @@ FrameState::syncForAllocation(RegisterAllocation *alloc, bool inlineReturn, Uses
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
@ -727,7 +727,17 @@ FrameState::syncForAllocation(RegisterAllocation *alloc, bool inlineReturn, Uses
/* 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);
}

View File

@ -2752,14 +2752,14 @@ stubs::TypeBarrierHelper(VMFrame &f, uint32 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);