diff --git a/js/src/jit-test/tests/jaeger/propertyOptimize-1.js b/js/src/jit-test/tests/jaeger/propertyOptimize-1.js new file mode 100644 index 00000000000..47ac2fc6ef1 --- /dev/null +++ b/js/src/jit-test/tests/jaeger/propertyOptimize-1.js @@ -0,0 +1,29 @@ + +function Foo(x) +{ + this.f = x + 10; +} + +function Bar() +{ + this.g = 0; +} + +Bar.prototype = Foo.prototype; + +var x = new Foo(0); +var y = new Bar(); + +assertEq(10, eval("x.f")); +assertEq(undefined, eval("y.f")); + +function Other(x) +{ + this.f = x + 10; +} + +var a = new Other(0); +var b = Object.create(Other.prototype); + +assertEq(10, eval("a.f")); +assertEq(undefined, eval("b.f")); diff --git a/js/src/jit-test/tests/jaeger/propertyOptimize-2.js b/js/src/jit-test/tests/jaeger/propertyOptimize-2.js new file mode 100644 index 00000000000..88df13e2c75 --- /dev/null +++ b/js/src/jit-test/tests/jaeger/propertyOptimize-2.js @@ -0,0 +1,16 @@ + +function Foo(x) +{ + this.f = x + 10; +} + +var x = new Foo(0); +assertEq(10, eval("x.f")); + +called = false; +Object.defineProperty(Foo.prototype, 'f', {set: function() { called = true; }}); + +var y = new Foo(0); +assertEq(10, eval("x.f")); +assertEq(undefined, eval("y.f")); +assertEq(called, true); diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index e9edca54c42..2d5c78c0ea4 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -2329,7 +2329,8 @@ class AutoGCRooter { STRING = -14, /* js::AutoStringRooter */ IDVECTOR = -15, /* js::AutoIdVector */ BINDINGS = -16, /* js::Bindings */ - SHAPEVECTOR = -17 /* js::AutoShapeVector */ + SHAPEVECTOR = -17, /* js::AutoShapeVector */ + TYPE = -18 /* js::types::AutoTypeRooter */ }; private: diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 3ecf4f061dc..cb35364f53c 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1611,6 +1611,13 @@ AutoGCRooter::trace(JSTracer *trc) static_cast(this)->bindings.trace(trc); return; } + + case TYPE: { + types::TypeObject *type = static_cast(this)->type; + if (!type->marked) + type->trace(trc); + return; + } } JS_ASSERT(tag >= 0); diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 2d4aab80ec8..6277bc7c5af 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -60,6 +60,7 @@ #include "methodjit/Retcon.h" #include "jsatominlines.h" +#include "jsgcinlines.h" #include "jsinferinlines.h" #include "jsobjinlines.h" #include "jsscriptinlines.h" @@ -382,11 +383,11 @@ TypeSet::addTypeSet(JSContext *cx, ClonedTypeSet *types) inline void TypeSet::add(JSContext *cx, TypeConstraint *constraint, bool callExisting) { - JS_ASSERT_IF(!constraint->condensed() && !constraint->baseSubset(), + JS_ASSERT_IF(!constraint->condensed() && !constraint->persistentObject(), constraint->script->compartment == cx->compartment); JS_ASSERT_IF(!constraint->condensed(), cx->compartment->types.inferenceDepth); JS_ASSERT_IF(typeFlags & TYPE_FLAG_INTERMEDIATE_SET, - !constraint->baseSubset() && !constraint->condensed()); + !constraint->persistentObject() && !constraint->condensed()); if (!constraint) { /* OOM failure while constructing the constraint. */ @@ -432,6 +433,9 @@ TypeSet::print(JSContext *cx) if (typeFlags & TYPE_FLAG_CONFIGURED_PROPERTY) printf(" [configured]"); + if (isDefiniteProperty()) + printf(" [definite:%d]", definiteSlot()); + if (baseFlags() == 0 && !objectCount) { printf(" missing"); return; @@ -512,7 +516,7 @@ public: void newType(JSContext *cx, TypeSet *source, jstype type); - TypeObject * baseSubset() { return object; } + TypeObject * persistentObject() { return object; } }; void @@ -521,6 +525,28 @@ TypeSet::addBaseSubset(JSContext *cx, TypeObject *obj, TypeSet *target) add(cx, cx->new_(obj, target)); } +/* Constraint clearing out newScript and definite properties from an object. */ +class TypeConstraintBaseClearDefinite : public TypeConstraint +{ +public: + TypeObject *object; + + TypeConstraintBaseClearDefinite(TypeObject *object) + : TypeConstraint("baseClearDefinite", (JSScript *) 0x1), + object(object) + {} + + void newType(JSContext *cx, TypeSet *source, jstype type); + + TypeObject * persistentObject() { return object; } +}; + +void +TypeSet::addBaseClearDefinite(JSContext *cx, TypeObject *object) +{ + add(cx, cx->new_(object)); +} + /* Condensed constraint marking a script dependent on this type set. */ class TypeConstraintCondensed : public TypeConstraint { @@ -793,6 +819,18 @@ TypeConstraintBaseSubset::newType(JSContext *cx, TypeSet *source, jstype type) target->addType(cx, type); } +void +TypeConstraintBaseClearDefinite::newType(JSContext *cx, TypeSet *source, jstype type) +{ + /* + * If the type could represent a setter, clear out the newScript shape and + * definite property information from the target object. Any type set with + * a getter/setter becomes unknown, so just watch for this type. + */ + if (type == TYPE_UNKNOWN && object->newScript) + object->clearNewScript(cx); +} + /* Get the object to use for a property access on type. */ static inline TypeObject * GetPropertyObject(JSContext *cx, JSScript *script, jstype type) @@ -1752,6 +1790,19 @@ TypeCompartment::newInitializerTypeObject(JSContext *cx, JSScript *script, res->initializerObject = true; res->initializerOffset = offset; + if (JSOp(script->code[offset]) == JSOP_NEWOBJECT) { + /* + * This object is always constructed the same way and will not be + * observed by other code before all properties have been added. Mark + * all the properties as definite properties of the object. + */ + JS_ASSERT(!script->hasSharps); + JSObject *baseobj = script->getObject(GET_SLOTNO(script->code + offset)); + + if (!res->addDefiniteProperties(cx, baseobj, false)) + return NULL; + } + return res; } @@ -2306,7 +2357,11 @@ TypeCompartment::fixArrayType(JSContext *cx, JSObject *obj) if (p) { obj->setType(p->value); } else { - TypeObject *objType = newTypeObject(cx, NULL, "TableArray", false, true, obj->getProto()); + static unsigned count = 0; + char *name = (char *) alloca(20); + JS_snprintf(name, 20, "TableArray:%u", ++count); + + TypeObject *objType = newTypeObject(cx, NULL, name, false, true, obj->getProto()); if (!objType) { js_ReportOutOfMemory(cx); return false; @@ -2335,19 +2390,23 @@ struct ObjectTableKey { jsid *ids; uint32 nslots; + uint32 nfixed; JSObject *proto; typedef JSObject * Lookup; static inline uint32 hash(JSObject *obj) { return (uint32) (JSID_BITS(obj->lastProperty()->id) ^ - obj->slotSpan() ^ + obj->slotSpan() ^ obj->numFixedSlots() ^ ((uint32)(size_t)obj->getProto() >> 2)); } static inline bool match(const ObjectTableKey &v, JSObject *obj) { - if (obj->slotSpan() != v.nslots || obj->getProto() != v.proto) + if (obj->slotSpan() != v.nslots || + obj->numFixedSlots() != v.nfixed || + obj->getProto() != v.proto) { return false; + } const Shape *shape = obj->lastProperty(); while (!JSID_IS_EMPTY(shape->id)) { if (shape->id != v.ids[shape->slot]) @@ -2431,7 +2490,11 @@ TypeCompartment::fixObjectType(JSContext *cx, JSObject *obj) } AutoObjectRooter xvr(cx, xobj); - TypeObject *objType = newTypeObject(cx, NULL, "TableObject", false, false, obj->getProto()); + static unsigned count = 0; + char *name = (char *) alloca(20); + JS_snprintf(name, 20, "TableObject:%u", ++count); + + TypeObject *objType = newTypeObject(cx, NULL, name, false, false, obj->getProto()); if (!objType) { js_ReportOutOfMemory(cx); return false; @@ -2465,9 +2528,13 @@ TypeCompartment::fixObjectType(JSContext *cx, JSObject *obj) JS_ASSERT(!xobj->inDictionaryMode()); const Shape *newShape = xobj->lastProperty(); + if (!objType->addDefiniteProperties(cx, xobj, false)) + return false; + ObjectTableKey key; key.ids = ids; key.nslots = obj->slotSpan(); + key.nfixed = obj->numFixedSlots(); key.proto = obj->getProto(); JS_ASSERT(ObjectTableKey::match(key, obj)); @@ -2583,6 +2650,57 @@ TypeObject::addProperty(JSContext *cx, jsid id, Property **pprop) return true; } +bool +TypeObject::addDefiniteProperties(JSContext *cx, JSObject *obj, bool clearUnknown) +{ + if (unknownProperties()) + return true; + + /* + * Mark all properties of obj as definite properties of this type. Return + * false if there is a setter/getter for any of the properties in the + * type's prototype. + */ + AutoEnterTypeInference enter(cx); + + const Shape *shape = obj->lastProperty(); + while (!JSID_IS_EMPTY(shape->id)) { + jsid id = MakeTypeId(cx, shape->id); + if (!JSID_IS_VOID(id) && obj->isFixedSlot(shape->slot) && + shape->slot <= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) { + TypeSet *types = getProperty(cx, id, true); + if (!types) + return false; + types->setDefinite(shape->slot); + + if (clearUnknown && proto) { + /* + * Ensure that if any of the properties named here could have + * a setter in the direct prototype (and thus its transitive + * prototypes), the definite properties and new shape attached + * to this object get cleared out. clearUnknown is set if the + * definite properties are affected by prototype setters + * (i.e. objects from scripted 'new', but not objects from + * initializers). + */ + TypeSet *parentTypes = proto->getType()->getProperty(cx, id, false); + if (!parentTypes || parentTypes->unknown()) + return false; + parentTypes->addBaseClearDefinite(cx, this); + } + } else { + /* + * We should have filtered these properties out before adding them + * to the shape associated with the new type. + */ + JS_ASSERT(!clearUnknown); + } + shape = shape->previous(); + } + + return cx->compartment->types.checkPendingRecompiles(cx); +} + void TypeObject::setFlags(JSContext *cx, TypeObjectFlags flags) { @@ -2634,6 +2752,34 @@ TypeObject::markUnknown(JSContext *cx) } } +void +TypeObject::clearNewScript(JSContext *cx) +{ + JS_ASSERT(newScript); + newScript = NULL; + + AutoEnterTypeInference enter(cx); + + /* + * Any definite properties we added due to analysis of the new script when + * the type object was created are now invalid: objects with the same type + * can be created by using 'new' on a different script or through some + * other mechanism (e.g. Object.create). Rather than clear out the definite + * bits on the object's properties, just mark such properties as having + * been deleted/reconfigured, which will have the same effect on JITs + * wanting to use the definite bits to optimize property accesses. + */ + for (unsigned i = 0; i < getPropertyCount(); i++) { + Property *prop = getProperty(i); + if (!prop) + continue; + if (prop->types.isDefiniteProperty()) + prop->types.setOwnProperty(cx, true); + } + + cx->compartment->types.checkPendingRecompiles(cx); // :XXX: handle failure +} + void TypeObject::print(JSContext *cx) { @@ -3774,6 +3920,172 @@ AnalyzeScriptNew(JSContext *cx, JSScript *script) prototypeTypes->addNewObject(cx, script, funType, script->thisTypes()); } +JSObject * +AnalyzeScriptProperties(JSContext *cx, JSScript *script) +{ + /* + * When invoking 'new' on the specified script, try to find some properties + * which will definitely be added to the created object before it has a + * chance to escape and be accessed elsewhere. This analysis is a forward + * scan through the script looking for assignments to 'this.f'. Any + * branching kills it, along with any use of 'this' other than for property + * assignments. + */ + + /* Strawman object to add properties to and watch for duplicates. */ + JSObject *baseobj = NewBuiltinClassInstance(cx, &js_ObjectClass, gc::FINALIZE_OBJECT16); + if (!baseobj) + return NULL; + + /* Number of added properties. */ + unsigned numProperties = 0; + + /* If 'this' is on the stack, index of its stack slot. */ + unsigned thisSlot = unsigned(-1); + + unsigned offset = 0; + unsigned depth = 0; + while (offset < script->length) { + jsbytecode *pc = script->code + offset; + JSOp op = JSOp(*pc); + + unsigned nuses = analyze::GetUseCount(script, offset); + unsigned ndefs = analyze::GetDefCount(script, offset); + + bool poppedThis = false; + if (thisSlot != unsigned(-1) && thisSlot >= depth - nuses) { + if (op != JSOP_SETPROP || thisSlot != depth - 2) { + /* + * 'this' escapes here and may be accessed before subsequent + * properties are added to the object. + */ + return baseobj; + } + poppedThis = true; + thisSlot = unsigned(-1); + } + + depth -= nuses; + depth += ndefs; + + switch (JSOp(*pc)) { + + case JSOP_THIS: + thisSlot = depth - 1; + break; + + case JSOP_SETPROP: { + if (!poppedThis) + return baseobj; + jsid id = GetAtomId(cx, script, pc, 0); + if (JSID_IS_VOID(id)) + return baseobj; + if (id == id_prototype(cx) || id == id___proto__(cx) || id == id_constructor(cx)) + return baseobj; + if (!js_DefineNativeProperty(cx, baseobj, id, UndefinedValue(), NULL, NULL, + JSPROP_ENUMERATE, 0, 0, NULL, 0)) { + return NULL; + } + numProperties++; + if (baseobj->slotSpan() != numProperties) { + /* Set a duplicate property. */ + return baseobj; + } + if (baseobj->inDictionaryMode()) + return NULL; + if (numProperties >= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) { + /* Maximum number of definite properties added. */ + return baseobj; + } + break; + } + + /* Whitelist of other ops that can be used while initializing 'this' properties. */ + case JSOP_POP: + case JSOP_NOP: + case JSOP_LINENO: + case JSOP_POPN: + case JSOP_VOID: + case JSOP_PUSH: + case JSOP_ZERO: + case JSOP_ONE: + case JSOP_INT8: + case JSOP_INT32: + case JSOP_UINT16: + case JSOP_UINT24: + case JSOP_BITAND: + case JSOP_BITOR: + case JSOP_BITXOR: + case JSOP_BITNOT: + case JSOP_RSH: + case JSOP_LSH: + case JSOP_URSH: + case JSOP_FALSE: + case JSOP_TRUE: + case JSOP_EQ: + case JSOP_NE: + case JSOP_LT: + case JSOP_LE: + case JSOP_GT: + case JSOP_GE: + case JSOP_NOT: + case JSOP_STRICTEQ: + case JSOP_STRICTNE: + case JSOP_IN: + case JSOP_DOUBLE: + case JSOP_STRING: + case JSOP_REGEXP: + case JSOP_DUP: + case JSOP_DUP2: + case JSOP_GETGLOBAL: + case JSOP_GETGNAME: + case JSOP_GETARG: + case JSOP_SETARG: + case JSOP_INCARG: + case JSOP_DECARG: + case JSOP_ARGINC: + case JSOP_ARGDEC: + case JSOP_GETLOCAL: + case JSOP_SETLOCAL: + case JSOP_SETLOCALPOP: + case JSOP_INCLOCAL: + case JSOP_DECLOCAL: + case JSOP_LOCALINC: + case JSOP_LOCALDEC: + case JSOP_GETPROP: + case JSOP_GETARGPROP: + case JSOP_GETLOCALPROP: + case JSOP_GETELEM: + case JSOP_LENGTH: + case JSOP_ADD: + case JSOP_SUB: + case JSOP_MUL: + case JSOP_MOD: + case JSOP_DIV: + case JSOP_NEG: + case JSOP_POS: + case JSOP_NEWINIT: + case JSOP_NEWARRAY: + case JSOP_NEWOBJECT: + case JSOP_ENDINIT: + case JSOP_INITELEM: + case JSOP_HOLE: + case JSOP_INITPROP: + case JSOP_INITMETHOD: + break; + + default: + return baseobj; + } + + offset += analyze::GetBytecodeLength(pc); + } + + /* We should have bailed out on a JSOP_STOP or similar. */ + JS_NOT_REACHED("Mystery!"); + return baseobj; +} + ///////////////////////////////////////////////////////////////////// // Printing ///////////////////////////////////////////////////////////////////// @@ -4194,7 +4506,7 @@ JSScript::typeCheckBytecode(JSContext *cx, const jsbytecode *pc, const js::Value ///////////////////////////////////////////////////////////////////// void -JSObject::makeNewType(JSContext *cx) +JSObject::makeNewType(JSContext *cx, JSScript *newScript) { JS_ASSERT(!newType); @@ -4202,18 +4514,51 @@ JSObject::makeNewType(JSContext *cx) if (!type) return; - if (cx->typeInferenceEnabled() && !getType()->unknownProperties()) { - js::types::AutoEnterTypeInference enter(cx); + if (!cx->typeInferenceEnabled()) { + newType = type; + setDelegate(); + return; + } + js::types::AutoEnterTypeInference enter(cx); + + if (!getType()->unknownProperties()) { /* Update the possible 'new' types for all prototype objects sharing the same type object. */ js::types::TypeSet *types = getType()->getProperty(cx, JSID_EMPTY, true); if (types) types->addType(cx, (js::types::jstype) type); - - if (!cx->compartment->types.checkPendingRecompiles(cx)) - return; } + if (newScript && !type->unknownProperties()) { + JSObject *baseobj = js::types::AnalyzeScriptProperties(cx, newScript); + if (baseobj && baseobj->slotSpan() > 0) { + 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()); + + /* + * The base object was created with a different type and + * finalize kind than we will use for subsequent new objects. + * Generate an object with the appropriate final shape. + */ + baseobj = NewReshapedObject(cx, type, baseobj->getParent(), kind, + (const js::Shape *) baseobj->lastProperty()); + if (!baseobj) + return; + + if (!type->addDefiniteProperties(cx, baseobj, true)) + return; + + type->newScript = newScript; + type->newScriptFinalizeKind = unsigned(kind); + type->newScriptShape = (js::Shape *) baseobj->lastProperty(); + } + } + + if (!cx->compartment->types.checkPendingRecompiles(cx)) + return; + newType = type; setDelegate(); } @@ -4261,6 +4606,11 @@ types::TypeObject::trace(JSTracer *trc) if (singleton) gc::MarkObject(trc, *singleton, "type_singleton"); + + if (newScript) { + js_TraceScript(trc, newScript); + gc::MarkShape(trc, newScriptShape, "new_shape"); + } } /* @@ -4343,7 +4693,7 @@ TypeSet::CondenseSweepTypeSet(JSContext *cx, TypeCompartment *compartment, while (constraint) { TypeConstraint *next = constraint->next; - TypeObject *object = constraint->baseSubset(); + TypeObject *object = constraint->persistentObject(); if (object) { /* * Constraint propagating data between objects. If the target diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index 39d4df96abd..961fb4b3211 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -197,11 +197,12 @@ public: virtual bool condensed() { return false; } /* - * If this is a persistent subset constraint, the object being propagated - * into. Such constraints describe relationships between TypeObject - * properties which are independent of the analysis of any script. + * If this is a persistent constraint other than a condensed constraint, + * the target object of the constraint. Such constraints describe + * relationships between TypeObjects which are independent of the analysis + * of any script. */ - virtual TypeObject * baseSubset() { return NULL; } + virtual TypeObject * persistentObject() { return NULL; } }; /* @@ -241,19 +242,33 @@ enum { TYPE_FLAG_UNKNOWN = 1 << TYPE_UNKNOWN, /* Flag for type sets which are cleared on GC. */ - TYPE_FLAG_INTERMEDIATE_SET = 0x1000, + TYPE_FLAG_INTERMEDIATE_SET = 0x0100, - /* For object property type sets, whether this property has been directly written. */ - TYPE_FLAG_OWN_PROPERTY = 0x2000, + /* Flags for type sets which are on object properties. */ + + /* Whether this property has ever been directly written. */ + TYPE_FLAG_OWN_PROPERTY = 0x0200, /* - * For object property type sets, whether the property has ever been - * deleted or reconfigured as non-writable. + * Whether the property has ever been deleted or reconfigured to behave + * differently from a normal native property (e.g. made non-writable or + * given a scripted getter or setter). */ - TYPE_FLAG_CONFIGURED_PROPERTY = 0x4000, + TYPE_FLAG_CONFIGURED_PROPERTY = 0x0400, + + /* + * Whether the property is definitely in a particular inline slot on all + * objects from which it has not been deleted or reconfigured. Implies + * OWN_PROPERTY and unlike OWN/CONFIGURED property, this cannot change. + */ + TYPE_FLAG_DEFINITE_PROPERTY = 0x0800, + + /* If the property is definite, mask and shift storing the slot. */ + TYPE_FLAG_DEFINITE_MASK = 0xf000, + TYPE_FLAG_DEFINITE_SHIFT = 12, /* Mask of non-type flags on a type set. */ - TYPE_FLAG_BASE_MASK = 0x7000 + TYPE_FLAG_BASE_MASK = 0xffffff00 }; /* Vector of the above flags. */ @@ -280,13 +295,6 @@ class TypeSet void print(JSContext *cx); - void setIntermediate() { typeFlags |= TYPE_FLAG_INTERMEDIATE_SET; } - void setOwnProperty(bool configurable) { - typeFlags |= TYPE_FLAG_OWN_PROPERTY; - if (configurable) - typeFlags |= TYPE_FLAG_CONFIGURED_PROPERTY; - } - inline void destroy(JSContext *cx); /* Whether this set contains a specific type. */ @@ -296,6 +304,12 @@ class TypeSet bool hasAnyFlag(TypeFlags flags) { return typeFlags & flags; } bool unknown() { return typeFlags & TYPE_FLAG_UNKNOWN; } + bool isDefiniteProperty() { return typeFlags & TYPE_FLAG_DEFINITE_PROPERTY; } + unsigned definiteSlot() { + JS_ASSERT(isDefiniteProperty()); + return typeFlags >> TYPE_FLAG_DEFINITE_SHIFT; + } + /* * Add a type to this set, calling any constraint handlers if this is a new * possible type. @@ -316,6 +330,17 @@ class TypeSet inline unsigned getObjectCount(); inline TypeObject *getObject(unsigned i); + void setIntermediate() { typeFlags |= TYPE_FLAG_INTERMEDIATE_SET; } + void setOwnProperty(bool configurable) { + typeFlags |= TYPE_FLAG_OWN_PROPERTY; + if (configurable) + typeFlags |= TYPE_FLAG_CONFIGURED_PROPERTY; + } + 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); @@ -333,6 +358,7 @@ class TypeSet void addMonitorRead(JSContext *cx, JSScript *script, TypeSet *target); void addBaseSubset(JSContext *cx, TypeObject *object, TypeSet *target); + void addBaseClearDefinite(JSContext *cx, TypeObject *object); void addCondensed(JSContext *cx, JSScript *script); /* @@ -477,6 +503,15 @@ struct TypeObject /* Mark bit for GC. */ bool marked; + /* + * If non-NULL, objects of this type have always been constructed using + * 'new' on the specified script. Moreover the given finalize kind and + * initial shape should also be used for the object. + */ + JSScript *newScript; + /* gc::FinalizeKind */ unsigned newScriptFinalizeKind; + Shape *newScriptShape; + /* * Whether this is an Object or Array keyed to an offset in the script containing * this in its objects list. @@ -564,9 +599,11 @@ struct TypeObject /* Helpers */ bool addProperty(JSContext *cx, jsid id, Property **pprop); + bool addDefiniteProperties(JSContext *cx, JSObject *obj, bool clearUnknown); void addPrototype(JSContext *cx, TypeObject *proto); void setFlags(JSContext *cx, TypeObjectFlags flags); void markUnknown(JSContext *cx); + void clearNewScript(JSContext *cx); void storeToInstances(JSContext *cx, Property *base); void getFromPrototypes(JSContext *cx, Property *base); diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index ed74ec1731f..2d74f3cbb93 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -1094,7 +1094,7 @@ TypeSet::destroy(JSContext *cx) ::js_free(objectSet); while (constraintList) { TypeConstraint *next = constraintList->next; - if (constraintList->condensed() || constraintList->baseSubset()) + if (constraintList->condensed() || constraintList->persistentObject()) ::js_free(constraintList); constraintList = next; } @@ -1169,7 +1169,7 @@ TypeSet::addType(JSContext *cx, jstype type) /* Propagate the type to all constraints. */ TypeConstraint *constraint = constraintList; while (constraint) { - JS_ASSERT_IF(!constraint->baseSubset(), + JS_ASSERT_IF(!constraint->persistentObject(), constraint->script->compartment == cx->compartment); cx->compartment->types.addPending(cx, constraint, this, type); constraint = constraint->next; @@ -1191,7 +1191,7 @@ TypeSet::setOwnProperty(JSContext *cx, bool configured) /* Propagate the change to all constraints. */ TypeConstraint *constraint = constraintList; while (constraint) { - JS_ASSERT_IF(!constraint->baseSubset(), + JS_ASSERT_IF(!constraint->persistentObject(), constraint->script->compartment == cx->compartment); constraint->newPropertyState(cx, this); constraint = constraint->next; @@ -1368,6 +1368,7 @@ TypeObject::name() inline TypeObject::TypeObject(jsid name, JSObject *proto) : proto(proto), emptyShapes(NULL), flags(0), isFunction(false), marked(false), + newScript(NULL), newScriptFinalizeKind(0), newScriptShape(NULL), initializerObject(false), initializerArray(false), initializerOffset(0), contribution(0), propertySet(NULL), propertyCount(0), instanceList(NULL), instanceNext(NULL), next(NULL), @@ -1411,6 +1412,23 @@ SweepClonedTypes(ClonedTypeSet *types) } } +class AutoTypeRooter : private AutoGCRooter { + public: + AutoTypeRooter(JSContext *cx, TypeObject *type + JS_GUARD_OBJECT_NOTIFIER_PARAM) + : AutoGCRooter(cx, TYPE), type(type) + { + JS_GUARD_OBJECT_NOTIFIER_INIT; + } + + friend void AutoGCRooter::trace(JSTracer *trc); + friend void MarkRuntime(JSTracer *trc); + + private: + TypeObject *type; + JS_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + } } /* namespace js::types */ #endif // jsinferinlines_h___ diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 3c82e515b8a..4e19a973184 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -2892,6 +2892,41 @@ js_Object(JSContext *cx, uintN argc, Value *vp) return JS_TRUE; } +JSObject * +js::NewReshapedObject(JSContext *cx, TypeObject *type, JSObject *parent, + gc::FinalizeKind kind, const Shape *shape) +{ + JSObject *res = NewObjectWithType(cx, type, parent, kind); + if (!res) + return NULL; + + if (JSID_IS_EMPTY(shape->id)) + return res; + + /* Get all the ids in the object, in order. */ + js::AutoIdVector ids(cx); + for (unsigned i = 0; i <= shape->slot; i++) { + if (!ids.append(JSID_VOID)) + return NULL; + } + const js::Shape *nshape = shape; + while (!JSID_IS_EMPTY(nshape->id)) { + ids[nshape->slot] = nshape->id; + nshape = nshape->previous(); + } + + /* Construct the new shape. */ + for (unsigned i = 0; i < ids.length(); i++) { + if (!js_DefineNativeProperty(cx, res, ids[i], js::UndefinedValue(), NULL, NULL, + JSPROP_ENUMERATE, 0, 0, NULL, 0)) { + return NULL; + } + } + JS_ASSERT(!res->inDictionaryMode()); + + return res; +} + JSObject* js_CreateThis(JSContext *cx, JSObject *callee) { @@ -2920,7 +2955,20 @@ js_CreateThis(JSContext *cx, JSObject *callee) JSObject * js_CreateThisForFunctionWithProto(JSContext *cx, JSObject *callee, JSObject *proto) { - /* Caller must ensure that proto's new type is not marked as an array. */ + if (proto) { + JSScript *calleeScript = callee->getFunctionPrivate()->script(); + types::TypeObject *type = proto->getNewType(cx, calleeScript); + + if (type && type->newScript) { + JS_ASSERT(type->newScript == calleeScript); + gc::FinalizeKind kind = gc::FinalizeKind(type->newScriptFinalizeKind); + JSObject *res = NewObjectWithType(cx, type, callee->getParent(), kind); + if (res) + res->setMap(type->newScriptShape); + return res; + } + } + gc::FinalizeKind kind = NewObjectGCKind(cx, &js_ObjectClass); return NewNonFunction(cx, &js_ObjectClass, proto, callee->getParent(), kind); } @@ -2935,23 +2983,34 @@ js_CreateThisForFunction(JSContext *cx, JSObject *callee, bool newType) return NULL; } JSObject *proto; - if (protov.isObject()) { + if (protov.isObject()) proto = &protov.toObject(); - TypeObject *type = proto->getNewType(cx); - if (!type) - return NULL; - } else { + else proto = NULL; - } JSObject *obj = js_CreateThisForFunctionWithProto(cx, callee, proto); + if (obj && newType) { + /* + * Make a new type and a new object with the type, reshaped according + * to any properties already added by CreateThisForFunctionWithProto. + */ JS_ASSERT(cx->typeInferenceEnabled()); - types::TypeObject *type = cx->newTypeObject("SpecializedThis", obj->getProto()); - if (!type || !obj->setTypeAndUniqueShape(cx, type)) + + static unsigned count = 0; + char *name = (char *) alloca(30); + JS_snprintf(name, 30, "SpecializedThis:%u", ++count); + + types::TypeObject *type = cx->newTypeObject(name, obj->getProto()); + types::AutoTypeRooter root(cx, type); + + obj = NewReshapedObject(cx, type, obj->getParent(), gc::FinalizeKind(obj->finalizeKind()), + (const Shape *) obj->lastProperty()); + if (!obj) return NULL; if (!callee->getFunctionPrivate()->script()->typeSetThis(cx, (types::jstype) type)) return NULL; } + return obj; } @@ -3038,9 +3097,6 @@ js_CreateThisFromTrace(JSContext *cx, JSObject *ctor, uintN protoSlot) const Value &protov = ctor->getSlotRef(protoSlot); if (protov.isObject()) { proto = &protov.toObject(); - TypeObject *type = proto->getNewType(cx); - if (!type) - return NULL; } else { /* * GetInterpretedFunctionPrototype found that ctor.prototype is diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 0550e41b4ff..1bf1cc483a6 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -654,9 +654,10 @@ struct JSObject : js::gc::Cell { /* Index into the dynamic slots array to use for a dynamic slot. */ inline size_t dynamicSlotIndex(size_t slot); + inline size_t numFixedSlots() const; + private: inline js::Value* fixedSlots() const; - inline size_t numFixedSlots() const; inline bool hasSlotsArray() const; public: @@ -789,8 +790,8 @@ struct JSObject : js::gc::Cell { inline bool setTypeAndEmptyShape(JSContext *cx, js::types::TypeObject *newType); inline void setTypeAndShape(js::types::TypeObject *newType, const js::Shape *newShape); - inline js::types::TypeObject *getNewType(JSContext *cx); - void makeNewType(JSContext *cx); + inline js::types::TypeObject *getNewType(JSContext *cx, JSScript *script = NULL); + void makeNewType(JSContext *cx, JSScript *script); JSObject * getProto() const { return type->proto; diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index 47c7b820eeb..9e00e10db43 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -858,12 +858,16 @@ JSObject::setWithThis(JSObject *thisp) } inline js::types::TypeObject * -JSObject::getNewType(JSContext *cx) +JSObject::getNewType(JSContext *cx, JSScript *script) { if (isDenseArray() && !makeDenseArraySlow(cx)) return NULL; - if (!newType) - makeNewType(cx); + if (newType) { + if (newType->newScript != script && newType->newScript) + newType->clearNewScript(cx); + } else { + makeNewType(cx, script); + } return newType; } @@ -1370,6 +1374,39 @@ NewObject(JSContext *cx, js::Class *clasp, JSObject *proto, JSObject *parent) return NewObject(cx, clasp, proto, parent, kind); } +/* + * Create a plain object with the specified type. This bypasses getNewType to + * avoid losing creation site information for objects made by scripted 'new'. + */ +static JS_ALWAYS_INLINE JSObject * +NewObjectWithType(JSContext *cx, types::TypeObject *type, JSObject *parent, gc::FinalizeKind kind) +{ + JSObject* obj = js_NewGCObject(cx, kind); + if (!obj) + goto out; + + /* + * Default parent to the parent of the prototype, which was set from + * the parent of the prototype's constructor. + */ + obj->init(cx, &js_ObjectClass, type, + (!parent && type->proto) ? type->proto->getParent() : parent, + NULL, false); + + if (!InitScopeForObject(cx, obj, &js_ObjectClass, type, kind)) { + obj = NULL; + goto out; + } + +out: + Probes::createObject(cx, obj); + return obj; +} + +extern JSObject * +NewReshapedObject(JSContext *cx, types::TypeObject *type, JSObject *parent, + gc::FinalizeKind kind, const Shape *shape); + /* * As for gc::GetGCObjectKind, where numSlots is a guess at the final size of * the object, zero if the final size is unknown. This should only be used for diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index f9c1aacfe80..39b53ebe5dd 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -1922,6 +1922,8 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_GETTHISPROP) /* Push thisv onto stack. */ jsop_this(); + if (cx->typeInferenceEnabled()) + frame.extra(frame.peek(-1)).types = script->thisTypes(); if (!jsop_getprop(script->getAtom(fullAtomIndex(PC)), knownPushedType(0))) return Compile_Error; END_CASE(JSOP_GETTHISPROP); @@ -1931,6 +1933,8 @@ mjit::Compiler::generateMethod() /* Push arg onto stack. */ uint32 arg = GET_SLOTNO(PC); frame.pushArg(arg, knownArgumentType(arg)); + if (cx->typeInferenceEnabled()) + frame.extra(frame.peek(-1)).types = script->argTypes(arg); if (!jsop_getprop(script->getAtom(fullAtomIndex(&PC[ARGNO_LEN])), knownPushedType(0))) return Compile_Error; } @@ -1940,6 +1944,8 @@ mjit::Compiler::generateMethod() { uint32 local = GET_SLOTNO(PC); frame.pushLocal(local, knownLocalType(local)); + if (cx->typeInferenceEnabled() && local < script->nfixed) + frame.extra(frame.peek(-1)).types = script->localTypes(local); if (!jsop_getprop(script->getAtom(fullAtomIndex(&PC[SLOTNO_LEN])), knownPushedType(0))) return Compile_Error; } @@ -2380,14 +2386,22 @@ mjit::Compiler::generateMethod() END_CASE(JSOP_BINDNAME) BEGIN_CASE(JSOP_SETPROP) - if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true)) + { + jsbytecode *next = &PC[JSOP_SETLOCAL_LENGTH]; + bool pop = JSOp(*next) == JSOP_POP && !a->analysis.jumpTarget(next); + if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true, pop)) return Compile_Error; + } END_CASE(JSOP_SETPROP) BEGIN_CASE(JSOP_SETNAME) BEGIN_CASE(JSOP_SETMETHOD) - if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true)) + { + jsbytecode *next = &PC[JSOP_SETLOCAL_LENGTH]; + bool pop = JSOp(*next) == JSOP_POP && !a->analysis.jumpTarget(next); + if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true, pop)) return Compile_Error; + } END_CASE(JSOP_SETNAME) BEGIN_CASE(JSOP_THROW) @@ -4362,6 +4376,39 @@ mjit::Compiler::jsop_getprop(JSAtom *atom, JSValueType knownType, frame.forgetMismatchedObject(top); + /* + * 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); + types::TypeSet *types = frame.extra(top).types; + if ((op == JSOP_GETPROP || op == JSOP_GETTHISPROP || op == JSOP_GETARGPROP || op == JSOP_GETLOCALPROP) && + 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); + if (!propertyTypes) + return false; + if (propertyTypes->isDefiniteProperty() && !propertyTypes->isOwnProperty(cx, true)) { + types->addFreeze(cx); + uint32 slot = propertyTypes->definiteSlot(); + if (!top->isTypeKnown()) { + Jump notObject = frame.testObject(Assembler::NotEqual, top); + stubcc.linkExit(notObject, Uses(1)); + stubcc.leave(); + OOL_STUBCALL(stubs::GetProp); + } + RegisterID reg = frame.tempRegForData(top); + frame.pop(); + frame.push(Address(reg, JSObject::getFixedSlotOffset(slot)), knownType); + if (!top->isTypeKnown()) + stubcc.rejoin(Changes(1)); + return true; + } + } + /* * These two must be loaded first. The objReg because the string path * wants to read it, and the shapeReg because it could cause a spill that @@ -4867,7 +4914,7 @@ mjit::Compiler::jsop_callprop(JSAtom *atom) } bool -mjit::Compiler::jsop_setprop(JSAtom *atom, bool usePropCache) +mjit::Compiler::jsop_setprop(JSAtom *atom, bool usePropCache, bool popGuaranteed) { REJOIN_SITE_2(usePropCache ? STRICT_VARIANT(stubs::SetName) @@ -4883,6 +4930,38 @@ mjit::Compiler::jsop_setprop(JSAtom *atom, bool usePropCache) return true; } + /* + * Set the property directly if we are accessing a known object which + * always has the property in a particular inline slot. + */ + types::TypeSet *types = frame.extra(lhs).types; + if (JSOp(*PC) == JSOP_SETPROP && 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); + if (!propertyTypes) + return false; + if (propertyTypes->isDefiniteProperty() && !propertyTypes->isOwnProperty(cx, true)) { + types->addFreeze(cx); + uint32 slot = propertyTypes->definiteSlot(); + if (!lhs->isTypeKnown()) { + Jump notObject = frame.testObject(Assembler::NotEqual, lhs); + stubcc.linkExit(notObject, Uses(2)); + stubcc.leave(); + masm.move(ImmPtr(atom), Registers::ArgReg1); + OOL_STUBCALL(STRICT_VARIANT(stubs::SetName)); + } + RegisterID reg = frame.tempRegForData(lhs); + frame.storeTo(rhs, Address(reg, JSObject::getFixedSlotOffset(slot)), popGuaranteed); + frame.shimmy(1); + if (!lhs->isTypeKnown()) + stubcc.rejoin(Changes(1)); + return true; + } + } + JSOp op = JSOp(*PC); ic::PICInfo::Kind kind = (op == JSOP_SETMETHOD) @@ -5410,7 +5489,7 @@ mjit::Compiler::jsop_nameinc(JSOp op, VoidStubAtom stub, uint32 index) return Compile_Retry; // OBJ N+1 - if (!jsop_setprop(atom, false)) + if (!jsop_setprop(atom, false, pop)) return Compile_Error; // N+1 @@ -5439,7 +5518,7 @@ mjit::Compiler::jsop_nameinc(JSOp op, VoidStubAtom stub, uint32 index) return Compile_Retry; // N OBJ N+1 - if (!jsop_setprop(atom, false)) + if (!jsop_setprop(atom, false, true)) return Compile_Error; // N N+1 @@ -5499,7 +5578,7 @@ mjit::Compiler::jsop_propinc(JSOp op, VoidStubAtom stub, uint32 index) frame.shimmy(1); // OBJ V+1 - if (!jsop_setprop(atom, false)) + if (!jsop_setprop(atom, false, pop)) return Compile_Error; // V+1 @@ -5535,7 +5614,7 @@ mjit::Compiler::jsop_propinc(JSOp op, VoidStubAtom stub, uint32 index) frame.dupAt(-2); // OBJ N N+1 OBJ N+1 - if (!jsop_setprop(atom, false)) + if (!jsop_setprop(atom, false, true)) return Compile_Error; // OBJ N N+1 N+1 @@ -6883,7 +6962,7 @@ mjit::Compiler::jsop_forprop(JSAtom *atom) // Before: ITER OBJ VALUE // After: ITER VALUE - jsop_setprop(atom, false); + jsop_setprop(atom, false, true); // Before: ITER VALUE // After: ITER diff --git a/js/src/methodjit/Compiler.h b/js/src/methodjit/Compiler.h index 319f0ec4f58..7b9ec3fd9e6 100644 --- a/js/src/methodjit/Compiler.h +++ b/js/src/methodjit/Compiler.h @@ -690,7 +690,7 @@ class Compiler : public BaseCompiler bool jsop_getprop(JSAtom *atom, JSValueType type, bool typeCheck = true, bool usePropCache = true); bool jsop_length(); - bool jsop_setprop(JSAtom *atom, 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); diff --git a/js/src/methodjit/FrameState-inl.h b/js/src/methodjit/FrameState-inl.h index 6cf02fc85b3..693097b3ef7 100644 --- a/js/src/methodjit/FrameState-inl.h +++ b/js/src/methodjit/FrameState-inl.h @@ -320,8 +320,11 @@ FrameState::push(Address address, JSValueType knownType, bool reuseBase) // Prevent us from clobbering this reg. bool free = a->freeRegs.hasReg(address.base); + bool needsPin = !free && regstate(address.base).fe(); if (free) a->freeRegs.takeReg(address.base); + if (needsPin) + pinReg(address.base); RegisterID typeReg = allocReg(); @@ -332,6 +335,8 @@ FrameState::push(Address address, JSValueType knownType, bool reuseBase) // writes to the register. if (free) a->freeRegs.putReg(address.base); + if (needsPin) + unpinReg(address.base); RegisterID dataReg = reuseBase ? address.base : allocReg(); masm.loadPayload(address, dataReg);