--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -60,23 +60,25 @@
#include "methodjit/MethodJIT.h"
#include "methodjit/Retcon.h"
#include "jsatominlines.h"
#include "jsgcinlines.h"
#include "jsinferinlines.h"
#include "jsobjinlines.h"
#include "jsscriptinlines.h"
+#include "vm/Stack-inl.h"
#ifdef JS_HAS_XML_SUPPORT
#include "jsxml.h"
#endif
using namespace js;
using namespace js::types;
+using namespace js::analyze;
static inline jsid
id_prototype(JSContext *cx) {
return ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
}
static inline jsid
id_arguments(JSContext *cx) {
@@ -386,41 +388,37 @@ TypeSet::print(JSContext *cx)
for (unsigned i = 0; i < count; i++) {
TypeObject *object = getObject(i);
if (object)
printf(" %s", object->name());
}
}
}
-class TypeConstraintInput : public TypeConstraint
-{
-public:
- TypeConstraintInput(JSScript *script)
- : TypeConstraint("input", script)
- {}
-
- bool input() { return true; }
-
- void newType(JSContext *cx, TypeSet *source, jstype type);
-};
+/////////////////////////////////////////////////////////////////////
+// TypeSet constraints
+/////////////////////////////////////////////////////////////////////
/* Standard subset constraint, propagate all types from one set to another. */
class TypeConstraintSubset : public TypeConstraint
{
public:
TypeSet *target;
TypeConstraintSubset(JSScript *script, TypeSet *target)
: TypeConstraint("subset", script), target(target)
{
JS_ASSERT(target);
}
- void newType(JSContext *cx, TypeSet *source, jstype type);
+ void newType(JSContext *cx, TypeSet *source, jstype type)
+ {
+ /* Basic subset constraint, move all types to the target. */
+ target->addType(cx, type);
+ }
};
void
TypeSet::addSubset(JSContext *cx, JSScript *script, TypeSet *target)
{
add(cx, ArenaNew<TypeConstraintSubset>(cx->compartment->pool, script, target));
}
@@ -431,49 +429,30 @@ public:
TypeObject *object;
TypeSet *target;
TypeConstraintBaseSubset(TypeObject *object, TypeSet *target)
: TypeConstraint("baseSubset", (JSScript *) 0x1),
object(object), target(target)
{}
- void newType(JSContext *cx, TypeSet *source, jstype type);
+ void newType(JSContext *cx, TypeSet *source, jstype type)
+ {
+ target->addType(cx, type);
+ }
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)
{}
@@ -658,17 +637,27 @@ public:
/* Primitive types other than null and undefined are passed through. */
bool onlyNullVoid;
TypeConstraintFilterPrimitive(JSScript *script, TypeSet *target, bool onlyNullVoid)
: TypeConstraint("filter", script), target(target), onlyNullVoid(onlyNullVoid)
{}
- void newType(JSContext *cx, TypeSet *source, jstype type);
+ void newType(JSContext *cx, TypeSet *source, jstype type)
+ {
+ if (onlyNullVoid) {
+ if (type == TYPE_NULL || type == TYPE_UNDEFINED)
+ return;
+ } else if (type != TYPE_UNKNOWN && TypeIsPrimitive(type)) {
+ return;
+ }
+
+ target->addType(cx, type);
+ }
};
void
TypeSet::addFilterPrimitives(JSContext *cx, JSScript *script, TypeSet *target, bool onlyNullVoid)
{
add(cx, ArenaNew<TypeConstraintFilterPrimitive>(cx->compartment->pool,
script, target, onlyNullVoid));
}
@@ -681,17 +670,21 @@ class TypeConstraintMonitorRead : public
{
public:
TypeSet *target;
TypeConstraintMonitorRead(JSScript *script, TypeSet *target)
: TypeConstraint("monitorRead", script), target(target)
{}
- void newType(JSContext *cx, TypeSet *source, jstype type);
+ void newType(JSContext *cx, TypeSet *source, jstype type)
+ {
+ /* :XXX: Not doing any handling for polymorphism yet. */
+ target->addType(cx, type);
+ }
};
void
TypeSet::addMonitorRead(JSContext *cx, JSScript *script, TypeSet *target)
{
add(cx, ArenaNew<TypeConstraintMonitorRead>(cx->compartment->pool, script, target));
}
@@ -703,48 +696,43 @@ class TypeConstraintGenerator : public T
{
public:
TypeSet *target;
TypeConstraintGenerator(JSScript *script, TypeSet *target)
: TypeConstraint("generator", script), target(target)
{}
- void newType(JSContext *cx, TypeSet *source, jstype type);
+ void newType(JSContext *cx, TypeSet *source, jstype type)
+ {
+ if (type == TYPE_UNKNOWN) {
+ target->addType(cx, TYPE_UNKNOWN);
+ return;
+ }
+
+ if (TypeIsPrimitive(type))
+ return;
+
+ /*
+ * Watch for 'for in' on Iterator and Generator objects, which can
+ * produce values other than strings.
+ */
+ TypeObject *object = (TypeObject *) type;
+ if (object->proto) {
+ Class *clasp = object->proto->getClass();
+ if (clasp == &js_IteratorClass || clasp == &js_GeneratorClass)
+ target->addType(cx, TYPE_UNKNOWN);
+ }
+ }
};
/////////////////////////////////////////////////////////////////////
// TypeConstraint
/////////////////////////////////////////////////////////////////////
-void
-TypeConstraintSubset::newType(JSContext *cx, TypeSet *source, jstype type)
-{
- /* Basic subset constraint, move all types to the target. */
- target->addType(cx, type);
-}
-
-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;
/*
@@ -958,17 +946,17 @@ TypeConstraintCall::newType(JSContext *c
return;
}
JSScript *callee = function->script;
unsigned nargs = callee->fun->nargs;
/* Analyze the function if we have not already done so. */
if (!callee->analyzed) {
- analyze::ScriptAnalysis *calleeAnalysis = callee->analysis(cx);
+ ScriptAnalysis *calleeAnalysis = callee->analysis(cx);
if (!calleeAnalysis) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
calleeAnalysis->analyzeTypes(cx);
}
/* Add bindings for the arguments of the call. */
@@ -1107,58 +1095,16 @@ TypeConstraintTransformThis::newType(JSC
if (!object) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
target->addType(cx, (jstype) object);
}
-void
-TypeConstraintFilterPrimitive::newType(JSContext *cx, TypeSet *source, jstype type)
-{
- if (onlyNullVoid) {
- if (type == TYPE_NULL || type == TYPE_UNDEFINED)
- return;
- } else if (type != TYPE_UNKNOWN && TypeIsPrimitive(type)) {
- return;
- }
-
- target->addType(cx, type);
-}
-
-void
-TypeConstraintMonitorRead::newType(JSContext *cx, TypeSet *source, jstype type)
-{
- target->addType(cx, type);
-}
-
-void
-TypeConstraintGenerator::newType(JSContext *cx, TypeSet *source, jstype type)
-{
- if (type == TYPE_UNKNOWN) {
- target->addType(cx, TYPE_UNKNOWN);
- return;
- }
-
- if (TypeIsPrimitive(type))
- return;
-
- /*
- * Watch for 'for in' on Iterator and Generator objects, which can produce
- * values other than strings.
- */
- TypeObject *object = (TypeObject *) type;
- if (object->proto) {
- Class *clasp = object->proto->getClass();
- if (clasp == &js_IteratorClass || clasp == &js_GeneratorClass)
- target->addType(cx, TYPE_UNKNOWN);
- }
-}
-
/////////////////////////////////////////////////////////////////////
// Freeze constraints
/////////////////////////////////////////////////////////////////////
/* Constraint which marks all types as pushed by some bytecode. */
class TypeConstraintPushAll : public TypeConstraint
{
public:
@@ -1662,17 +1608,17 @@ TypeCompartment::newInitializerTypeObjec
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.
*/
JSObject *baseobj = script->getObject(GET_SLOTNO(script->code + offset));
- if (!res->addDefiniteProperties(cx, baseobj, false))
+ if (!res->addDefiniteProperties(cx, baseobj))
return NULL;
}
return res;
}
static inline jsid
GetAtomId(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset)
@@ -1776,16 +1722,55 @@ TypeCompartment::dynamicCall(JSContext *
for (; arg < args.argc() && arg < nargs; arg++)
script->typeSetArgument(cx, arg, args[arg]);
/* Watch for fewer actuals than formals to the call. */
for (; arg < nargs; arg++)
script->typeSetArgument(cx, arg, UndefinedValue());
}
+/* Intermediate type information for a dynamic type pushed in a script. */
+class TypeIntermediatePushed : public TypeIntermediate
+{
+ uint32 offset;
+ jstype type;
+
+ public:
+ TypeIntermediatePushed(uint32 offset, jstype type)
+ : offset(offset), type(type)
+ {}
+
+ void replay(JSContext *cx, JSScript *script)
+ {
+ TypeSet *pushed = script->analysis(cx)->pushedTypes(offset);
+ pushed->addType(cx, type);
+ }
+
+ bool hasDynamicResult(uint32 offset, jstype type) {
+ return this->offset == offset && this->type == type;
+ }
+
+ bool sweep(JSContext *cx, JSCompartment *compartment)
+ {
+ if (!TypeIsObject(type))
+ return true;
+
+ TypeObject *object = (TypeObject *) type;
+ if (object->marked)
+ return true;
+
+ if (object->unknownProperties()) {
+ type = (jstype) &compartment->types.typeEmpty;
+ return true;
+ }
+
+ return false;
+ }
+};
+
void
TypeCompartment::dynamicPush(JSContext *cx, JSScript *script, uint32 offset, jstype type)
{
JS_ASSERT(cx->typeInferenceEnabled());
AutoEnterTypeInference enter(cx);
/*
* For inc/dec ops, we need to go back and reanalyze the affected opcode
@@ -1824,77 +1809,73 @@ TypeCompartment::dynamicPush(JSContext *
case JSOP_ARGINC:
case JSOP_ARGDEC: {
/*
* Just mark the slot's type as holding the new type. This captures
* the effect if the slot is not being tracked, and if the slot
* doesn't escape we will update the pushed types below to capture
* the slot's value after this write.
*/
- uint32 slot = analyze::GetBytecodeSlot(script, pc);
- if (slot < analyze::TotalSlots(script)) {
+ uint32 slot = GetBytecodeSlot(script, pc);
+ if (slot < TotalSlots(script)) {
if (!script->ensureVarTypes(cx))
return;
TypeSet *types = script->slotTypes(slot);
types->addType(cx, type);
}
break;
}
default:;
}
}
if (script->hasAnalysis() && script->analysis(cx)->ranInference()) {
/*
* If the pushed set already has this type, we don't need to ensure
- * there is a TypeResult. Either there already is a TypeResult, or the
+ * there is a TypeIntermediate. Either there already is one, or the
* type could be determined from the script's other input type sets.
*/
TypeSet *pushed = script->analysis(cx)->pushedTypes(offset, 0);
if (pushed->hasType(type))
return;
} else {
- /* Scan all TypeResults on the script to check for a duplicate. */
- TypeResult *result, **presult = &script->typeResults;
+ /* Scan all intermediate types on the script to check for a dupe. */
+ TypeIntermediate *result, **presult = &script->intermediateTypes;
while (*presult) {
result = *presult;
- if (result->offset == offset && result->type == type) {
- if (presult != &script->typeResults) {
- /* Move this result to the head of the list, maintain LRU order. */
+ if (result->hasDynamicResult(offset, type)) {
+ if (presult != &script->intermediateTypes) {
+ /* Move to the head of the list, maintain LRU order. */
*presult = result->next;
- result->next = script->typeResults;
- script->typeResults = result;
+ result->next = script->intermediateTypes;
+ script->intermediateTypes = result;
}
return;
}
presult = &result->next;
}
}
InferSpew(ISpewOps, "externalType: monitorResult #%u:%05u: %s",
script->id(), offset, TypeString(type));
- TypeResult *result = (TypeResult *) cx->calloc_(sizeof(TypeResult));
+ TypeIntermediatePushed *result = cx->new_<TypeIntermediatePushed>(offset, type);
if (!result) {
setPendingNukeTypes(cx);
return;
}
-
- result->offset = offset;
- result->type = type;
- result->next = script->typeResults;
- script->typeResults = result;
+ script->addIntermediateType(result);
if (script->hasAnalysis() && script->analysis(cx)->ranInference()) {
TypeSet *pushed = script->analysis(cx)->pushedTypes(offset, 0);
pushed->addType(cx, type);
} else if (script->analyzed) {
/* Any new dynamic result triggers reanalysis and recompilation. */
- analyze::ScriptAnalysis *analysis = script->analysis(cx);
+ ScriptAnalysis *analysis = script->analysis(cx);
if (!analysis) {
setPendingNukeTypes(cx);
return;
}
analysis->analyzeTypes(cx);
}
/*
@@ -2390,17 +2371,17 @@ TypeCompartment::fixObjectType(JSContext
JSPROP_ENUMERATE, 0, 0, NULL, 0)) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
}
JS_ASSERT(!xobj->inDictionaryMode());
const Shape *newShape = xobj->lastProperty();
- if (!objType->addDefiniteProperties(cx, xobj, false))
+ if (!objType->addDefiniteProperties(cx, xobj))
return;
ObjectTableKey key;
key.ids = ids;
key.nslots = obj->slotSpan();
key.nfixed = obj->numFixedSlots();
key.proto = obj->getProto();
JS_ASSERT(ObjectTableKey::match(key, obj));
@@ -2528,17 +2509,17 @@ 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)
+TypeObject::addDefiniteProperties(JSContext *cx, JSObject *obj)
{
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.
@@ -2549,38 +2530,16 @@ TypeObject::addDefiniteProperties(JSCont
while (!JSID_IS_EMPTY(shape->propid)) {
jsid id = MakeTypeId(cx, shape->propid);
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 true;
}
void
@@ -2633,17 +2592,16 @@ 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
@@ -2653,16 +2611,93 @@ TypeObject::clearNewScript(JSContext *cx
*/
for (unsigned i = 0; i < getPropertyCount(); i++) {
Property *prop = getProperty(i);
if (!prop)
continue;
if (prop->types.isDefiniteProperty())
prop->types.setOwnProperty(cx, true);
}
+
+#ifdef JS_METHODJIT
+ mjit::ExpandInlineFrames(cx, true);
+#endif
+
+ /*
+ * If we cleared the new script while in the middle of initializing an
+ * object, it will still have the new script's shape and reflect the no
+ * longer correct state of the object once its initialization is completed.
+ * We can't really detect the possibility of this statically, but the new
+ * script keeps track of where each property is initialized so we can walk
+ * the stack and fix up any such objects.
+ */
+ for (AllFramesIter iter(cx); !iter.done(); ++iter) {
+ StackFrame *fp = iter.fp();
+ if (fp->isScriptFrame() && fp->isConstructing() &&
+ fp->script() == newScript->script && fp->thisValue().isObject() &&
+ fp->thisValue().toObject().type == this) {
+ JSObject *obj = &fp->thisValue().toObject();
+ JSInlinedSite *inline_;
+ jsbytecode *pc = fp->pc(cx, NULL, &inline_);
+ JS_ASSERT(!inline_);
+
+ /* Number of properties that have been added. */
+ uint32 numProperties = 0;
+
+ /*
+ * If non-zero, we are scanning initializers in a call which has
+ * already finished.
+ */
+ size_t depth = 0;
+
+ for (TypeNewScript::Initializer *init = newScript->initializerList;; init++) {
+ uint32 offset = uint32(pc - fp->script()->code);
+ if (init->kind == TypeNewScript::Initializer::SETPROP) {
+ if (!depth && init->offset > offset) {
+ /* Advanced past all properties which have been initialized. */
+ break;
+ }
+ numProperties++;
+ } else if (init->kind == TypeNewScript::Initializer::FRAME_PUSH) {
+ if (depth) {
+ depth++;
+ } else if (init->offset > offset) {
+ /* Advanced past all properties which have been initialized. */
+ break;
+ } else if (init->offset == offset) {
+ StackSegment &seg = cx->stack.space().containingSegment(fp);
+ if (seg.currentFrame() == fp)
+ break;
+ fp = seg.computeNextFrame(fp);
+ pc = fp->pc(cx, NULL, &inline_);
+ JS_ASSERT(!inline_);
+ } else {
+ /* This call has already finished. */
+ depth = 1;
+ }
+ } else if (init->kind == TypeNewScript::Initializer::FRAME_POP) {
+ if (depth) {
+ depth--;
+ } else {
+ /* This call has not finished yet. */
+ break;
+ }
+ } else {
+ JS_ASSERT(init->kind == init->kind != TypeNewScript::Initializer::DONE);
+ JS_ASSERT(numProperties == obj->slotSpan());
+ break;
+ }
+ }
+
+ obj->rollbackProperties(numProperties);
+ }
+ }
+
+ cx->free_(newScript);
+ newScript = NULL;
}
void
TypeObject::print(JSContext *cx)
{
printf("%s : %s", name(), proto ? proto->getType()->name() : "(null)");
if (unknownProperties()) {
@@ -2742,17 +2777,17 @@ BytecodeNoFallThrough(JSOp op)
/*
* If the bytecode immediately following code/pc is a test of the value
* pushed by code, that value should be marked as possibly void.
*/
static inline bool
CheckNextTest(jsbytecode *pc)
{
- jsbytecode *next = pc + analyze::GetBytecodeLength(pc);
+ jsbytecode *next = pc + GetBytecodeLength(pc);
switch ((JSOp)*next) {
case JSOP_IFEQ:
case JSOP_IFNE:
case JSOP_NOT:
case JSOP_OR:
case JSOP_ORX:
case JSOP_AND:
case JSOP_ANDX:
@@ -2773,17 +2808,17 @@ GetInitializerType(JSContext *cx, JSScri
JSOp op = JSOp(*pc);
JS_ASSERT(op == JSOP_NEWARRAY || op == JSOP_NEWOBJECT || op == JSOP_NEWINIT);
bool isArray = (op == JSOP_NEWARRAY || (op == JSOP_NEWINIT && pc[1] == JSProto_Array));
return script->getTypeInitObject(cx, pc, isArray);
}
inline void
-analyze::ScriptAnalysis::setForTypes(JSContext *cx, jsbytecode *pc, TypeSet *types)
+ScriptAnalysis::setForTypes(JSContext *cx, jsbytecode *pc, TypeSet *types)
{
/* Find the initial ITER opcode which constructed the active iterator. */
const SSAValue &iterv = poppedValue(pc, 0);
jsbytecode *iterpc = script->code + iterv.pushedOffset();
JS_ASSERT(JSOp(*iterpc) == JSOP_ITER);
uintN flags = iterpc[1];
if (flags & JSITER_FOREACH) {
@@ -2799,29 +2834,29 @@ analyze::ScriptAnalysis::setForTypes(JSC
types->addType(cx, TYPE_STRING);
pushedTypes(iterpc, 0)->add(cx,
ArenaNew<TypeConstraintGenerator>(cx->compartment->pool, script, types));
}
/* Analyze type information for a single bytecode. */
bool
-analyze::ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
+ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
TypeInferenceState &state)
{
jsbytecode *pc = script->code + offset;
JSOp op = (JSOp)*pc;
Bytecode &code = getCode(offset);
JS_ASSERT(!code.pushedTypes);
InferSpew(ISpewOps, "analyze: #%u:%05u", script->id(), offset);
- unsigned defCount = analyze::GetDefCount(script, offset);
- if (analyze::ExtendedDef(pc))
+ unsigned defCount = GetDefCount(script, offset);
+ if (ExtendedDef(pc))
defCount++;
TypeSet *pushed = ArenaArray<TypeSet>(cx->compartment->pool, defCount);
if (!pushed)
return false;
PodZero(pushed, defCount);
code.pushedTypes = pushed;
@@ -3301,17 +3336,17 @@ analyze::ScriptAnalysis::analyzeTypesByt
break;
case JSOP_CALL:
case JSOP_EVAL:
case JSOP_FUNCALL:
case JSOP_FUNAPPLY:
case JSOP_NEW: {
/* Construct the base call information about this site. */
- unsigned argCount = analyze::GetUseCount(script, offset) - 2;
+ unsigned argCount = GetUseCount(script, offset) - 2;
TypeCallsite *callsite = ArenaNew<TypeCallsite>(cx->compartment->pool,
cx, script, pc, op == JSOP_NEW, argCount);
if (!callsite || (argCount && !callsite->argumentTypes)) {
cx->compartment->types.setPendingNukeTypes(cx);
break;
}
callsite->thisTypes = poppedTypes(pc, argCount);
callsite->returnTypes = &pushed[0];
@@ -3577,17 +3612,17 @@ analyze::ScriptAnalysis::analyzeTypesByt
default:
TypeFailure(cx, "Unknown bytecode at #%u:%05u", script->id(), offset);
}
return true;
}
void
-analyze::ScriptAnalysis::analyzeTypes(JSContext *cx)
+ScriptAnalysis::analyzeTypes(JSContext *cx)
{
JS_ASSERT(!ranInference() && !failed());
/*
* Refuse to analyze the types in a script which is compileAndGo but is
* running against a global with a cleared scope. Per GlobalObject::clear,
* we won't be running anymore compileAndGo code against the global
* (moreover, after clearing our analysis results will be wrong for the
@@ -3632,52 +3667,51 @@ analyze::ScriptAnalysis::analyzeTypes(JS
/* Make sure the initial type set of all local vars includes void. */
for (unsigned i = 0; i < script->nfixed; i++)
script->localTypes(i)->addType(cx, TYPE_UNDEFINED);
TypeInferenceState state(cx);
unsigned offset = 0;
while (offset < script->length) {
- analyze::Bytecode *code = maybeCode(offset);
+ Bytecode *code = maybeCode(offset);
jsbytecode *pc = script->code + offset;
- analyze::UntrapOpcode untrap(cx, script, pc);
+ UntrapOpcode untrap(cx, script, pc);
if (code && !analyzeTypesBytecode(cx, offset, state)) {
cx->compartment->types.setPendingNukeTypes(cx);
return;
}
- offset += analyze::GetBytecodeLength(pc);
+ offset += GetBytecodeLength(pc);
}
for (unsigned i = 0; i < state.phiNodes.length(); i++) {
SSAPhiNode *node = state.phiNodes[i];
for (unsigned j = 0; j < node->length; j++) {
const SSAValue &v = node->options[j];
getValueTypes(v)->addSubset(cx, script, &node->types);
}
}
/*
- * Sync with any dynamic types previously generated either because
- * we ran the interpreter some before analyzing or because we
- * are reanalyzing after a GC.
+ * Replay any intermediate type information which has been generated for
+ * the script either because we ran the interpreter some before analyzing
+ * or because we are reanalyzing after a GC.
*/
- TypeResult *result = script->typeResults;
+ TypeIntermediate *result = script->intermediateTypes;
while (result) {
- TypeSet *pushed = pushedTypes(result->offset);
- pushed->addType(cx, result->type);
+ result->replay(cx, script);
result = result->next;
}
}
void
-analyze::ScriptAnalysis::analyzeTypesNew(JSContext *cx)
+ScriptAnalysis::analyzeTypesNew(JSContext *cx)
{
JS_ASSERT(script->calledWithNew && script->fun);
/*
* Compute the 'this' type when called with 'new'. We do not distinguish regular
* from 'new' calls to the function.
*/
@@ -3690,201 +3724,315 @@ analyze::ScriptAnalysis::analyzeTypesNew
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)
+/*
+ * Persistent constraint clearing out newScript and definite properties from
+ * an object should a property on another object get a setter.
+ */
+class TypeConstraintClearDefiniteSetter : public TypeConstraint
+{
+public:
+ TypeObject *object;
+
+ TypeConstraintClearDefiniteSetter(TypeObject *object)
+ : TypeConstraint("baseClearDefinite", (JSScript *) 0x1), object(object)
+ {}
+
+ void newType(JSContext *cx, TypeSet *source, jstype type) {
+ if (!object->newScript)
+ return;
+ /*
+ * Clear out the newScript shape and definite property information from
+ * an object if the source type set could be a setter (its type set
+ * becomes unknown).
+ */
+ if (type == TYPE_UNKNOWN)
+ object->clearNewScript(cx);
+ }
+
+ TypeObject * persistentObject() { return object; }
+};
+
+/*
+ * Constraint which clears definite properties on an object should a type set
+ * contain any types other than a single object.
+ */
+class TypeConstraintClearDefiniteSingle : public TypeConstraint
+{
+public:
+ TypeObject *object;
+
+ TypeConstraintClearDefiniteSingle(JSScript *script, TypeObject *object)
+ : TypeConstraint("baseClearDefinite", script), object(object)
+ {}
+
+ void newType(JSContext *cx, TypeSet *source, jstype type) {
+ if (object->newScript && !source->getSingleObject())
+ object->clearNewScript(cx);
+ }
+};
+
+/*
+ * Mark an intermediate type set such that changes will clear the definite
+ * properties on a type object.
+ */
+class TypeIntermediateClearDefinite : public TypeIntermediate
+{
+ uint32 offset;
+ uint32 which;
+ TypeObject *object;
+
+ public:
+ TypeIntermediateClearDefinite(uint32 offset, uint32 which, TypeObject *object)
+ : offset(offset), which(which), object(object)
+ {}
+
+ void replay(JSContext *cx, JSScript *script)
+ {
+ TypeSet *pushed = script->analysis(cx)->pushedTypes(offset, which);
+ pushed->add(cx, ArenaNew<TypeConstraintClearDefiniteSingle>(cx->compartment->pool, script, object));
+ }
+
+ bool sweep(JSContext *cx, JSCompartment *compartment)
+ {
+ return object->marked;
+ }
+};
+
+static bool
+AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, JSScript *script, JSObject **pbaseobj,
+ Vector<TypeNewScript::Initializer> *initializerList)
{
/*
* 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.
+ * chance to escape and be accessed elsewhere.
+ *
+ * Returns true if the entire script was analyzed (pbaseobj has been
+ * preserved), false if we had to bail out part way through (pbaseobj may
+ * have been cleared).
*/
- /* 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) {
+ ScriptAnalysis *analysis = script->analysis(cx);
+ if (analysis && !analysis->ranInference())
+ analysis->analyzeTypes(cx);
+ if (!analysis || analysis->OOM()) {
+ *pbaseobj = NULL;
+ cx->compartment->types.setPendingNukeTypes(cx);
+ return false;
+ }
+
+ /*
+ * Offset of the last bytecode which popped 'this' and which we have
+ * processed. For simplicity, we scan for places where 'this' is pushed
+ * and immediately analyze the place where that pushed value is popped.
+ * This runs the risk of doing things out of order, if the script looks
+ * something like 'this.f = (this.g = ...)', so we watch and bail out if
+ * a 'this' is pushed before the previous 'this' value was popped.
+ */
+ uint32 lastThisPopped = 0;
+
+ unsigned nextOffset = 0;
+ while (nextOffset < script->length) {
+ unsigned offset = nextOffset;
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);
+ nextOffset += GetBytecodeLength(pc);
+
+ /* 'this' can escape through a call to eval. */
+ if (op == JSOP_EVAL)
+ return false;
+
+ /*
+ * We are only interested in places where 'this' is popped. The new
+ * 'this' value cannot escape and be accessed except through such uses.
+ */
+ if (op != JSOP_THIS)
+ continue;
+
+ SSAValue thisv = SSAValue::PushedValue(offset, 0);
+ SSAUseChain *uses = analysis->useChain(thisv);
+
+ JS_ASSERT(uses);
+ if (uses->next || !uses->popped) {
+ /* 'this' value popped in more than one place. */
+ return false;
+ }
+
+ /* Only handle 'this' values popped in unconditional code. */
+
+ /* Maintain ordering property on how 'this' is used, as described above. */
+ if (offset < lastThisPopped) {
+ *pbaseobj = NULL;
+ return false;
}
-
- depth -= nuses;
- depth += ndefs;
-
- switch (JSOp(*pc)) {
-
- case JSOP_THIS:
- thisSlot = depth - 1;
- break;
-
- case JSOP_SETPROP: {
- if (!poppedThis)
- return baseobj;
+ lastThisPopped = uses->offset;
+
+ pc = script->code + uses->offset;
+ op = JSOp(*pc);
+
+ JSObject *obj = *pbaseobj;
+
+ if (op == JSOP_SETPROP && uses->u.which == 1) {
jsid id = GetAtomId(cx, script, pc, 0);
- if (JSID_IS_VOID(id))
- return baseobj;
+ if (MakeTypeId(cx, id) != id)
+ return false;
if (id == id_prototype(cx) || id == id___proto__(cx) || id == id_constructor(cx))
- return baseobj;
- if (!js_DefineNativeProperty(cx, baseobj, id, UndefinedValue(), NULL, NULL,
+ return false;
+
+ unsigned slotSpan = obj->slotSpan();
+ if (!js_DefineNativeProperty(cx, obj, id, UndefinedValue(), NULL, NULL,
JSPROP_ENUMERATE, 0, 0, NULL, 0)) {
- return NULL;
+ cx->compartment->types.setPendingNukeTypes(cx);
+ *pbaseobj = NULL;
+ return false;
+ }
+
+ if (obj->inDictionaryMode()) {
+ *pbaseobj = NULL;
+ return false;
}
- numProperties++;
- if (baseobj->slotSpan() != numProperties) {
+
+ if (obj->slotSpan() == slotSpan) {
/* Set a duplicate property. */
- return baseobj;
+ return false;
}
- if (baseobj->inDictionaryMode())
- return NULL;
- if (numProperties >= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) {
+
+ TypeNewScript::Initializer setprop(TypeNewScript::Initializer::SETPROP, uses->offset);
+ if (!initializerList->append(setprop)) {
+ cx->compartment->types.setPendingNukeTypes(cx);
+ *pbaseobj = NULL;
+ return false;
+ }
+
+ if (obj->slotSpan() >= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)) {
/* Maximum number of definite properties added. */
- return baseobj;
+ return false;
}
- 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_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:
- case JSOP_CALL:
- case JSOP_NEW:
- break;
-
- default:
- return baseobj;
+
+ /*
+ * Ensure that if the properties named here could have a setter in
+ * the direct prototype (and thus its transitive prototypes), the
+ * definite properties get cleared from the shape.
+ */
+ TypeSet *parentTypes = type->proto->getType()->getProperty(cx, id, false);
+ if (!parentTypes || parentTypes->unknown())
+ return false;
+ parentTypes->add(cx, cx->new_<TypeConstraintClearDefiniteSetter>(type));
+ } else if (op == JSOP_FUNCALL && uses->u.which == GET_ARGC(pc) - 1) {
+ /*
+ * Passed as the first parameter to Function.call. Follow control
+ * into the callee, and add any definite properties it assigns to
+ * the object as well. :TODO: This is narrow pattern matching on
+ * the inheritance patterns seen in the v8-deltablue benchmark, and
+ * needs robustness against other ways initialization can cross
+ * script boundaries.
+ *
+ * Add constraints ensuring we are calling Function.call on a
+ * particular script, removing definite properties from the result
+ */
+
+ /* Callee/this must have been pushed by a CALLPROP. */
+ SSAValue calleev = analysis->poppedValue(pc, GET_ARGC(pc) + 1);
+ if (calleev.kind() != SSAValue::PUSHED ||
+ JSOp(script->code[calleev.pushedOffset()]) != JSOP_CALLPROP ||
+ calleev.pushedIndex() != 0) {
+ return false;
+ }
+
+ TypeSet *funcallTypes = analysis->pushedTypes(calleev.pushedOffset(), 0);
+ TypeSet *scriptTypes = analysis->pushedTypes(calleev.pushedOffset(), 1);
+
+ /* Need to definitely be calling Function.call on a specific script. */
+ TypeObject *funcallObj = funcallTypes->getSingleObject();
+ if (!funcallObj || !funcallObj->singleton ||
+ !funcallObj->singleton->isFunction() ||
+ funcallObj->singleton->getFunctionPrivate()->maybeNative() != js_fun_call) {
+ return false;
+ }
+ TypeObject *scriptObj = scriptTypes->getSingleObject();
+ if (!scriptObj || !scriptObj->isFunction || !scriptObj->asFunction()->script)
+ return false;
+
+ /*
+ * Add constraints to clear definite properties from the type
+ * should the Function.call or callee itself change in the future.
+ */
+ TypeIntermediateClearDefinite *funcallTrap =
+ cx->new_<TypeIntermediateClearDefinite>(calleev.pushedOffset(), 0, type);
+ TypeIntermediateClearDefinite *calleeTrap =
+ cx->new_<TypeIntermediateClearDefinite>(calleev.pushedOffset(), 1, type);
+ if (!funcallTrap || !calleeTrap) {
+ cx->compartment->types.setPendingNukeTypes(cx);
+ *pbaseobj = NULL;
+ return false;
+ }
+ script->addIntermediateType(funcallTrap);
+ script->addIntermediateType(calleeTrap);
+ funcallTrap->replay(cx, script);
+ calleeTrap->replay(cx, script);
+
+ TypeNewScript::Initializer pushframe(TypeNewScript::Initializer::FRAME_PUSH, uses->offset);
+ if (!initializerList->append(pushframe)) {
+ cx->compartment->types.setPendingNukeTypes(cx);
+ *pbaseobj = NULL;
+ return false;
+ }
+
+ if (!AnalyzeNewScriptProperties(cx, type, scriptObj->asFunction()->script,
+ pbaseobj, initializerList)) {
+ return false;
+ }
+
+ TypeNewScript::Initializer popframe(TypeNewScript::Initializer::FRAME_POP, 0);
+ if (!initializerList->append(popframe)) {
+ cx->compartment->types.setPendingNukeTypes(cx);
+ *pbaseobj = NULL;
+ return false;
+ }
+
+ /*
+ * The callee never lets the 'this' value escape, continue looking
+ * for definite properties in the remainder of this script.
+ */
+ } else {
+ /* Unhandled use of 'this'. */
+ return false;
}
-
- offset += analyze::GetBytecodeLength(pc);
}
- /* We should have bailed out on a JSOP_STOP or similar. */
- JS_NOT_REACHED("Mystery!");
- return baseobj;
+ return true;
}
/////////////////////////////////////////////////////////////////////
// Printing
/////////////////////////////////////////////////////////////////////
void
-analyze::ScriptAnalysis::printTypes(JSContext *cx)
+ScriptAnalysis::printTypes(JSContext *cx)
{
AutoEnterAnalysis enter(cx);
TypeCompartment *compartment = &script->compartment->types;
/*
* Check if there are warnings for used values with unknown types, and build
* statistics about the size of type sets found for stack values.
*/
for (unsigned offset = 0; offset < script->length; offset++) {
if (!maybeCode(offset))
continue;
- unsigned defCount = analyze::GetDefCount(script, offset);
+ unsigned defCount = GetDefCount(script, offset);
if (!defCount)
continue;
for (unsigned i = 0; i < defCount; i++) {
TypeSet *types = pushedTypes(offset, i);
if (types->unknown()) {
compartment->typeCountOver++;
@@ -3950,17 +4098,17 @@ analyze::ScriptAnalysis::printTypes(JSCo
printf("\n");
for (unsigned offset = 0; offset < script->length; offset++) {
if (!maybeCode(offset))
continue;
PrintBytecode(cx, script, script->code + offset);
- unsigned defCount = analyze::GetDefCount(script, offset);
+ unsigned defCount = GetDefCount(script, offset);
for (unsigned i = 0; i < defCount; i++) {
printf(" type %d:", i);
pushedTypes(offset, i)->print(cx);
printf("\n");
}
if (monitoredTypes(offset))
printf(" monitored\n");
@@ -4141,17 +4289,17 @@ JSScript::typeSetFunction(JSContext *cx,
void
JSScript::typeCheckBytecode(JSContext *cx, const jsbytecode *pc, const js::Value *sp)
{
AutoEnterTypeInference enter(cx);
if (!(analysis_ && analysis_->ranInference()))
return;
- int defCount = js::analyze::GetDefCount(this, pc - code);
+ int defCount = GetDefCount(this, pc - code);
for (int i = 0; i < defCount; i++) {
const js::Value &val = sp[-defCount + i];
TypeSet *types = analysis_->pushedTypes(pc, i);
if (IgnorePushed(pc, i))
continue;
jstype type = GetValueType(cx, val);
@@ -4210,39 +4358,60 @@ JSObject::makeNewType(JSContext *cx, JSS
if (!getType()->unknownProperties()) {
/* Update the possible 'new' types for all prototype objects sharing the same type object. */
TypeSet *types = getType()->getProperty(cx, JSID_EMPTY, true);
if (types)
types->addType(cx, (jstype) type);
}
if (newScript && !type->unknownProperties()) {
- JSObject *baseobj = AnalyzeScriptProperties(cx, newScript);
+ /* Strawman object to add properties to and watch for duplicates. */
+ JSObject *baseobj = NewBuiltinClassInstance(cx, &js_ObjectClass, gc::FINALIZE_OBJECT16);
+ if (!baseobj)
+ return;
+
+ Vector<TypeNewScript::Initializer> initializerList(cx);
+ AnalyzeNewScriptProperties(cx, type, newScript, &baseobj, &initializerList);
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());
+ TypeNewScript::Initializer done(TypeNewScript::Initializer::DONE, 0);
+
/*
* 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,
baseobj->lastProperty());
- if (!baseobj)
- return;
-
- if (!type->addDefiniteProperties(cx, baseobj, true))
+ if (!baseobj ||
+ !type->addDefiniteProperties(cx, baseobj) ||
+ !initializerList.append(done)) {
+ cx->compartment->types.setPendingNukeTypes(cx);
return;
-
- type->newScript = newScript;
- type->newScriptFinalizeKind = unsigned(kind);
- type->newScriptShape = baseobj->lastProperty();
+ }
+
+ size_t numBytes = sizeof(TypeNewScript)
+ + (initializerList.length() * sizeof(TypeNewScript::Initializer));
+ type->newScript = (TypeNewScript *) cx->calloc_(numBytes);
+ if (!type->newScript) {
+ cx->compartment->types.setPendingNukeTypes(cx);
+ return;
+ }
+
+ type->newScript->script = newScript;
+ type->newScript->finalizeKind = unsigned(kind);
+ type->newScript->shape = baseobj->lastProperty();
+
+ type->newScript->initializerList = (TypeNewScript::Initializer *)
+ ((char *) type->newScript + sizeof(TypeNewScript));
+ PodCopy(type->newScript->initializerList, initializerList.begin(), initializerList.length());
}
}
newType = type;
setDelegate();
}
/////////////////////////////////////////////////////////////////////
@@ -4282,18 +4451,18 @@ 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");
+ js_TraceScript(trc, newScript->script);
+ gc::MarkShape(trc, newScript->shape, "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.
*/
@@ -4608,32 +4777,25 @@ JSScript::condenseTypes(JSContext *cx)
cx->free_(varTypes);
varTypes = NULL;
} else {
for (unsigned i = 0; i < num; i++)
TypeSet::CondenseSweepTypeSet(cx, &compartment->types, pcondensed, &varTypes[i]);
}
}
- TypeResult **presult = &typeResults;
+ TypeIntermediate **presult = &intermediateTypes;
while (*presult) {
- TypeResult *result = *presult;
- if (TypeIsObject(result->type)) {
- TypeObject *object = (TypeObject *) result->type;
- if (!object->marked) {
- if (!object->unknownProperties()) {
- *presult = result->next;
- cx->free_(result);
- continue;
- } else {
- result->type = (jstype) &compartment->types.typeEmpty;
- }
- }
+ TypeIntermediate *result = *presult;
+ if (result->sweep(cx, compartment)) {
+ presult = &result->next;
+ } else {
+ *presult = result->next;
+ cx->delete_(result);
}
- presult = &result->next;
}
}
void
JSScript::sweepAnalysis(JSContext *cx)
{
SweepTypeObjectList(cx, typeObjects);
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -1,9 +1,9 @@
-/* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
+//* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
/* vim: set ts=40 sw=4 et tw=99: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
@@ -104,35 +104,38 @@ TypeIsObject(jstype type)
/* Get the type of a jsval, or zero for an unknown special value. */
inline jstype GetValueType(JSContext *cx, const Value &val);
/*
* Type inference memory management overview.
*
* Inference constructs a global web of constraints relating the contents of
- * type sets particular to various scripts and type objects within a compartment.
- * There are two issues at hand to manage inference memory: collecting
- * the constraints, and collecting type sets (on TypeObject destruction).
+ * type sets particular to various scripts and type objects within a
+ * compartment. There are two issues at hand to manage inference memory:
+ * collecting the constraints, and collecting type sets on TypeObject
+ * destruction.
*
- * The constraints and types generated during analysis of a script depend entirely on
- * that script's input type sets --- the types of its arguments, upvar locals,
- * callee return values, object properties, and dynamic types (overflows, undefined
- * reads, etc.). On a GC, we collect the analysis information for all scripts
- * which have been analyzed, destroying the type constraints and intermediate
- * type sets associated with stack values, and add new condensed constraints to
- * the script's inputs which will trigger reanalysis and recompilation should
- * that input change in the future.
+ * The constraints and types generated during analysis of a script depend
+ * entirely on that script's input type sets --- the types of its arguments,
+ * upvar locals, callee return values, object properties, and dynamic types
+ * (overflows, undefined reads, etc.). On a GC, we collect the analysis
+ * information for all scripts which have been analyzed, destroying the type
+ * constraints and intermediate type sets associated with stack values, and add
+ * condensed constraints to the script's inputs which will trigger reanalysis
+ * and recompilation should that input change in the future.
*
* TypeObjects are collected when either the script they are associated with is
* destroyed or their prototype JSObject is destroyed.
*
- * If a GC happens while we are in the middle of analysis or working with a TypeScript
- * or TypeObject, we do not destroy/condense analysis information or collect any
- * TypeObjects or JSScripts. This is controlled with AutoEnterTypeInference.
+ * If a GC happens while we are in the middle of or working with analysis
+ * information (both type information and transient information stored in
+ * ScriptAnalysis), we do not destroy/condense analysis information or collect
+ * TypeObjects or JSScripts. This is controlled with AutoEnterAnalysis and/or
+ * AutoEnterTypeInference.
*/
/*
* A constraint which listens to additions to a type set and propagates those
* changes to other type sets.
*/
class TypeConstraint
{
@@ -326,16 +329,19 @@ 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);
+ /* If this type set definitely represents only a particular type object, get that object. */
+ inline TypeObject *getSingleObject();
+
void setIntermediate() { JS_ASSERT(!typeFlags); 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));
@@ -354,17 +360,16 @@ 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);
@@ -431,16 +436,45 @@ class TypeSet
/* A type set captured for use by JIT compilers. */
struct ClonedTypeSet
{
TypeFlags typeFlags;
TypeObject **objectSet;
unsigned objectCount;
};
+/*
+ * Handler which persists information about the intermediate types in a script
+ * (those appearing on stack values, which are destroyed on GC). These are
+ * attached to the JSScript and persist until it is destroyed; every time the
+ * types in the script are analyzed these are replayed to reconstruct
+ * the intermediate info they store.
+ *
+ * This is mostly for dynamic type results like integer overflows or holes read
+ * out of objects, but also allows specialized type constraints on intermediate
+ * values to be regenerated after GC.
+ */
+class TypeIntermediate
+{
+ public:
+ /* Next intermediate information for the script. */
+ TypeIntermediate *next;
+
+ TypeIntermediate() : next(NULL) {}
+
+ /* Replay this type information on a script whose types have just been analyzed. */
+ virtual void replay(JSContext *cx, JSScript *script) = 0;
+
+ /* Sweep this intermediate info, returning false to unlink and destroy this. */
+ virtual bool sweep(JSContext *cx, JSCompartment *compartment) = 0;
+
+ /* Whether this subsumes a dynamic type pushed by the bytecode at offset. */
+ virtual bool hasDynamicResult(uint32 offset, jstype type) { return false; }
+};
+
/* Type information about a property. */
struct Property
{
/* Identifier for this property, JSID_VOID for the aggregate integer index property. */
jsid id;
/* Possible types for this property, including types inherited from prototypes. */
TypeSet types;
@@ -448,16 +482,53 @@ struct Property
Property(jsid id)
: id(id)
{}
static uint32 keyBits(jsid id) { return (uint32) JSID_BITS(id); }
static jsid getKey(Property *p) { return p->id; }
};
+/*
+ * Information attached to a TypeObject if it is always constructed using 'new'
+ * on a particular script.
+ */
+struct TypeNewScript
+{
+ JSScript *script;
+
+ /* Finalize kind to use for newly constructed objects. */
+ /* gc::FinalizeKind */ unsigned finalizeKind;
+
+ /* Shape to use for newly constructed objects. */
+ const Shape *shape;
+
+ /*
+ * Order in which properties become initialized. We need this in case a
+ * scripted setter is added to one of the object's prototypes while it is in
+ * the middle of being initialized, so we can walk the stack and fixup any
+ * objects which look for in-progress objects which were prematurely set
+ * with their final shape. Initialization can traverse stack frames,
+ * in which case FRAME_PUSH/FRAME_POP are used.
+ */
+ struct Initializer {
+ enum Kind {
+ SETPROP,
+ FRAME_PUSH,
+ FRAME_POP,
+ DONE
+ } kind;
+ uint32 offset;
+ Initializer(Kind kind, uint32 offset)
+ : kind(kind), offset(offset)
+ {}
+ };
+ Initializer *initializerList;
+};
+
/* Type information about an object accessed by a script. */
struct TypeObject
{
#ifdef DEBUG
/* Name of this object. */
jsid name_;
#endif
@@ -473,22 +544,20 @@ 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.
+ * 'new' on the specified script, which adds some number of properties to
+ * the object in a definite order before the object escapes.
*/
- JSScript *newScript;
- /* gc::FinalizeKind */ unsigned newScriptFinalizeKind;
- const Shape *newScriptShape;
+ TypeNewScript *newScript;
/*
* 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;
@@ -568,17 +637,17 @@ 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);
+ bool addDefiniteProperties(JSContext *cx, JSObject *obj);
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);
@@ -641,36 +710,16 @@ struct TypeCallsite
inline bool forceThisTypes(JSContext *cx);
/* Get the new object at this callsite. */
inline TypeObject* getInitObject(JSContext *cx, bool isArray);
inline bool hasGlobal();
};
-/*
- * Type information about a dynamic value pushed by a script's opcode.
- * These are associated with each JSScript and persist after the
- * TypeScript is destroyed by GCs.
- */
-struct TypeResult
-{
- /*
- * Offset pushing the value. TypeResults are only generated for
- * the first stack slot actually pushed by a bytecode.
- */
- uint32 offset;
-
- /* Type which was pushed. */
- jstype type;
-
- /* Next dynamic result for the script. */
- TypeResult *next;
-};
-
struct ArrayTableKey;
typedef HashMap<ArrayTableKey,TypeObject*,ArrayTableKey,SystemAllocPolicy> ArrayTypeTable;
struct ObjectTableKey;
struct ObjectTableEntry;
typedef HashMap<ObjectTableKey,ObjectTableEntry,ObjectTableKey,SystemAllocPolicy> ObjectTypeTable;
/* Type information for a compartment. */