Move JSObject::newType to a hashtable, bug 684410.

This commit is contained in:
Brian Hackett 2011-10-07 20:09:09 -07:00
parent d0e05ed02f
commit 226f6d2a95
19 changed files with 187 additions and 139 deletions

View File

@ -50,7 +50,7 @@ bool checkObjectFields(JSObject *savedCopy, JSObject *obj)
/* Ignore fields which are unstable across GCs. */
CHECK(savedCopy->lastProp == obj->lastProp);
CHECK(savedCopy->flags == obj->flags);
CHECK(savedCopy->newType == obj->newType);
CHECK(savedCopy->initializedLength == obj->initializedLength);
CHECK(savedCopy->getProto() == obj->getProto());
CHECK(savedCopy->parent == obj->parent);
CHECK(savedCopy->privateData == obj->privateData);

View File

@ -1333,7 +1333,6 @@ JSObject::makeDenseArraySlow(JSContext *cx)
/* The initialized length is used iff this is a dense array. */
initializedLength = 0;
JS_ASSERT(newType == NULL);
/*
* Begin with the length property to share more of the property tree.
@ -2681,7 +2680,7 @@ TryReuseArrayType(JSObject *obj, JSObject *nobj)
* and has the same prototype.
*/
JS_ASSERT(nobj->isDenseArray());
JS_ASSERT(nobj->type() == nobj->getProto()->newType);
JS_ASSERT(nobj->getProto()->hasNewType(nobj->type()));
if (obj->isArray() && !obj->hasSingletonType() && obj->getProto() == nobj->getProto())
nobj->setType(obj->type());

View File

@ -95,6 +95,7 @@ JSCompartment::JSCompartment(JSRuntime *rt)
emptyDeclEnvShape(NULL),
emptyEnumeratorShape(NULL),
emptyWithShape(NULL),
emptyTypeObject(NULL),
initialRegExpShape(NULL),
initialStringShape(NULL),
debugModeBits(rt->debugMode ? DebugFromC : 0),
@ -493,10 +494,10 @@ JSCompartment::markTypes(JSTracer *trc)
template <class T>
void
CheckWeakShape(JSContext *cx, T *&shape)
CheckWeakReference(JSContext *cx, T *&ptr)
{
if (shape && IsAboutToBeFinalized(cx, shape))
shape = NULL;
if (ptr && IsAboutToBeFinalized(cx, ptr))
ptr = NULL;
}
void
@ -513,20 +514,23 @@ JSCompartment::sweep(JSContext *cx, uint32 releaseInterval)
}
}
/* Remove dead empty shapes. */
CheckWeakShape(cx, emptyStrictArgumentsShape);
CheckWeakShape(cx, emptyNormalArgumentsShape);
CheckWeakShape(cx, emptyBlockShape);
CheckWeakShape(cx, emptyCallShape);
CheckWeakShape(cx, emptyDeclEnvShape);
CheckWeakShape(cx, emptyEnumeratorShape);
CheckWeakShape(cx, emptyWithShape);
/* Remove dead references held weakly by the compartment. */
CheckWeakShape(cx, initialRegExpShape);
CheckWeakShape(cx, initialStringShape);
CheckWeakReference(cx, emptyStrictArgumentsShape);
CheckWeakReference(cx, emptyNormalArgumentsShape);
CheckWeakReference(cx, emptyBlockShape);
CheckWeakReference(cx, emptyCallShape);
CheckWeakReference(cx, emptyDeclEnvShape);
CheckWeakReference(cx, emptyEnumeratorShape);
CheckWeakReference(cx, emptyWithShape);
CheckWeakReference(cx, initialRegExpShape);
CheckWeakReference(cx, initialStringShape);
/* Remove dead base shapes */
sweepBaseShapeTable(cx);
sweepNewTypeObjectTable(cx);
CheckWeakReference(cx, emptyTypeObject);
sweepBreakpoints(cx);

View File

@ -506,6 +506,29 @@ struct JS_FRIEND_API(JSCompartment) {
void sweepBaseShapeTable(JSContext *cx);
/*
* Set of all type objects in the compartment which are the default 'new'
* types of some (possibly NULL) prototype.
*/
struct NewTypeObjectEntry {
typedef JSObject *Lookup;
static inline js::HashNumber hash(JSObject *base);
static inline bool match(js::types::TypeObject *key, JSObject *lookup);
};
typedef js::HashSet<js::types::TypeObject *, NewTypeObjectEntry, js::SystemAllocPolicy> NewTypeObjectSet;
NewTypeObjectSet newTypeObjects;
void sweepNewTypeObjectTable(JSContext *cx);
js::types::TypeObject *emptyTypeObject;
/* Get the default 'new' type for objects with a NULL prototype. */
inline js::types::TypeObject *getEmptyType(JSContext *cx);
/*
* Initial shapes given to RegExp and String objects, encoding the initial
* sets of built-in instance properties and the fixed slots where they must

View File

@ -761,10 +761,14 @@ NewDeclEnvObject(JSContext *cx, StackFrame *fp)
if (!envobj)
return NULL;
types::TypeObject *type = cx->compartment->getEmptyType(cx);
if (!type)
return NULL;
EmptyShape *emptyDeclEnvShape = EmptyShape::getEmptyDeclEnvShape(cx);
if (!emptyDeclEnvShape)
return NULL;
envobj->init(cx, &emptyTypeObject, &fp->scopeChain(), fp, false);
envobj->init(cx, type, &fp->scopeChain(), fp, false);
envobj->setMap(emptyDeclEnvShape);
return envobj;
@ -1568,7 +1572,8 @@ js_XDRFunctionObject(JSXDRState *xdr, JSObject **objp)
if (!fun)
return false;
fun->clearParent();
fun->clearType();
if (!fun->clearType(cx))
return false;
}
AutoObjectRooter tvr(cx, fun);

View File

@ -227,8 +227,6 @@ MarkTypeObject(JSTracer *trc, types::TypeObject *type, const char *name)
JS_ASSERT(trc);
JS_ASSERT(type);
JS_SET_TRACING_NAME(trc, name);
if (type == &types::emptyTypeObject)
return;
Mark(trc, type);
/*
@ -713,8 +711,7 @@ ScanObject(GCMarker *gcmarker, JSObject *obj)
return;
types::TypeObject *type = obj->typeFromGC();
if (type != &types::emptyTypeObject)
PushMarkStack(gcmarker, type);
PushMarkStack(gcmarker, type);
if (JSObject *parent = obj->getParent())
PushMarkStack(gcmarker, parent);
@ -733,13 +730,8 @@ ScanObject(GCMarker *gcmarker, JSObject *obj)
clasp->trace(gcmarker, obj);
}
} else {
if (obj->newType)
PushMarkStack(gcmarker, obj->newType);
clasp->trace(gcmarker, obj);
}
} else {
if (obj->newType)
PushMarkStack(gcmarker, obj->newType);
}
js::Shape *shape = obj->lastProp;
@ -795,8 +787,6 @@ MarkChildren(JSTracer *trc, JSObject *obj)
MarkTypeObject(trc, obj->typeFromGC(), "type");
/* Trace universal (ops-independent) members. */
if (!obj->isDenseArray() && obj->newType)
MarkTypeObject(trc, obj->newType, "new_type");
if (JSObject *parent = obj->getParent())
MarkObject(trc, *parent, "parent");

View File

@ -1886,8 +1886,6 @@ TypeSet::hasGlobalObject(JSContext *cx, JSObject *global)
// TypeCompartment
/////////////////////////////////////////////////////////////////////
TypeObject types::emptyTypeObject(NULL, false, true);
void
TypeCompartment::init(JSContext *cx)
{
@ -2402,7 +2400,6 @@ GetValueTypeForTable(JSContext *cx, const Value &v)
{
Type type = GetValueType(cx, v);
JS_ASSERT(!type.isSingleObject());
JS_ASSERT_IF(type.isTypeObject(), type.typeObject() != &emptyTypeObject);
return type;
}
@ -5575,7 +5572,7 @@ JSObject::splicePrototype(JSContext *cx, JSObject *proto)
}
if (!cx->typeInferenceEnabled()) {
TypeObject *type = proto ? proto->getNewType(cx) : &emptyTypeObject;
TypeObject *type = proto ? proto->getNewType(cx) : cx->compartment->getEmptyType(cx);
if (!type)
return false;
type_ = type;
@ -5662,21 +5659,75 @@ JSObject::makeLazyType(JSContext *cx)
flags &= ~LAZY_TYPE;
}
void
JSObject::makeNewType(JSContext *cx, JSFunction *fun, bool unknown)
/* static */ inline HashNumber
JSCompartment::NewTypeObjectEntry::hash(JSObject *proto)
{
JS_ASSERT(!newType);
return PointerHasher<JSObject *, 3>::hash(proto);
}
/* static */ inline bool
JSCompartment::NewTypeObjectEntry::match(TypeObject *key, JSObject *lookup)
{
return key->proto == lookup;
}
#ifdef DEBUG
bool
JSObject::hasNewType(TypeObject *type)
{
JSCompartment::NewTypeObjectSet &table = compartment()->newTypeObjects;
if (!table.initialized())
return false;
JSCompartment::NewTypeObjectSet::AddPtr p = table.lookupForAdd(this);
return p && *p == type;
}
#endif /* DEBUG */
TypeObject *
JSObject::getNewType(JSContext *cx, JSFunction *fun, bool markUnknown)
{
JSCompartment::NewTypeObjectSet &table = cx->compartment->newTypeObjects;
if (!table.initialized() && !table.init())
return NULL;
JSCompartment::NewTypeObjectSet::AddPtr p = table.lookupForAdd(this);
if (p) {
TypeObject *type = *p;
/*
* If set, the type's newScript indicates the script used to create
* all objects in existence which have this type. If there are objects
* in existence which are not created by calling 'new' on newScript,
* we must clear the new script information from the type and will not
* be able to assume any definite properties for instances of the type.
* This case is rare, but can happen if, for example, two scripted
* functions have the same value for their 'prototype' property, or if
* Object.create is called with a prototype object that is also the
* 'prototype' property of some scripted function.
*/
if (type->newScript && type->newScript->fun != fun)
type->clearNewScript(cx);
if (markUnknown && cx->typeInferenceEnabled() && !type->unknownProperties())
type->markUnknown(cx);
return type;
}
TypeObject *type = cx->compartment->types.newTypeObject(cx, NULL,
JSProto_Object, this, unknown);
JSProto_Object, this, markUnknown);
if (!type)
return;
return NULL;
if (!table.relookupOrAdd(p, this, type))
return NULL;
newType = type;
setDelegate();
if (!cx->typeInferenceEnabled())
return;
return type;
AutoEnterTypeInference enter(cx);
@ -5710,6 +5761,8 @@ JSObject::makeNewType(JSContext *cx, JSFunction *fun, bool unknown)
*/
if (type->unknownProperties())
type->flags |= OBJECT_FLAG_SETS_MARKED_UNKNOWN;
return type;
}
/////////////////////////////////////////////////////////////////////
@ -5761,15 +5814,6 @@ TypeSet::sweep(JSContext *cx, JSCompartment *compartment)
flags &= ~TYPE_FLAG_PROPAGATED_PROPERTY;
}
inline void
JSObject::revertLazyType()
{
JS_ASSERT(hasSingletonType() && !hasLazyType());
JS_ASSERT_IF(type_->proto, type_->proto->newType);
flags |= LAZY_TYPE;
type_ = (type_->proto) ? type_->proto->newType : &emptyTypeObject;
}
inline void
TypeObject::clearProperties()
{
@ -5803,19 +5847,6 @@ TypeObject::sweep(JSContext *cx)
*/
clearProperties();
if (!isMarked()) {
/*
* Singleton objects do not hold strong references on their types.
* When removing the type, however, we need to fixup the singleton
* so that it has a lazy type again. The generic 'new' type for the
* proto must be live, since the type's prototype and its 'new'
* type are both strong references.
*/
JS_ASSERT_IF(singleton->isMarked() && proto,
proto->isMarked() && proto->newType->isMarked());
singleton->revertLazyType();
}
return;
}
@ -5905,9 +5936,6 @@ struct SweepTypeObjectOp
void
SweepTypeObjects(JSContext *cx, JSCompartment *compartment)
{
JS_ASSERT(!emptyTypeObject.emptyShapes);
JS_ASSERT(!emptyTypeObject.newScript);
SweepTypeObjectOp op(cx);
gc::ForEachArenaAndCell(compartment, gc::FINALIZE_TYPE_OBJECT, gc::EmptyArenaOp, op);
}
@ -5991,6 +6019,18 @@ TypeCompartment::sweep(JSContext *cx)
pendingCapacity = 0;
}
void
JSCompartment::sweepNewTypeObjectTable(JSContext *cx)
{
if (newTypeObjects.initialized()) {
for (NewTypeObjectSet::Enum e(newTypeObjects); !e.empty(); e.popFront()) {
TypeObject *type = e.front();
if (!type->isMarked())
e.removeFront();
}
}
}
TypeCompartment::~TypeCompartment()
{
if (pendingArray)

View File

@ -872,9 +872,6 @@ struct TypeObject : gc::Cell
}
};
/* Global singleton for the generic type of objects with no prototype. */
extern TypeObject emptyTypeObject;
/*
* Call to mark a script's arguments as having been created, recompile any
* dependencies and walk the stack if necessary to fix any lazy arguments.

View File

@ -1312,4 +1312,12 @@ js::analyze::ScriptAnalysis::addPushedType(JSContext *cx, uint32 offset, uint32
pushed->addType(cx, type);
}
inline js::types::TypeObject *
JSCompartment::getEmptyType(JSContext *cx)
{
if (!emptyTypeObject)
emptyTypeObject = types.newTypeObject(cx, NULL, JSProto_Object, NULL, true);
return emptyTypeObject;
}
#endif // jsinferinlines_h___

View File

@ -409,10 +409,15 @@ NewIteratorObject(JSContext *cx, uintN flags)
if (!obj)
return NULL;
types::TypeObject *type = cx->compartment->getEmptyType(cx);
if (!type)
return NULL;
EmptyShape *emptyEnumeratorShape = EmptyShape::getEmptyEnumeratorShape(cx);
if (!emptyEnumeratorShape)
return NULL;
obj->init(cx, &types::emptyTypeObject, NULL, NULL, false);
obj->init(cx, type, NULL, NULL, false);
obj->setMap(emptyEnumeratorShape);
return obj;
}

View File

@ -3510,10 +3510,15 @@ js_NewBlockObject(JSContext *cx)
if (!blockObj)
return NULL;
types::TypeObject *type = cx->compartment->getEmptyType(cx);
if (!type)
return NULL;
EmptyShape *emptyBlockShape = EmptyShape::getEmptyBlockShape(cx);
if (!emptyBlockShape)
return NULL;
blockObj->init(cx, &emptyTypeObject, NULL, NULL, false);
blockObj->init(cx, type, NULL, NULL, false);
blockObj->setMap(emptyBlockShape);
return blockObj;
@ -3881,10 +3886,6 @@ JSObject::TradeGuts(JSContext *cx, JSObject *a, JSObject *b, TradeGutsReserved &
JS_ASSERT(!a->isDenseArray() && !b->isDenseArray());
JS_ASSERT(!a->isArrayBuffer() && !b->isArrayBuffer());
/* New types for a JSObject need to be stable when trading guts. */
TypeObject *newTypeA = a->newType;
TypeObject *newTypeB = b->newType;
/* Trade the guts of the objects. */
const size_t size = a->structSize();
if (size == b->structSize()) {
@ -3945,9 +3946,6 @@ JSObject::TradeGuts(JSContext *cx, JSObject *a, JSObject *b, TradeGutsReserved &
reserved.newaslots = NULL;
reserved.newbslots = NULL;
}
a->newType = newTypeA;
b->newType = newTypeB;
}
/*
@ -4739,7 +4737,7 @@ SetProto(JSContext *cx, JSObject *obj, JSObject *proto, bool checkForCycles)
TypeObject *type = proto
? proto->getNewType(cx, NULL, /* markUnknown = */ true)
: &emptyTypeObject;
: cx->compartment->getEmptyType(cx);
if (!type)
return false;

View File

@ -478,13 +478,8 @@ struct JSObject : js::gc::Cell {
uint32 flags; /* flags */
union {
/* If prototype, type of values using this as their prototype. */
js::types::TypeObject *newType;
/* If dense array, the initialized length (see jsarray.cpp). */
jsuword initializedLength;
};
/* If dense array, the initialized length (see jsarray.cpp). */
jsuword initializedLength;
JS_FRIEND_API(size_t) sizeOfSlotsArray(JSUsableSizeFun usf);
@ -783,9 +778,6 @@ struct JSObject : js::gc::Cell {
*/
inline bool setSingletonType(JSContext *cx);
/* Called from GC, reverts a singleton object to having a lazy type. */
inline void revertLazyType();
inline js::types::TypeObject *getType(JSContext *cx);
js::types::TypeObject *type() const {
@ -800,14 +792,15 @@ struct JSObject : js::gc::Cell {
static inline size_t offsetOfType() { return offsetof(JSObject, type_); }
inline void clearType();
inline bool clearType(JSContext *cx);
inline void setType(js::types::TypeObject *newType);
inline js::types::TypeObject *getNewType(JSContext *cx, JSFunction *fun = NULL,
bool markUnknown = false);
private:
void makeNewType(JSContext *cx, JSFunction *fun, bool markUnknown);
public:
js::types::TypeObject *getNewType(JSContext *cx, JSFunction *fun = NULL,
bool markUnknown = false);
#ifdef DEBUG
bool hasNewType(js::types::TypeObject *newType);
#endif
/* Set a new prototype for an object with a singleton type. */
bool splicePrototype(JSContext *cx, JSObject *proto);

View File

@ -311,7 +311,11 @@ JSObject::finalize(JSContext *cx, bool background)
inline bool
JSObject::initCall(JSContext *cx, const js::Bindings &bindings, JSObject *parent)
{
init(cx, &js::types::emptyTypeObject, parent, NULL, false);
js::types::TypeObject *type = cx->compartment->getEmptyType(cx);
if (!type)
return false;
init(cx, type, parent, NULL, false);
setMap(bindings.lastShape());
JS_ASSERT(isCall());
@ -853,38 +857,17 @@ JSObject::getType(JSContext *cx)
return type_;
}
inline js::types::TypeObject *
JSObject::getNewType(JSContext *cx, JSFunction *fun, bool markUnknown)
{
if (isDenseArray() && !makeDenseArraySlow(cx))
return NULL;
if (newType) {
/*
* If set, the newType's newScript indicates the script used to create
* all objects in existence which have this type. If there are objects
* in existence which are not created by calling 'new' on newScript,
* we must clear the new script information from the type and will not
* be able to assume any definite properties for instances of the type.
* This case is rare, but can happen if, for example, two scripted
* functions have the same value for their 'prototype' property, or if
* Object.create is called with a prototype object that is also the
* 'prototype' property of some scripted function.
*/
if (newType->newScript && newType->newScript->fun != fun)
newType->clearNewScript(cx);
if (markUnknown && cx->typeInferenceEnabled() && !newType->unknownProperties())
newType->markUnknown(cx);
} else {
makeNewType(cx, fun, markUnknown);
}
return newType;
}
inline void
JSObject::clearType()
inline bool
JSObject::clearType(JSContext *cx)
{
JS_ASSERT(!hasSingletonType());
type_ = &js::types::emptyTypeObject;
js::types::TypeObject *type = cx->compartment->getEmptyType(cx);
if (!type)
return false;
type_ = type;
return true;
}
inline void
@ -969,8 +952,7 @@ JSObject::init(JSContext *cx, js::types::TypeObject *type,
js::ClearValueRange(fixedSlots(), capacity, denseArray);
}
newType = NULL;
JS_ASSERT(initializedLength == 0);
initializedLength = 0;
setType(type);
setParent(parent);
@ -1612,7 +1594,7 @@ NewObject(JSContext *cx, js::Class *clasp, JSObject *proto, JSObject *parent,
return NULL;
}
types::TypeObject *type = proto ? proto->getNewType(cx) : &js::types::emptyTypeObject;
types::TypeObject *type = proto ? proto->getNewType(cx) : cx->compartment->getEmptyType(cx);
if (!type)
return NULL;
@ -1712,7 +1694,7 @@ NewObject(JSContext *cx, js::Class *clasp, JSObject *proto, JSObject *parent)
static JS_ALWAYS_INLINE JSObject *
NewObjectWithType(JSContext *cx, types::TypeObject *type, JSObject *parent, gc::AllocKind kind)
{
JS_ASSERT(type == type->proto->newType);
JS_ASSERT(type->proto->hasNewType(type));
if (CanBeFinalizedInBackground(kind, &ObjectClass))
kind = GetBackgroundAllocKind(kind);

View File

@ -1947,7 +1947,8 @@ Parser::newFunction(JSTreeContext *tc, JSAtom *atom, FunctionSyntaxKind kind)
parent, atom);
if (fun && !tc->compileAndGo()) {
fun->clearParent();
fun->clearType();
if (!fun->clearType(context))
return NULL;
}
return fun;
}
@ -8884,7 +8885,8 @@ Parser::primaryExpr(TokenKind tt, JSBool afterDot)
return NULL;
if (!tc->compileAndGo()) {
obj->clearParent();
obj->clearType();
if (!obj->clearType(context))
return NULL;
}
pn->pn_objbox = tc->parser->newObjectBox(obj);

View File

@ -469,7 +469,8 @@ js_XDRRegExpObject(JSXDRState *xdr, JSObject **objp)
if (!obj)
return false;
obj->clearParent();
obj->clearType();
if (!obj->clearType(xdr->cx))
return false;
/*
* initRegExp can GC before storing re in the private field of the

View File

@ -68,7 +68,7 @@ js::types::TypeObject::getEmptyShape(JSContext *cx, js::Class *aclasp,
* Objects with a common prototype use the same shape lineage, even if
* their prototypes differ.
*/
JS_ASSERT(this == proto->newType);
JS_ASSERT(proto->hasNewType(this));
JS_ASSERT(kind >= js::gc::FINALIZE_OBJECT0 && kind <= js::gc::FINALIZE_OBJECT_LAST);
int i = kind - js::gc::FINALIZE_OBJECT0;

View File

@ -1394,7 +1394,8 @@ js_NewScriptObject(JSContext *cx, JSScript *script)
* Clear the object's type/proto, to avoid entraining stuff. Once we no longer use the parent
* for security checks, then we can clear the parent, too.
*/
obj->clearType();
if (!obj->clearType(cx))
return NULL;
return obj;
}

View File

@ -7424,7 +7424,8 @@ GlobalObject::getFunctionNamespace(JSContext *cx, Value *vp)
* names, its prefix and uri references are copied to the QName.
* The parent remains set and links back to global.
*/
obj->clearType();
if (!obj->clearType(cx))
return false;
v.setObject(*obj);
}

View File

@ -1285,20 +1285,19 @@ static const JSC::MacroAssembler::RegisterID JSParamReg_Argc = JSC::SparcRegist
* as for dense arrays we will need to get the address of the fixed
* slots first.
*/
JS_ASSERT(!templateObject->initializedLength);
if (templateObject->isDenseArray()) {
JS_ASSERT(!templateObject->initializedLength);
addPtr(Imm32(-thingSize + sizeof(JSObject)), result);
storePtr(result, Address(result, -(int)sizeof(JSObject) + JSObject::offsetOfSlots()));
addPtr(Imm32(-(int)sizeof(JSObject)), result);
} else {
JS_ASSERT(!templateObject->newType);
addPtr(Imm32(-thingSize), result);
storePtr(ImmPtr(NULL), Address(result, JSObject::offsetOfSlots()));
}
storePtr(ImmPtr(templateObject->lastProp), Address(result, offsetof(JSObject, lastProp)));
store32(Imm32(templateObject->flags), Address(result, offsetof(JSObject, flags)));
storePtr(ImmPtr(templateObject->newType), Address(result, offsetof(JSObject, newType)));
storePtr(ImmPtr((void *) templateObject->initializedLength), Address(result, offsetof(JSObject, initializedLength)));
storePtr(ImmPtr(templateObject->parent), Address(result, offsetof(JSObject, parent)));
storePtr(ImmPtr(templateObject->privateData), Address(result, offsetof(JSObject, privateData)));
storePtr(ImmPtr((void *) templateObject->capacity), Address(result, offsetof(JSObject, capacity)));