[INFER] Compute types for singleton/JSON arrays and objects, bug 639263.

This commit is contained in:
Brian Hackett 2011-03-10 16:17:39 -08:00
parent af409eac97
commit 9bdadcc044
10 changed files with 405 additions and 14 deletions

View File

@ -0,0 +1,12 @@
var a = [1,2,3,4];
var b = [{a:0,b:1},{a:0,b:1},{a:0,b:1}];
var c = {a:0,b:4.5};
var d = [1,2,3,true];
var e = {a:0,b:1,c:2};
var f = {a:0,b:1,c:true};
var w = JSON.parse('[1,2,3,4]');
var x = JSON.parse('{"a":0,"b":true,"c":4.5}');
var y = JSON.parse('{"d":true,"b":true,"c":4.5}');
var z = JSON.parse('[{"a":0,"b":1},{"a":0,"b":1},{"a":0,"b":1}]');

View File

@ -1062,7 +1062,7 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode)
JSCompartment **compartment = rt->compartments.begin();
JSCompartment **end = rt->compartments.end();
while (compartment < end) {
(*compartment)->types.finish(cx, *compartment);
(*compartment)->types.print(cx, *compartment);
compartment++;
}

View File

@ -2185,6 +2185,14 @@ public:
/* Monitor all properties of a type object as unknown. */
inline bool markTypeObjectUnknownProperties(js::types::TypeObject *obj);
/*
* For an array or object which has not yet escaped and been referenced elsewhere,
* pick a new type based on the object's current contents.
*/
inline bool fixArrayType(JSObject *obj);
inline bool fixObjectType(JSObject *obj);
}; /* struct JSContext */
#ifdef JS_THREADSAFE

View File

@ -4453,6 +4453,8 @@ JSParseNode::getConstantValue(JSContext *cx, bool strictChecks, Value *vp)
}
JS_ASSERT(idx == pn_count);
if (!cx->fixArrayType(obj))
return false;
vp->setObject(*obj);
return true;
}
@ -4492,6 +4494,8 @@ JSParseNode::getConstantValue(JSContext *cx, bool strictChecks, Value *vp)
}
}
if (!cx->fixObjectType(obj))
return false;
vp->setObject(*obj);
return true;
}
@ -6840,7 +6844,8 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
}
#endif /* JS_HAS_GENERATORS */
if (!cg->hasSharps() && !(pn->pn_xflags & PNX_NONCONST) && cg->checkSingletonContext()) {
if (!cg->hasSharps() && !(pn->pn_xflags & PNX_NONCONST) && pn->pn_head &&
cg->checkSingletonContext()) {
if (!EmitSingletonInitialiser(cx, cg, pn))
return JS_FALSE;
break;
@ -6900,7 +6905,8 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
}
#endif
if (!cg->hasSharps() && !(pn->pn_xflags & PNX_NONCONST) && cg->checkSingletonContext()) {
if (!cg->hasSharps() && !(pn->pn_xflags & PNX_NONCONST) && pn->pn_head &&
cg->checkSingletonContext()) {
if (!EmitSingletonInitialiser(cx, cg, pn))
return JS_FALSE;
break;

View File

@ -284,7 +284,7 @@ void TypeFailure(JSContext *cx, const char *fmt, ...)
fprintf(stderr, "\n");
va_end(ap);
cx->compartment->types.finish(cx, cx->compartment);
cx->compartment->types.print(cx, cx->compartment);
fflush(stderr);
*((int*)NULL) = 0; /* Type warnings */
@ -1559,6 +1559,18 @@ TypeCompartment::init(JSContext *cx)
JS_InitArenaPool(&pool, "typeinfer", 512, 8, NULL);
}
TypeCompartment::~TypeCompartment()
{
if (pendingArray)
js_free(pendingArray);
if (arrayTypeTable)
js_delete<ArrayTypeTable>(arrayTypeTable);
if (objectTypeTable)
js_delete<ObjectTypeTable>(objectTypeTable);
}
TypeObject *
TypeCompartment::newTypeObject(JSContext *cx, JSScript *script, const char *name,
bool isFunction, JSObject *proto)
@ -1660,14 +1672,14 @@ void
TypeCompartment::growPendingArray(JSContext *cx)
{
unsigned newCapacity = js::Max(unsigned(100), pendingCapacity * 2);
PendingWork *newArray = (PendingWork *) cx->calloc(newCapacity * sizeof(PendingWork));
PendingWork *newArray = (PendingWork *) js_calloc(newCapacity * sizeof(PendingWork));
if (!newArray) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
memcpy(newArray, pendingArray, pendingCount * sizeof(PendingWork));
cx->free(pendingArray);
js_free(pendingArray);
pendingArray = newArray;
pendingCapacity = newCapacity;
@ -2010,13 +2022,10 @@ TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32 offset)
}
void
TypeCompartment::finish(JSContext *cx, JSCompartment *compartment)
TypeCompartment::print(JSContext *cx, JSCompartment *compartment)
{
JS_ASSERT(this == &compartment->types);
if (pendingArray)
cx->free(pendingArray);
if (!InferSpewActive(ISpewResult) || JS_CLIST_IS_EMPTY(&compartment->scripts))
return;
@ -2024,7 +2033,7 @@ TypeCompartment::finish(JSContext *cx, JSCompartment *compartment)
&script->links != &compartment->scripts;
script = (JSScript *)script->links.next) {
if (script->types)
script->types->finish(cx, script);
script->types->print(cx, script);
TypeObject *object = script->typeObjects;
while (object) {
object->print(cx);
@ -2054,6 +2063,270 @@ TypeCompartment::finish(JSContext *cx, JSCompartment *compartment)
printf("Time: %.2f ms\n", millis);
}
/////////////////////////////////////////////////////////////////////
// TypeCompartment tables
/////////////////////////////////////////////////////////////////////
/*
* The arrayTypeTable and objectTypeTable are per-compartment tables for making
* common type objects to model the contents of large script singletons and
* JSON objects. These are vanilla Arrays and native Objects, so we distinguish
* the types of different ones by looking at the types of their properties.
*
* All singleton/JSON arrays which have the same prototype, are homogenous and
* of the same type will share a type object. All singleton/JSON objects which
* have the same shape and property types will also share a type object. We
* don't try to collate arrays or objects that have type mismatches.
*/
static inline bool
NumberTypes(jstype a, jstype b)
{
return (a == TYPE_INT32 || a == TYPE_DOUBLE) && (b == TYPE_INT32 || b == TYPE_DOUBLE);
}
struct ArrayTableKey
{
jstype type;
JSObject *proto;
typedef ArrayTableKey Lookup;
static inline uint32 hash(const ArrayTableKey &v) {
return (uint32) (v.type ^ ((uint32)v.proto >> 2));
}
static inline bool match(const ArrayTableKey &v1, const ArrayTableKey &v2) {
return v1.type == v2.type && v1.proto == v2.proto;
}
};
bool
TypeCompartment::fixArrayType(JSContext *cx, JSObject *obj)
{
if (!arrayTypeTable) {
arrayTypeTable = js_new<ArrayTypeTable>(cx);
if (!arrayTypeTable || !arrayTypeTable->init()) {
arrayTypeTable = NULL;
js_ReportOutOfMemory(cx);
return false;
}
}
/*
* If the array is of homogenous type, pick a type object which will be
* shared with all other singleton/JSON arrays of the same type.
* If the array is heterogenous, keep the existing type object, which has
* unknown properties.
*/
JS_ASSERT(obj->isPackedDenseArray());
unsigned len = obj->getDenseArrayInitializedLength();
if (len == 0)
return true;
jstype type = GetValueType(cx, obj->getDenseArrayElement(0));
for (unsigned i = 1; i < len; i++) {
jstype ntype = GetValueType(cx, obj->getDenseArrayElement(i));
if (ntype != type) {
if (NumberTypes(type, ntype))
type = TYPE_DOUBLE;
else
return true;
}
}
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, obj->getProto());
if (!objType) {
js_ReportOutOfMemory(cx);
return false;
}
obj->setType(objType);
if (!cx->addTypePropertyId(objType, JSID_VOID, type))
return false;
if (!arrayTypeTable->relookupOrAdd(p, key, objType)) {
js_ReportOutOfMemory(cx);
return false;
}
}
return true;
}
/*
* N.B. We could also use the initial shape of the object (before its type is
* 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;
JSObject *proto;
typedef JSObject * Lookup;
static inline uint32 hash(JSObject *obj) {
return (uint32) (JSID_BITS(obj->lastProperty()->id) ^
obj->slotSpan() ^
((uint32)obj->getProto() >> 2));
}
static inline bool match(const ObjectTableKey &v, JSObject *obj) {
if (obj->slotSpan() != v.nslots || 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;
}
};
struct ObjectTableEntry
{
TypeObject *object;
Shape *newShape;
jstype *types;
};
bool
TypeCompartment::fixObjectType(JSContext *cx, JSObject *obj)
{
if (!objectTypeTable) {
objectTypeTable = js_new<ObjectTypeTable>(cx);
if (!objectTypeTable || !objectTypeTable->init()) {
objectTypeTable = NULL;
js_ReportOutOfMemory(cx);
return false;
}
}
/*
* Use the same type object for all singleton/JSON arrays with the same
* base shape, i.e. the same fields written in the same order. If there
* is a type mismatch with previous objects of the same shape, use the
* generic unknown type.
*/
JS_ASSERT(obj->isObject());
if (obj->slotSpan() == 0 || obj->inDictionaryMode())
return true;
ObjectTypeTable::AddPtr p = objectTypeTable->lookupForAdd(obj);
const Shape *baseShape = obj->lastProperty();
if (p) {
/* The lookup ensures the shape matches, now check that the types match. */
jstype *types = p->value.types;
for (unsigned i = 0; i < obj->slotSpan(); i++) {
jstype ntype = GetValueType(cx, obj->getSlot(i));
if (ntype != types[i]) {
if (NumberTypes(ntype, types[i])) {
if (types[i] == TYPE_INT32) {
types[i] = TYPE_DOUBLE;
const Shape *shape = baseShape;
while (!JSID_IS_EMPTY(shape->id)) {
if (shape->slot == i) {
if (!cx->addTypePropertyId(p->value.object, shape->id, TYPE_DOUBLE))
return false;
break;
}
shape = shape->previous();
}
}
} else {
return true;
}
}
}
obj->setTypeAndShape(p->value.object, p->value.newShape);
} else {
/*
* Make a new type to use, and regenerate a new shape to go with it.
* Shapes are rooted at the empty shape for the object's type, so we
* can't change the type without changing the shape.
*/
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, obj->getProto());
if (!objType) {
js_ReportOutOfMemory(cx);
return false;
}
xobj->setType(objType);
jsid *ids = (jsid *) cx->calloc(obj->slotSpan() * sizeof(jsid));
if (!ids)
return false;
jstype *types = (jstype *) cx->calloc(obj->slotSpan() * sizeof(jstype));
if (!types)
return false;
const Shape *shape = baseShape;
while (!JSID_IS_EMPTY(shape->id)) {
ids[shape->slot] = shape->id;
types[shape->slot] = GetValueType(cx, obj->getSlot(shape->slot));
if (!cx->addTypePropertyId(objType, shape->id, types[shape->slot]))
return false;
shape = shape->previous();
}
/* Construct the new shape. */
for (unsigned i = 0; i < obj->slotSpan(); i++) {
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();
ObjectTableKey key;
key.ids = ids;
key.nslots = obj->slotSpan();
key.proto = obj->getProto();
JS_ASSERT(ObjectTableKey::match(key, obj));
ObjectTableEntry entry;
entry.object = objType;
entry.newShape = (Shape *) newShape;
entry.types = types;
p = objectTypeTable->lookupForAdd(obj);
if (!objectTypeTable->add(p, key, entry)) {
js_ReportOutOfMemory(cx);
return false;
}
obj->setTypeAndShape(objType, newShape);
}
return true;
}
/////////////////////////////////////////////////////////////////////
// TypeObject
/////////////////////////////////////////////////////////////////////
@ -3450,7 +3723,7 @@ PrintBytecode(JSContext *cx, JSScript *script, jsbytecode *pc)
#endif
void
TypeScript::finish(JSContext *cx, JSScript *script)
TypeScript::print(JSContext *cx, JSScript *script)
{
TypeCompartment *compartment = &script->compartment->types;
@ -4076,6 +4349,52 @@ TypeCompartment::sweep(JSContext *cx)
typeEmpty.emptyShapes = NULL;
}
/*
* Iterate through the array/object type tables and remove all entries
* referencing collected data. These tables only hold weak references.
*/
if (arrayTypeTable) {
for (ArrayTypeTable::Enum e(*arrayTypeTable); !e.empty(); e.popFront()) {
const ArrayTableKey &key = e.front().key;
TypeObject *obj = e.front().value;
JS_ASSERT(obj->proto == key.proto);
bool remove = false;
if (TypeIsObject(key.type) && !((TypeObject *)key.type)->marked)
remove = true;
if (!obj->marked)
remove = true;
if (remove)
e.removeFront();
}
}
if (objectTypeTable) {
for (ObjectTypeTable::Enum e(*objectTypeTable); !e.empty(); e.popFront()) {
const ObjectTableKey &key = e.front().key;
const ObjectTableEntry &entry = e.front().value;
JS_ASSERT(entry.object->proto == key.proto);
bool remove = false;
if (!entry.object->marked || !entry.newShape->marked())
remove = true;
for (unsigned i = 0; !remove && i < key.nslots; i++) {
if (JSID_IS_STRING(key.ids[i]) && !JSID_TO_STRING(key.ids[i])->asCell()->isMarked())
remove = true;
if (TypeIsObject(entry.types[i]) && !((TypeObject *)entry.types[i])->marked)
remove = true;
}
if (remove) {
cx->free(key.ids);
cx->free(entry.types);
e.removeFront();
}
}
}
SweepTypeObjectList(cx, objects);
}

View File

@ -556,7 +556,7 @@ struct TypeScript
TypeSet **pushedArray;
/* Gather statistics off this script and print it if necessary. */
void finish(JSContext *cx, JSScript *script);
void print(JSContext *cx, JSScript *script);
inline bool monitored(uint32 offset);
inline void setMonitored(uint32 offset);
@ -573,6 +573,13 @@ void AnalyzeScriptTypes(JSContext *cx, JSScript *script);
/* Analyze the effect of invoking 'new' on script. */
void AnalyzeScriptNew(JSContext *cx, JSScript *script);
struct ArrayTableKey;
typedef HashMap<ArrayTableKey,TypeObject*,ArrayTableKey> ArrayTypeTable;
struct ObjectTableKey;
struct ObjectTableEntry;
typedef HashMap<ObjectTableKey,ObjectTableEntry,ObjectTableKey> ObjectTypeTable;
/* Type information for a compartment. */
struct TypeCompartment
{
@ -611,6 +618,14 @@ struct TypeCompartment
/* Pending recompilations to perform before execution of JIT code can resume. */
Vector<JSScript*> *pendingRecompiles;
/* Tables for determining types of singleton/JSON objects. */
ArrayTypeTable *arrayTypeTable;
ObjectTypeTable *objectTypeTable;
bool fixArrayType(JSContext *cx, JSObject *obj);
bool fixObjectType(JSContext *cx, JSObject *obj);
/* Constraint solving worklist structures. */
/* A type that needs to be registered with a constraint. */
@ -649,6 +664,7 @@ struct TypeCompartment
unsigned recompilations;
void init(JSContext *cx);
~TypeCompartment();
uint64 currentTime()
{
@ -670,7 +686,7 @@ struct TypeCompartment
inline void resolvePending(JSContext *cx);
/* Prints results of this compartment if spew is enabled, checks for warnings. */
void finish(JSContext *cx, JSCompartment *compartment);
void print(JSContext *cx, JSCompartment *compartment);
/* Make a function or non-function object associated with an optional script. */
TypeObject *newTypeObject(JSContext *cx, JSScript *script,

View File

@ -369,6 +369,18 @@ JSContext::typeMonitorCall(const js::CallArgs &args, bool constructing)
return compartment->types.dynamicCall(this, callee, args, constructing);
}
inline bool
JSContext::fixArrayType(JSObject *obj)
{
return !typeInferenceEnabled() || compartment->types.fixArrayType(this, obj);
}
inline bool
JSContext::fixObjectType(JSObject *obj)
{
return !typeInferenceEnabled() || compartment->types.fixObjectType(this, obj);
}
/////////////////////////////////////////////////////////////////////
// JSScript
/////////////////////////////////////////////////////////////////////

View File

@ -714,6 +714,7 @@ struct JSObject : js::gc::Cell {
inline bool clearType(JSContext *cx);
inline void setType(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);

View File

@ -760,6 +760,14 @@ JSObject::setType(js::types::TypeObject *newType)
type = newType;
}
inline void
JSObject::setTypeAndShape(js::types::TypeObject *newType, const js::Shape *newShape)
{
JS_ASSERT(newShape->slot == lastProperty()->slot);
setType(newType);
setLastProperty(newShape);
}
inline void
JSObject::init(JSContext *cx, js::Class *aclasp, js::types::TypeObject *type,
JSObject *parent, void *priv, bool useHoles)

View File

@ -900,6 +900,15 @@ CloseObject(JSContext *cx, JSONParser *jp)
jsuint len;
if (!js_GetLengthProperty(cx, jp->objectStack, &len))
return JS_FALSE;
Value p;
if (!jp->objectStack->getProperty(cx, INT_TO_JSID(len - 1), &p))
return JS_FALSE;
JSObject *obj = &p.toObject();
if (obj->isArray() ? !cx->fixArrayType(obj) : !cx->fixObjectType(obj))
return JS_FALSE;
if (!js_SetLengthProperty(cx, jp->objectStack, len - 1))
return JS_FALSE;