[INFER] Track multiple types when monitoring SETPROP/SETNAME and calls, bug 641714.

This commit is contained in:
Brian Hackett 2011-03-15 23:50:44 -07:00
parent 57802e99bd
commit d10d0da404
13 changed files with 203 additions and 71 deletions

View File

@ -2173,6 +2173,7 @@ public:
inline bool addTypeProperty(js::types::TypeObject *obj, const char *name, const js::Value &value);
inline bool addTypePropertyId(js::types::TypeObject *obj, jsid id, js::types::jstype type);
inline bool addTypePropertyId(js::types::TypeObject *obj, jsid id, const js::Value &value);
inline bool addTypePropertyId(js::types::TypeObject *obj, jsid id, js::types::ClonedTypeSet *types);
/* Get the type to use for objects with no prototype. */
inline js::types::TypeObject *getTypeEmpty();

View File

@ -364,6 +364,27 @@ struct AnalyzeState {
// 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_STRING; 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)
{
@ -1277,14 +1298,14 @@ TypeConstraintCondensed::arrayNotPacked(JSContext *cx, bool notDense)
}
/* Constraint which triggers recompilation of a script if any type is added to a type set. */
class TypeConstraintFreezeSingleType : public TypeConstraint
class TypeConstraintFreeze : public TypeConstraint
{
public:
/* Whether a second type has already been added, triggering recompilation. */
/* Whether a new type has already been added, triggering recompilation. */
bool typeAdded;
TypeConstraintFreezeSingleType(JSScript *script)
: TypeConstraint("freezeSingleType", script), typeAdded(false)
TypeConstraintFreeze(JSScript *script)
: TypeConstraint("freeze", script), typeAdded(false)
{}
void newType(JSContext *cx, TypeSet *source, jstype type)
@ -1297,44 +1318,39 @@ public:
}
};
static inline jstype
GetSingleTypeFromTypeFlags(TypeFlags flags)
void
TypeSet::Clone(JSContext *cx, JSScript *script, TypeSet *source, ClonedTypeSet *target)
{
switch (flags) {
case TYPE_FLAG_UNDEFINED:
return TYPE_UNDEFINED;
case TYPE_FLAG_NULL:
return TYPE_NULL;
case TYPE_FLAG_BOOLEAN:
return TYPE_BOOLEAN;
case TYPE_FLAG_INT32:
return TYPE_INT32;
case (TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE):
return TYPE_DOUBLE;
case TYPE_FLAG_STRING:
return TYPE_STRING;
default:
return TYPE_UNKNOWN;
if (!source) {
target->typeFlags = TYPE_FLAG_UNKNOWN;
return;
}
}
jstype
TypeSet::getSingleType(JSContext *cx, JSScript *script)
{
TypeFlags flags = typeFlags & ~TYPE_FLAG_INTERMEDIATE_SET;
jstype type;
if (script && !source->unknown())
source->add(cx, ArenaNew<TypeConstraintFreeze>(cx->compartment->types.pool, script), false);
if (objectCount >= 2)
type = TYPE_UNKNOWN;
else if (objectCount == 1)
type = flags ? TYPE_UNKNOWN : (jstype) objectSet;
else
type = GetSingleTypeFromTypeFlags(flags);
if (script && type != TYPE_UNKNOWN)
add(cx, ArenaNew<TypeConstraintFreezeSingleType>(cx->compartment->types.pool, script), false);
return type;
target->typeFlags = source->typeFlags & ~TYPE_FLAG_INTERMEDIATE_SET;
target->objectCount = source->objectCount;
if (source->objectCount >= 2) {
target->objectSet = (TypeObject **) ::js_malloc(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;
}
}
/*

View File

@ -67,6 +67,7 @@ struct TypeCallsite;
struct TypeObject;
struct TypeFunction;
struct TypeCompartment;
struct ClonedTypeSet;
/*
* Information about a single concrete type. This is a non-zero value whose
@ -270,6 +271,9 @@ struct TypeSet
*/
inline void addType(JSContext *cx, jstype type);
/* Add all types in a cloned set to this set. */
void addTypeSet(JSContext *cx, ClonedTypeSet *types);
/* 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);
@ -306,9 +310,6 @@ struct TypeSet
* will be marked for recompilation.
*/
/* Get the single type representing all values in this set, TYPE_UNKNOWN otherwise. */
jstype getSingleType(JSContext *cx, JSScript *script);
/* Get any type tag which all values in this set must have. */
JSValueType getKnownTypeTag(JSContext *cx, JSScript *script);
@ -318,10 +319,24 @@ struct TypeSet
/* Get whether this type set is non-empty. */
bool knownNonEmpty(JSContext *cx, JSScript *script);
/*
* Clone this type set onto target; if any new types are added to this set
* in the future, the script will be recompiled.
*/
static void Clone(JSContext *cx, JSScript *script, TypeSet *source, ClonedTypeSet *target);
private:
inline void markUnknown(JSContext *cx);
};
/* A type set captured for use by JIT compilers. */
struct ClonedTypeSet
{
TypeFlags typeFlags;
TypeObject **objectSet;
unsigned objectCount;
};
/* Type information about a property. */
struct Property
{

View File

@ -322,6 +322,26 @@ JSContext::addTypePropertyId(js::types::TypeObject *obj, jsid id, const js::Valu
return true;
}
inline bool
JSContext::addTypePropertyId(js::types::TypeObject *obj, jsid id, js::types::ClonedTypeSet *set)
{
if (obj->unknownProperties)
return true;
id = js::types::MakeTypeId(this, id);
js::types::AutoEnterTypeInference enter(this);
js::types::TypeSet *types = obj->getProperty(this, id, true);
if (!types)
return compartment->types.checkPendingRecompiles(this);
js::types::InferSpew(js::types::ISpewOps, "externalType: property %s %s",
obj->name(), js::types::TypeIdString(id));
types->addTypeSet(this, set);
return compartment->types.checkPendingRecompiles(this);
}
inline js::types::TypeObject *
JSContext::getTypeEmpty()
{
@ -575,6 +595,19 @@ JSScript::typeSetThis(JSContext *cx, const js::Value &value)
return true;
}
inline bool
JSScript::typeSetThis(JSContext *cx, js::types::ClonedTypeSet *set)
{
if (!ensureVarTypes(cx))
return false;
js::types::AutoEnterTypeInference enter(cx);
js::types::InferSpew(js::types::ISpewOps, "externalType: setThis #%u: %s", id());
thisTypes()->addTypeSet(cx, set);
return cx->compartment->types.checkPendingRecompiles(cx);
}
inline bool
JSScript::typeSetNewCalled(JSContext *cx)
{
@ -628,6 +661,19 @@ JSScript::typeSetLocal(JSContext *cx, unsigned local, const js::Value &value)
return true;
}
inline bool
JSScript::typeSetLocal(JSContext *cx, unsigned local, js::types::ClonedTypeSet *set)
{
if (!ensureVarTypes(cx))
return false;
js::types::AutoEnterTypeInference enter(cx);
js::types::InferSpew(js::types::ISpewOps, "externalType: setLocal #%u %u", id(), local);
localTypes(local)->addTypeSet(cx, set);
return compartment->types.checkPendingRecompiles(cx);
}
inline bool
JSScript::typeSetArgument(JSContext *cx, unsigned arg, js::types::jstype type)
{
@ -657,6 +703,19 @@ JSScript::typeSetArgument(JSContext *cx, unsigned arg, const js::Value &value)
return true;
}
inline bool
JSScript::typeSetArgument(JSContext *cx, unsigned arg, js::types::ClonedTypeSet *set)
{
if (!ensureVarTypes(cx))
return false;
js::types::AutoEnterTypeInference enter(cx);
js::types::InferSpew(js::types::ISpewOps, "externalType: setArg #%u %u", id(), arg);
argTypes(arg)->addTypeSet(cx, set);
return cx->compartment->types.checkPendingRecompiles(cx);
}
inline bool
JSScript::typeSetUpvar(JSContext *cx, unsigned upvar, const js::Value &value)
{
@ -1167,10 +1226,28 @@ inline TypeFunction::TypeFunction(jsid name, JSObject *proto)
}
inline void
SweepType(jstype *ptype)
SweepClonedTypes(ClonedTypeSet *types)
{
if (TypeIsObject(*ptype) && !((TypeObject*)*ptype)->marked)
*ptype = TYPE_UNKNOWN;
if (types->objectCount >= 2) {
for (unsigned i = 0; i < types->objectCount; i++) {
if (!types->objectSet[i]->marked)
types->objectSet[i--] = types->objectSet[--types->objectCount];
}
if (types->objectCount == 1) {
TypeObject *obj = (TypeObject *) types->objectSet;
::js_free(types->objectSet);
types->objectSet = (TypeObject **) obj;
} else if (types->objectCount == 0) {
::js_free(types->objectSet);
types->objectSet = NULL;
}
} else if (types->objectCount == 1) {
TypeObject *obj = (TypeObject *) types->objectSet;
if (!obj->marked) {
types->objectSet = NULL;
types->objectCount = 0;
}
}
}
} } /* namespace js::types */

View File

@ -527,11 +527,14 @@ struct JSScript {
/* Add a type for a variable in this script. */
inline bool typeSetThis(JSContext *cx, js::types::jstype type);
inline bool typeSetThis(JSContext *cx, const js::Value &value);
inline bool typeSetThis(JSContext *cx, js::types::ClonedTypeSet *types);
inline bool typeSetNewCalled(JSContext *cx);
inline bool typeSetLocal(JSContext *cx, unsigned local, js::types::jstype type);
inline bool typeSetLocal(JSContext *cx, unsigned local, const js::Value &value);
inline bool typeSetLocal(JSContext *cx, unsigned local, js::types::ClonedTypeSet *types);
inline bool typeSetArgument(JSContext *cx, unsigned arg, js::types::jstype type);
inline bool typeSetArgument(JSContext *cx, unsigned arg, const js::Value &value);
inline bool typeSetArgument(JSContext *cx, unsigned arg, js::types::ClonedTypeSet *types);
inline bool typeSetUpvar(JSContext *cx, unsigned upvar, const js::Value &value);
/*

View File

@ -3017,14 +3017,17 @@ mjit::Compiler::inlineCallHelper(uint32 callImmArgc, bool callingNew)
callIC.typeMonitored = monitored(PC);
if (callIC.typeMonitored && callIC.frameSize.isStatic()) {
unsigned argc = callIC.frameSize.staticArgc();
callIC.argTypes = (types::jstype *) js_calloc((1 + argc) * sizeof(types::jstype));
if (!callIC.argTypes)
callIC.argTypes = (types::ClonedTypeSet *)
js_calloc((1 + argc) * sizeof(types::ClonedTypeSet));
if (!callIC.argTypes) {
js_ReportOutOfMemory(cx);
return false;
}
types::TypeSet *types = frame.getTypeSet(frame.peek(-(argc + 1)));
callIC.argTypes[0] = types ? types->getSingleType(cx, script) : types::TYPE_UNKNOWN;
types::TypeSet::Clone(cx, script, types, &callIC.argTypes[0]);
for (unsigned i = 0; i < argc; i++) {
types::TypeSet *types = frame.getTypeSet(frame.peek(-(argc - i)));
callIC.argTypes[i + 1] = types ? types->getSingleType(cx, script) : types::TYPE_UNKNOWN;
types::TypeSet::Clone(cx, script, types, &callIC.argTypes[i + 1]);
}
}
@ -3834,10 +3837,15 @@ mjit::Compiler::jsop_setprop(JSAtom *atom, bool usePropCache)
if (monitored(PC)) {
types::TypeSet *types = frame.getTypeSet(rhs);
pic.typeMonitored = true;
pic.knownType = types ? types->getSingleType(cx, script) : types::TYPE_UNKNOWN;
pic.rhsTypes = (types::ClonedTypeSet *) ::js_calloc(sizeof(types::ClonedTypeSet));
if (!pic.rhsTypes) {
js_ReportOutOfMemory(cx);
return false;
}
types::TypeSet::Clone(cx, script, types, pic.rhsTypes);
} else {
pic.typeMonitored = false;
pic.knownType = types::TYPE_UNKNOWN;
pic.rhsTypes = NULL;
}
RESERVE_IC_SPACE(masm);

View File

@ -155,7 +155,7 @@ class Compiler : public BaseCompiler
RegisterID funPtrReg;
FrameSize frameSize;
bool typeMonitored;
types::jstype *argTypes;
types::ClonedTypeSet *argTypes;
};
private:
@ -219,7 +219,7 @@ class Compiler : public BaseCompiler
struct PICGenInfo : public BaseICInfo {
PICGenInfo(ic::PICInfo::Kind kind, JSOp op, bool usePropCache)
: BaseICInfo(op), kind(kind), usePropCache(usePropCache)
: BaseICInfo(op), kind(kind), usePropCache(usePropCache), typeMonitored(false)
{ }
ic::PICInfo::Kind kind;
Label typeCheck;
@ -232,7 +232,7 @@ class Compiler : public BaseCompiler
JSAtom *atom;
bool hasTypeCheck;
bool typeMonitored;
types::jstype knownType;
types::ClonedTypeSet *rhsTypes;
ValueRemat vr;
#ifdef JS_HAS_IC_LABELS
union {
@ -290,7 +290,7 @@ class Compiler : public BaseCompiler
ic.u.get.hasTypeCheck = hasTypeCheck;
}
ic.typeMonitored = typeMonitored;
ic.knownType = knownType;
ic.rhsTypes = rhsTypes;
#ifdef JS_HAS_IC_LABELS
if (ic.isGet())
ic.setLabels(getPropLabels());

View File

@ -343,7 +343,8 @@ stubs::CompileFunction(VMFrame &f, uint32 nactual)
}
static inline bool
UncachedInlineCall(VMFrame &f, uint32 flags, void **pret, bool *unjittable, uint32 argc, types::jstype *argTypes)
UncachedInlineCall(VMFrame &f, uint32 flags, void **pret, bool *unjittable, uint32 argc,
types::ClonedTypeSet *argTypes)
{
JSContext *cx = f.cx;
Value *vp = f.regs.sp - (argc + 2);
@ -361,17 +362,20 @@ UncachedInlineCall(VMFrame &f, uint32 flags, void **pret, bool *unjittable, uint
* used without further type checking. If there is an argument count mismatch,
* the callee's args will end up getting marked as unknown.
*/
types::AutoEnterTypeInference enter(cx);
if (flags & JSFRAME_CONSTRUCTING) {
if (!newscript->typeSetNewCalled(cx))
return false;
} else {
if (!newscript->typeSetThis(cx, argTypes[0]))
if (!newscript->typeSetThis(cx, &argTypes[0]))
return false;
}
for (unsigned i = 0; i < argc; i++) {
if (!newscript->typeSetArgument(cx, i, argTypes[1 + i]))
if (!newscript->typeSetArgument(cx, i, &argTypes[1 + i]))
return false;
}
if (!cx->compartment->types.checkPendingRecompiles(cx))
return false;
} else {
CallArgs args(vp + 2, argc);
if (!cx->typeMonitorCall(args, flags & JSFRAME_CONSTRUCTING))
@ -438,7 +442,8 @@ stubs::UncachedNew(VMFrame &f, uint32 argc)
}
void
stubs::UncachedNewHelper(VMFrame &f, uint32 argc, types::jstype *argTypes, UncachedCallResult *ucr)
stubs::UncachedNewHelper(VMFrame &f, uint32 argc, types::ClonedTypeSet *argTypes,
UncachedCallResult *ucr)
{
ucr->init();
@ -481,7 +486,8 @@ stubs::Eval(VMFrame &f, uint32 argc)
}
void
stubs::UncachedCallHelper(VMFrame &f, uint32 argc, types::jstype *argTypes, UncachedCallResult *ucr)
stubs::UncachedCallHelper(VMFrame &f, uint32 argc, types::ClonedTypeSet *argTypes,
UncachedCallResult *ucr)
{
ucr->init();

View File

@ -1423,8 +1423,8 @@ JITScript::sweepCallICs(JSContext *cx, bool purgeAll)
ic::CallICInfo &ic = callICs_[i];
if (ic.argTypes) {
for (unsigned i = 0; i < ic.frameSize.staticArgc(); i++)
types::SweepType(&ic.argTypes[i]);
for (unsigned i = 0; i < ic.frameSize.staticArgc() + 1; i++)
types::SweepClonedTypes(&ic.argTypes[i]);
}
/*

View File

@ -256,7 +256,7 @@ struct CallICInfo {
* For monitored calls with static argc, the types of 'this' and arguments.
* For calls through Function.prototype.call, this refers to the inner this/arguments.
*/
types::jstype *argTypes;
types::ClonedTypeSet *argTypes;
inline void reset() {
fastGuardedObject = NULL;

View File

@ -200,7 +200,8 @@ class SetPropCompiler : public PICStubCompiler
static void reset(Repatcher &repatcher, ic::PICInfo &pic)
{
types::SweepType(&pic.knownType);
if (pic.rhsTypes)
types::SweepClonedTypes(pic.rhsTypes);
SetPropLabels &labels = pic.setPropLabels();
repatcher.repatchLEAToLoadPtr(labels.getDslotsLoad(pic.fastPathRejoin, pic.u.vr));
@ -601,7 +602,7 @@ class SetPropCompiler : public PICStubCompiler
if (pic.typeMonitored) {
uint32 recompilations = f.jit()->recompilations;
if (!cx->addTypePropertyId(obj->getType(), shape->id, pic.knownType))
if (!cx->addTypePropertyId(obj->getType(), shape->id, pic.rhsTypes))
return error();
if (f.jit()->recompilations != recompilations)
return Lookup_Uncacheable;
@ -621,7 +622,7 @@ class SetPropCompiler : public PICStubCompiler
return disable("invalid slot");
if (pic.typeMonitored) {
uint32 recompilations = f.jit()->recompilations;
if (!cx->addTypePropertyId(obj->getType(), shape->id, pic.knownType))
if (!cx->addTypePropertyId(obj->getType(), shape->id, pic.rhsTypes))
return error();
if (f.jit()->recompilations != recompilations)
return Lookup_Uncacheable;
@ -640,10 +641,10 @@ class SetPropCompiler : public PICStubCompiler
if (!script->ensureVarTypes(cx))
return error();
if (shape->setterOp() == SetCallArg) {
if (!script->typeSetArgument(cx, slot, pic.knownType))
if (!script->typeSetArgument(cx, slot, pic.rhsTypes))
return error();
} else {
if (!script->typeSetLocal(cx, slot, pic.knownType))
if (!script->typeSetLocal(cx, slot, pic.rhsTypes))
return error();
}
if (f.jit()->recompilations != recompilations)

View File

@ -453,8 +453,8 @@ struct PICInfo : public BasePolyIC {
// Offset from start of fast path to initial shape guard.
uint32 shapeGuard;
// Exact known type of the RHS, for monitored PICs.
types::jstype knownType;
// Possible types of the RHS, for monitored SETPROP PICs.
types::ClonedTypeSet *rhsTypes;
inline bool isSet() const {
return kind == SET || kind == SETMETHOD;
@ -544,6 +544,11 @@ struct PICInfo : public BasePolyIC {
inlinePathPatched = false;
shapeRegHasBaseShape = true;
}
~PICInfo() {
if (typeMonitored)
js_free(rhsTypes);
}
};
#ifdef JS_POLYIC

View File

@ -104,8 +104,8 @@ struct UncachedCallResult {
* These functions either execute the function, return a native code
* pointer that can be used to call the function, or throw.
*/
void UncachedCallHelper(VMFrame &f, uint32 argc, types::jstype *argTypes, UncachedCallResult *ucr);
void UncachedNewHelper(VMFrame &f, uint32 argc, types::jstype *argTypes, UncachedCallResult *ucr);
void UncachedCallHelper(VMFrame &f, uint32 argc, types::ClonedTypeSet *argTypes, UncachedCallResult *ucr);
void UncachedNewHelper(VMFrame &f, uint32 argc, types::ClonedTypeSet *argTypes, UncachedCallResult *ucr);
void JS_FASTCALL CreateThis(VMFrame &f, JSObject *proto);
void JS_FASTCALL Throw(VMFrame &f);