new file mode 100644
--- /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"));
new file mode 100644
--- /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);
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -2324,17 +2324,18 @@ class AutoGCRooter {
XML = -9, /* js::AutoXMLRooter */
OBJECT = -10, /* js::AutoObjectRooter */
ID = -11, /* js::AutoIdRooter */
VALVECTOR = -12, /* js::AutoValueVector */
DESCRIPTOR = -13, /* js::AutoPropertyDescriptorRooter */
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:
/* No copy or assignment semantics. */
AutoGCRooter(AutoGCRooter &ida);
void operator=(AutoGCRooter &ida);
};
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1606,16 +1606,23 @@ AutoGCRooter::trace(JSTracer *trc)
MarkShapeRange(trc, vector.length(), vector.begin(), "js::AutoShapeVector.vector");
return;
}
case BINDINGS: {
static_cast<js::AutoBindingsRooter *>(this)->bindings.trace(trc);
return;
}
+
+ case TYPE: {
+ types::TypeObject *type = static_cast<types::AutoTypeRooter *>(this)->type;
+ if (!type->marked)
+ type->trace(trc);
+ return;
+ }
}
JS_ASSERT(tag >= 0);
MarkValueRange(trc, tag, static_cast<AutoArrayRooter *>(this)->array, "js::AutoArrayRooter.array");
}
namespace js {
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -55,16 +55,17 @@
#include "jsstr.h"
#include "jstl.h"
#include "jsiter.h"
#include "methodjit/MethodJIT.h"
#include "methodjit/Retcon.h"
#include "jsatominlines.h"
+#include "jsgcinlines.h"
#include "jsinferinlines.h"
#include "jsobjinlines.h"
#include "jsscriptinlines.h"
#ifdef JS_HAS_XML_SUPPORT
#include "jsxml.h"
#endif
@@ -377,21 +378,21 @@ TypeSet::addTypeSet(JSContext *cx, Clone
} else if (types->objectCount == 1) {
addType(cx, (jstype) types->objectSet);
}
}
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. */
cx->compartment->types.setPendingNukeTypes(cx);
}
InferSpew(ISpewOps, "addConstraint: T%p C%p %s",
this, constraint, constraint->kind());
@@ -427,16 +428,19 @@ TypeSet::add(JSContext *cx, TypeConstrai
void
TypeSet::print(JSContext *cx)
{
if (typeFlags & TYPE_FLAG_OWN_PROPERTY)
printf(" [own]");
if (typeFlags & TYPE_FLAG_CONFIGURED_PROPERTY)
printf(" [configured]");
+ if (isDefiniteProperty())
+ printf(" [definite:%d]", definiteSlot());
+
if (baseFlags() == 0 && !objectCount) {
printf(" missing");
return;
}
if (typeFlags & TYPE_FLAG_UNKNOWN)
printf(" unknown");
@@ -507,25 +511,47 @@ public:
TypeConstraintBaseSubset(TypeObject *object, TypeSet *target)
: TypeConstraint("baseSubset", (JSScript *) 0x1),
object(object), target(target)
{}
void newType(JSContext *cx, TypeSet *source, jstype type);
- TypeObject * baseSubset() { return object; }
+ TypeObject * persistentObject() { return object; }
};
void
TypeSet::addBaseSubset(JSContext *cx, TypeObject *obj, TypeSet *target)
{
add(cx, cx->new_<TypeConstraintBaseSubset>(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_<TypeConstraintBaseClearDefinite>(object));
+}
+
/* Condensed constraint marking a script dependent on this type set. */
class TypeConstraintCondensed : public TypeConstraint
{
public:
TypeConstraintCondensed(JSScript *script)
: TypeConstraint("condensed", script)
{}
@@ -788,16 +814,28 @@ TypeConstraintSubset::newType(JSContext
}
void
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)
{
if (TypeIsObject(type))
return (TypeObject*) type;
/*
@@ -1747,16 +1785,29 @@ TypeCompartment::newInitializerTypeObjec
return NULL;
if (isArray)
res->initializerArray = true;
else
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;
}
static inline jsid
GetAtomId(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset)
{
unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, offset);
return MakeTypeId(cx, ATOM_TO_JSID(script->getAtom(index)));
@@ -2301,17 +2352,21 @@ TypeCompartment::fixArrayType(JSContext
ArrayTableKey key;
key.type = type;
key.proto = obj->getProto();
ArrayTypeTable::AddPtr p = arrayTypeTable->lookupForAdd(key);
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;
}
obj->setType(objType);
if (!cx->addTypePropertyId(objType, JSID_VOID, type))
return false;
@@ -2330,29 +2385,33 @@ TypeCompartment::fixArrayType(JSContext
* fixed) as the key in the object table, but since all references in the table
* are weak the hash entries would usually be collected on GC even if objects
* with the new type/shape are still live.
*/
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])
return false;
shape = shape->previous();
}
return true;
}
@@ -2426,17 +2485,21 @@ TypeCompartment::fixObjectType(JSContext
JSObject *xobj = NewBuiltinClassInstance(cx, &js_ObjectClass,
(gc::FinalizeKind) obj->finalizeKind());
if (!xobj) {
js_ReportOutOfMemory(cx);
return false;
}
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;
}
xobj->setType(objType);
jsid *ids = (jsid *) cx->calloc_(obj->slotSpan() * sizeof(jsid));
if (!ids)
@@ -2460,19 +2523,23 @@ TypeCompartment::fixObjectType(JSContext
if (!js_DefineNativeProperty(cx, xobj, ids[i], UndefinedValue(), NULL, NULL,
JSPROP_ENUMERATE, 0, 0, NULL, 0)) {
return false;
}
}
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));
ObjectTableEntry entry;
entry.object = objType;
entry.newShape = (Shape *) newShape;
entry.types = types;
@@ -2578,16 +2645,67 @@ TypeObject::addProperty(JSContext *cx, j
/* Pull in this property from all prototypes up the chain. */
getFromPrototypes(cx, base);
}
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)
{
JS_ASSERT(cx->compartment->types.inferenceDepth);
JS_ASSERT((this->flags & flags) != flags);
this->flags |= flags;
@@ -2630,16 +2748,44 @@ TypeObject::markUnknown(JSContext *cx)
if (prop) {
prop->types.addType(cx, TYPE_UNKNOWN);
prop->types.setOwnProperty(cx, true);
}
}
}
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)
{
printf("%s : %s", name(), proto ? proto->getType()->name() : "(null)");
if (unknownProperties()) {
printf(" unknown");
} else {
if (!hasFlags(OBJECT_FLAG_NON_PACKED_ARRAY))
@@ -3769,16 +3915,182 @@ AnalyzeScriptNew(JSContext *cx, JSScript
TypeFunction *funType = script->fun->getType()->asFunction();
TypeSet *prototypeTypes = funType->getProperty(cx, id_prototype(cx), false);
if (!prototypeTypes)
return;
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
/////////////////////////////////////////////////////////////////////
#ifdef DEBUG
void
PrintBytecode(JSContext *cx, JSScript *script, jsbytecode *pc)
@@ -4189,36 +4501,69 @@ JSScript::typeCheckBytecode(JSContext *c
#endif
/////////////////////////////////////////////////////////////////////
// JSObject
/////////////////////////////////////////////////////////////////////
void
-JSObject::makeNewType(JSContext *cx)
+JSObject::makeNewType(JSContext *cx, JSScript *newScript)
{
JS_ASSERT(!newType);
js::types::TypeObject *type = cx->newTypeObject(getType()->name(), "new", this);
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();
}
/////////////////////////////////////////////////////////////////////
// Tracing
/////////////////////////////////////////////////////////////////////
@@ -4256,16 +4601,21 @@ types::TypeObject::trace(JSTracer *trc)
}
}
if (proto)
gc::MarkObject(trc, *proto, "type_proto");
if (singleton)
gc::MarkObject(trc, *singleton, "type_singleton");
+
+ if (newScript) {
+ js_TraceScript(trc, newScript);
+ gc::MarkShape(trc, newScriptShape, "new_shape");
+ }
}
/*
* Condense any constraints on a type set which were generated during analysis
* of a script, and sweep all type objects and references to type objects
* which no longer exist.
*/
void
@@ -4338,17 +4688,17 @@ TypeSet::CondenseSweepTypeSet(JSContext
* condensed constraints for, in the condensed table. We reuse the
* same table for each type set to avoid extra initialization cost,
* but the table is emptied after each set is processed.
*/
while (constraint) {
TypeConstraint *next = constraint->next;
- TypeObject *object = constraint->baseSubset();
+ TypeObject *object = constraint->persistentObject();
if (object) {
/*
* Constraint propagating data between objects. If the target
* is not being collected (these are weak references) then
* keep the constraint.
*/
if (object->marked) {
constraint->next = types->constraintList;
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -192,21 +192,22 @@ public:
* Whether this is an input type constraint condensed from the original
* constraints generated during analysis of the associated script.
* If this type set changes then the script will be reanalyzed/recompiled
* should the type set change at all in the future.
*/
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; }
};
/*
* Coarse kinds of a set of objects. These form the following lattice:
*
* NONE
* ____/ \_____
* / \
@@ -236,29 +237,43 @@ enum {
TYPE_FLAG_BOOLEAN = 1 << TYPE_BOOLEAN,
TYPE_FLAG_INT32 = 1 << TYPE_INT32,
TYPE_FLAG_DOUBLE = 1 << TYPE_DOUBLE,
TYPE_FLAG_STRING = 1 << TYPE_STRING,
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. */
typedef uint32 TypeFlags;
/* Information about the set of types associated with an lvalue. */
class TypeSet
{
@@ -275,32 +290,31 @@ class TypeSet
TypeConstraint *constraintList;
TypeSet()
: typeFlags(0), objectSet(NULL), objectCount(0), constraintList(NULL)
{}
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. */
inline bool hasType(jstype type);
TypeFlags baseFlags() { return typeFlags & ~TYPE_FLAG_BASE_MASK; }
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.
*/
inline void addType(JSContext *cx, jstype type);
/* Add all types in a cloned set to this set. */
void addTypeSet(JSContext *cx, ClonedTypeSet *types);
@@ -311,16 +325,27 @@ class TypeSet
/*
* Iterate through the objects in this set. getObjectCount overapproximates
* in the hash case (see SET_ARRAY_SIZE in jsinferinlines.h), and getObject
* may return NULL.
*/
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);
void addGetProperty(JSContext *cx, JSScript *script, const jsbytecode *pc,
TypeSet *target, jsid id);
void addSetProperty(JSContext *cx, JSScript *script, const jsbytecode *pc,
TypeSet *target, jsid id);
void addNewObject(JSContext *cx, JSScript *script, TypeFunction *fun, TypeSet *target);
@@ -328,16 +353,17 @@ class TypeSet
void addArith(JSContext *cx, JSScript *script,
TypeSet *target, TypeSet *other = NULL);
void addTransformThis(JSContext *cx, JSScript *script, TypeSet *target);
void addFilterPrimitives(JSContext *cx, JSScript *script,
TypeSet *target, bool onlyNullVoid);
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);
/*
* Make an intermediate type set with the specified debugging name,
* not embedded in another structure.
*/
static inline TypeSet* make(JSContext *cx, const char *name);
@@ -473,16 +499,25 @@ struct TypeObject
/* Whether this is a function object, and may be cast into TypeFunction. */
bool isFunction;
/* 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.
*/
bool initializerObject;
bool initializerArray;
uint32 initializerOffset;
/*
@@ -559,19 +594,21 @@ struct TypeObject
void splicePrototype(JSContext *cx, JSObject *proto);
inline unsigned getPropertyCount();
inline Property *getProperty(unsigned i);
/* 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);
void print(JSContext *cx);
void trace(JSTracer *trc);
};
/*
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -1089,17 +1089,17 @@ struct TypeObjectKey {
inline void
TypeSet::destroy(JSContext *cx)
{
JS_ASSERT(!(typeFlags & TYPE_FLAG_INTERMEDIATE_SET));
if (objectCount >= 2)
::js_free(objectSet);
while (constraintList) {
TypeConstraint *next = constraintList->next;
- if (constraintList->condensed() || constraintList->baseSubset())
+ if (constraintList->condensed() || constraintList->persistentObject())
::js_free(constraintList);
constraintList = next;
}
}
inline bool
TypeSet::hasType(jstype type)
{
@@ -1164,17 +1164,17 @@ TypeSet::addType(JSContext *cx, jstype t
}
}
InferSpew(ISpewOps, "addType: T%p %s", this, TypeString(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;
}
cx->compartment->types.resolvePending(cx);
}
@@ -1186,17 +1186,17 @@ TypeSet::setOwnProperty(JSContext *cx, b
if ((typeFlags & nflags) == nflags)
return;
typeFlags |= nflags;
/* 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;
}
}
inline unsigned
TypeSet::getObjectCount()
@@ -1363,16 +1363,17 @@ TypeObject::name()
#else
return NULL;
#endif
}
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),
singleton(NULL)
{
#ifdef DEBUG
this->name_ = name;
#endif
@@ -1406,11 +1407,28 @@ SweepClonedTypes(ClonedTypeSet *types)
TypeObject *obj = (TypeObject *) types->objectSet;
if (!obj->marked) {
types->objectSet = NULL;
types->objectCount = 0;
}
}
}
+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___
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2887,16 +2887,51 @@ js_Object(JSContext *cx, uintN argc, Val
TypeObject *type = cx->getTypeCallerInitObject(false);
if (!type || !obj->setTypeAndEmptyShape(cx, type))
return JS_FALSE;
}
vp->setObject(*obj);
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)
{
Class *clasp = callee->getClass();
Class *newclasp = &js_ObjectClass;
if (clasp == &js_FunctionClass) {
JSFunction *fun = callee->getFunctionPrivate();
@@ -2915,48 +2950,72 @@ js_CreateThis(JSContext *cx, JSObject *c
if (obj)
obj->syncSpecialEquality();
return obj;
}
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<WithProto::Class>(cx, &js_ObjectClass, proto, callee->getParent(), kind);
}
JSObject *
js_CreateThisForFunction(JSContext *cx, JSObject *callee, bool newType)
{
Value protov;
if (!callee->getProperty(cx,
ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom),
&protov)) {
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;
}
#ifdef JS_TRACER
static JS_ALWAYS_INLINE JSObject*
NewObjectWithClassProto(JSContext *cx, Class *clasp, JSObject *proto,
/*gc::FinalizeKind*/ unsigned _kind)
@@ -3033,19 +3092,16 @@ js_CreateThisFromTrace(JSContext *cx, JS
JS_ASSERT(!shape->isMethod());
#endif
JSObject *parent = ctor->getParent();
JSObject *proto;
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
* primitive. Use Object.prototype for proto, per ES5 13.2.2 step 7.
*/
if (!js_GetClassPrototype(cx, parent, JSProto_Object, &proto))
return NULL;
}
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -649,19 +649,20 @@ struct JSObject : js::gc::Cell {
inline js::Value *getRawSlot(size_t slot, js::Value *slots);
/* Whether a slot is at a fixed offset from this object. */
inline bool isFixedSlot(size_t slot);
/* 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:
/* Minimum size for dynamically allocated slots. */
static const uint32 SLOT_CAPACITY_MIN = 8;
bool allocSlots(JSContext *cx, size_t nslots);
bool growSlots(JSContext *cx, size_t nslots);
@@ -784,18 +785,18 @@ struct JSObject : js::gc::Cell {
js::types::TypeObject* getType() const { return type; }
inline bool clearType(JSContext *cx);
inline void setType(js::types::TypeObject *newType);
inline bool setTypeAndUniqueShape(JSContext *cx, js::types::TypeObject *newType);
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;
}
JSObject *getParent() const {
return parent;
}
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -853,22 +853,26 @@ JSObject::getWithThis() const
inline void
JSObject::setWithThis(JSObject *thisp)
{
getFixedSlotRef(JSSLOT_WITH_THIS).setObject(*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;
}
inline bool
JSObject::clearType(JSContext *cx)
{
js::types::TypeObject *newType = cx->getTypeEmpty();
if (!newType)
@@ -1366,16 +1370,49 @@ template <WithProto::e withProto>
static JS_ALWAYS_INLINE JSObject *
NewObject(JSContext *cx, js::Class *clasp, JSObject *proto, JSObject *parent)
{
gc::FinalizeKind kind = gc::GetGCObjectKind(JSCLASS_RESERVED_SLOTS(clasp));
return NewObject<withProto>(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
* objects that do not require any fixed slots.
*/
static inline gc::FinalizeKind
GuessObjectGCKind(size_t numSlots, bool isArray)
{
if (numSlots)
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1917,34 +1917,40 @@ mjit::Compiler::generateMethod()
BEGIN_CASE(JSOP_ELEMDEC)
jsop_eleminc(op, STRICT_VARIANT(stubs::ElemDec));
END_CASE(JSOP_ELEMDEC)
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);
BEGIN_CASE(JSOP_GETARGPROP)
{
/* 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;
}
END_CASE(JSOP_GETARGPROP)
BEGIN_CASE(JSOP_GETLOCALPROP)
{
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;
}
END_CASE(JSOP_GETLOCALPROP)
BEGIN_CASE(JSOP_GETPROP)
if (!jsop_getprop(script->getAtom(fullAtomIndex(PC)), knownPushedType(0)))
return Compile_Error;
@@ -2375,24 +2381,32 @@ mjit::Compiler::generateMethod()
iterNext();
END_CASE(JSOP_FORELEM)
BEGIN_CASE(JSOP_BINDNAME)
jsop_bindname(script->getAtom(fullAtomIndex(PC)), true);
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)
prepareStubCall(Uses(1));
INLINE_STUBCALL_NO_REJOIN(stubs::Throw);
frame.pop();
END_CASE(JSOP_THROW)
@@ -4358,16 +4372,49 @@ mjit::Compiler::jsop_getprop(JSAtom *ato
if (top->isNotType(JSVAL_TYPE_OBJECT)) {
jsop_getprop_slow(atom, usePropCache);
return true;
}
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
* the string path wouldn't sink back.
*/
RegisterID objReg = Registers::ReturnReg;
RegisterID shapeReg = Registers::ReturnReg;
if (atom == cx->runtime->atomState.lengthAtom) {
objReg = frame.copyDataIntoReg(top);
@@ -4862,32 +4909,64 @@ mjit::Compiler::jsop_callprop(JSAtom *at
}
if (top->isTypeKnown())
return jsop_callprop_obj(atom);
return jsop_callprop_generic(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)
: STRICT_VARIANT(stubs::SetPropNoCache),
ic::SetProp);
FrameEntry *lhs = frame.peek(-2);
FrameEntry *rhs = frame.peek(-1);
/* If the incoming type will never PIC, take slow path. */
if (lhs->isTypeKnown() && lhs->getKnownType() != JSVAL_TYPE_OBJECT) {
jsop_setprop_slow(atom, usePropCache);
return true;
}
+ /*
+ * Set the property directly if we are accessing a known object which
+ * always has the property in a particular inline slot.
+ */
+ 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)
? ic::PICInfo::SETMETHOD
: ic::PICInfo::SET;
PICGenInfo pic(kind, op, usePropCache);
pic.atom = atom;
@@ -5405,17 +5484,17 @@ mjit::Compiler::jsop_nameinc(JSOp op, Vo
// OBJ V 1
/* Use sub since it calls ValueToNumber instead of string concat. */
frame.syncAt(-3);
if (!jsop_binary(JSOP_SUB, stubs::Sub, JSVAL_TYPE_UNKNOWN, pushedTypeSet(0)))
return Compile_Retry;
// OBJ N+1
- if (!jsop_setprop(atom, false))
+ if (!jsop_setprop(atom, false, pop))
return Compile_Error;
// N+1
if (pop)
frame.pop();
} else {
/* The pre-value is observed, making this more tricky. */
@@ -5434,17 +5513,17 @@ mjit::Compiler::jsop_nameinc(JSOp op, Vo
frame.push(Int32Value(-amt));
// N OBJ N 1
frame.syncAt(-3);
if (!jsop_binary(JSOP_ADD, stubs::Add, JSVAL_TYPE_UNKNOWN, pushedTypeSet(0)))
return Compile_Retry;
// N OBJ N+1
- if (!jsop_setprop(atom, false))
+ if (!jsop_setprop(atom, false, true))
return Compile_Error;
// N N+1
frame.pop();
// N
}
if (pop)
@@ -5494,17 +5573,17 @@ mjit::Compiler::jsop_propinc(JSOp op, Vo
frame.syncAt(-4);
if (!jsop_binary(JSOP_SUB, stubs::Sub, JSVAL_TYPE_UNKNOWN, pushedTypeSet(0)))
return Compile_Retry;
// OBJ * V+1
frame.shimmy(1);
// OBJ V+1
- if (!jsop_setprop(atom, false))
+ if (!jsop_setprop(atom, false, pop))
return Compile_Error;
// V+1
if (pop)
frame.pop();
} else {
/* The pre-value is observed, making this more tricky. */
@@ -5530,17 +5609,17 @@ mjit::Compiler::jsop_propinc(JSOp op, Vo
// OBJ N N+1
frame.dupAt(-3);
// OBJ N N+1 OBJ
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
frame.popn(2);
// OBJ N
frame.shimmy(1);
// N
@@ -6878,17 +6957,17 @@ mjit::Compiler::jsop_forprop(JSAtom *ato
iterNext();
// Before: ITER OBJ ITER VALUE
// After: ITER OBJ VALUE
frame.shimmy(1);
// Before: ITER OBJ VALUE
// After: ITER VALUE
- jsop_setprop(atom, false);
+ jsop_setprop(atom, false, true);
// Before: ITER VALUE
// After: ITER
frame.pop();
}
void
mjit::Compiler::jsop_forname(JSAtom *atom)
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -685,17 +685,17 @@ class Compiler : public BaseCompiler
void jsop_bindgname();
void jsop_setelem_slow();
void jsop_getelem_slow();
void jsop_callelem_slow();
void jsop_unbrand();
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);
bool jsop_callprop_obj(JSAtom *atom);
bool jsop_callprop_str(JSAtom *atom);
bool jsop_callprop_generic(JSAtom *atom);
bool jsop_instanceof();
void jsop_name(JSAtom *atom, JSValueType type);
--- a/js/src/methodjit/FrameState-inl.h
+++ b/js/src/methodjit/FrameState-inl.h
@@ -315,28 +315,33 @@ FrameState::push(Address address, JSValu
RegisterID dataReg = reuseBase ? address.base : allocReg();
masm.loadPayload(address, dataReg);
pushTypedPayload(knownType, dataReg);
return;
}
// 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();
masm.loadTypeTag(address, typeReg);
// Allow re-use of the base register. This could avoid a spill, and
// is safe because the following allocReg() won't actually emit any
// 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);
#endif
pushRegs(typeReg, dataReg, knownType);
}