Bug 898746 - Type rest argument arrays as dense arrays with unknown element type. (r=bhackett)
authorShu-yu Guo <shu@rfrn.org>
Fri, 02 Aug 2013 08:24:57 -0700
changeset 141144 80fe42f29748b5f794c1028e02c9c5c34ccfb7dd
parent 141143 9b4314a6c8ee391b24366bf40774d8d6295ddc0d
child 141145 0834a49b378d6175315b4e78fb9dc340cb039b7e
push idunknown
push userunknown
push dateunknown
reviewersbhackett
bugs898746
milestone25.0a1
Bug 898746 - Type rest argument arrays as dense arrays with unknown element type. (r=bhackett)
js/src/ion/BaselineIC.cpp
js/src/ion/IonBuilder.cpp
js/src/ion/LIR-Common.h
js/src/ion/ParallelFunctions.cpp
js/src/ion/VMFunctions.cpp
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/vm/Stack.cpp
--- a/js/src/ion/BaselineIC.cpp
+++ b/js/src/ion/BaselineIC.cpp
@@ -8210,16 +8210,17 @@ DoCreateRestParameter(JSContext *cx, Bas
     unsigned numFormals = frame->numFormalArgs() - 1;
     unsigned numActuals = frame->numActualArgs();
     unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0;
     Value *rest = frame->argv() + numFormals;
 
     JSObject *obj = NewDenseCopiedArray(cx, numRest, rest, NULL);
     if (!obj)
         return false;
+    types::FixRestArgumentsType(cx, obj);
     res.setObject(*obj);
     return true;
 }
 
 typedef bool(*DoCreateRestParameterFn)(JSContext *cx, BaselineFrame *, ICRest_Fallback *,
                                        MutableHandleValue);
 static const VMFunction DoCreateRestParameterInfo =
     FunctionInfo<DoCreateRestParameterFn>(DoCreateRestParameter);
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -7210,42 +7210,51 @@ IonBuilder::jsop_arguments_getelem()
 }
 
 bool
 IonBuilder::jsop_arguments_setelem(MDefinition *object, MDefinition *index, MDefinition *value)
 {
     return abort("NYI arguments[]=");
 }
 
+static JSObject *
+CreateRestArgumentsTemplateObject(JSContext *cx, unsigned length)
+{
+    JSObject *templateObject = NewDenseUnallocatedArray(cx, 0, NULL, TenuredObject);
+    if (templateObject)
+        types::FixRestArgumentsType(cx, templateObject);
+    return templateObject;
+}
+
 bool
 IonBuilder::jsop_rest()
 {
     // We don't know anything about the callee.
     if (inliningDepth_ == 0) {
-        // Get an empty template array that doesn't have a pc-tracked type.
-        JSObject *templateObject = NewDenseUnallocatedArray(cx, 0, NULL, TenuredObject);
+        JSObject *templateObject = CreateRestArgumentsTemplateObject(cx, 0);
         if (!templateObject)
             return false;
-
         MArgumentsLength *numActuals = MArgumentsLength::New();
         current->add(numActuals);
 
         // Pass in the number of actual arguments, the number of formals (not
         // including the rest parameter slot itself), and the template object.
         MRest *rest = MRest::New(numActuals, info().nargs() - 1, templateObject);
         current->add(rest);
         current->push(rest);
         return true;
     }
 
     // We know the exact number of arguments the callee pushed.
     unsigned numActuals = inlineCallInfo_->argv().length();
     unsigned numFormals = info().nargs() - 1;
     unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0;
-    JSObject *templateObject = NewDenseUnallocatedArray(cx, numRest, NULL, TenuredObject);
+    JSObject *templateObject = CreateRestArgumentsTemplateObject(cx, numRest);
+    if (!templateObject)
+        return false;
 
     MNewArray *array = new MNewArray(numRest, templateObject, MNewArray::NewArray_Allocating);
     current->add(array);
 
     if (numActuals <= numFormals) {
         current->push(array);
         return true;
     }
--- a/js/src/ion/LIR-Common.h
+++ b/js/src/ion/LIR-Common.h
@@ -4405,18 +4405,18 @@ class LRestPar : public LCallInstruction
         setTemp(2, temp3);
     }
     const LAllocation *forkJoinSlice() {
         return getOperand(0);
     }
     const LAllocation *numActuals() {
         return getOperand(1);
     }
-    MRest *mir() const {
-        return mir_->toRest();
+    MRestPar *mir() const {
+        return mir_->toRestPar();
     }
 };
 
 class LGuardThreadLocalObject : public LCallInstructionHelper<0, 2, 1>
 {
   public:
     LIR_HEADER(GuardThreadLocalObject);
 
--- a/js/src/ion/ParallelFunctions.cpp
+++ b/js/src/ion/ParallelFunctions.cpp
@@ -479,17 +479,16 @@ ion::InitRestParameterPar(ForkJoinSlice 
 {
     // In parallel execution, we should always have succeeded in allocation
     // before this point. We can do the allocation here like in the sequential
     // path, but duplicating the initGCThing logic is too tedious.
     JS_ASSERT(res);
     JS_ASSERT(res->is<ArrayObject>());
     JS_ASSERT(!res->getDenseInitializedLength());
     JS_ASSERT(res->type() == templateObj->type());
-    JS_ASSERT(res->type()->unknownProperties());
 
     if (length) {
         JSObject::EnsureDenseResult edr = res->parExtendDenseElements(slice, rest, length);
         if (edr != JSObject::ED_OK)
             return TP_FATAL;
     }
 
     out.set(res);
--- a/js/src/ion/VMFunctions.cpp
+++ b/js/src/ion/VMFunctions.cpp
@@ -681,31 +681,33 @@ JSObject *
 InitRestParameter(JSContext *cx, uint32_t length, Value *rest, HandleObject templateObj,
                   HandleObject objRes)
 {
     if (objRes) {
         Rooted<ArrayObject*> arrRes(cx, &objRes->as<ArrayObject>());
 
         JS_ASSERT(!arrRes->getDenseInitializedLength());
         JS_ASSERT(arrRes->type() == templateObj->type());
-        JS_ASSERT(arrRes->type()->unknownProperties());
 
         // Fast path: we managed to allocate the array inline; initialize the
         // slots.
         if (length > 0) {
             if (!arrRes->ensureElements(cx, length))
                 return NULL;
             arrRes->setDenseInitializedLength(length);
             arrRes->initDenseElements(0, rest, length);
             arrRes->setLengthInt32(length);
         }
         return arrRes;
     }
 
-    return NewDenseCopiedArray(cx, length, rest, NULL);
+    ArrayObject *arrRes = NewDenseCopiedArray(cx, length, rest, NULL);
+    if (arrRes)
+        arrRes->setType(templateObj->type());
+    return arrRes;
 }
 
 bool
 HandleDebugTrap(JSContext *cx, BaselineFrame *frame, uint8_t *retAddr, bool *mustReturn)
 {
     *mustReturn = false;
 
     RootedScript script(cx, frame->script());
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -3126,29 +3126,59 @@ struct types::ArrayTableKey
     }
 
     static inline bool match(const ArrayTableKey &v1, const ArrayTableKey &v2) {
         return v1.type == v2.type && v1.proto == v2.proto;
     }
 };
 
 void
-TypeCompartment::fixArrayType(JSContext *cx, JSObject *obj)
-{
-    AutoEnterAnalysis enter(cx);
-
+TypeCompartment::setTypeToHomogenousArray(JSContext *cx, JSObject *obj, Type elementType)
+{
     if (!arrayTypeTable) {
         arrayTypeTable = cx->new_<ArrayTypeTable>();
         if (!arrayTypeTable || !arrayTypeTable->init()) {
             arrayTypeTable = NULL;
             cx->compartment()->types.setPendingNukeTypes(cx);
             return;
         }
     }
 
+    ArrayTableKey key;
+    key.type = elementType;
+    key.proto = obj->getProto();
+    ArrayTypeTable::AddPtr p = arrayTypeTable->lookupForAdd(key);
+
+    if (p) {
+        obj->setType(p->value);
+    } else {
+        /* Make a new type to use for future arrays with the same elements. */
+        RootedObject objProto(cx, obj->getProto());
+        TypeObject *objType = newTypeObject(cx, &ArrayObject::class_, objProto);
+        if (!objType) {
+            cx->compartment()->types.setPendingNukeTypes(cx);
+            return;
+        }
+        obj->setType(objType);
+
+        if (!objType->unknownProperties())
+            objType->addPropertyType(cx, JSID_VOID, elementType);
+
+        if (!arrayTypeTable->relookupOrAdd(p, key, objType)) {
+            cx->compartment()->types.setPendingNukeTypes(cx);
+            return;
+        }
+    }
+}
+
+void
+TypeCompartment::fixArrayType(JSContext *cx, JSObject *obj)
+{
+    AutoEnterAnalysis enter(cx);
+
     /*
      * 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->is<ArrayObject>());
 
@@ -3163,52 +3193,41 @@ TypeCompartment::fixArrayType(JSContext 
         if (ntype != type) {
             if (NumberTypes(type, ntype))
                 type = Type::DoubleType();
             else
                 return;
         }
     }
 
-    ArrayTableKey key;
-    key.type = type;
-    key.proto = obj->getProto();
-    ArrayTypeTable::AddPtr p = arrayTypeTable->lookupForAdd(key);
-
-    if (p) {
-        obj->setType(p->value);
-    } else {
-        Rooted<Type> origType(cx, type);
-        /* Make a new type to use for future arrays with the same elements. */
-        RootedObject objProto(cx, obj->getProto());
-        Rooted<TypeObject*> objType(cx, newTypeObject(cx, &ArrayObject::class_, objProto));
-        if (!objType) {
-            cx->compartment()->types.setPendingNukeTypes(cx);
-            return;
-        }
-        obj->setType(objType);
-
-        if (!objType->unknownProperties())
-            objType->addPropertyType(cx, JSID_VOID, type);
-
-        // The key's fields may have been moved by moving GC and therefore the
-        // AddPtr is now invalid. ArrayTypeTable's equality and hashcodes
-        // operators use only the two fields (type and proto) directly, so we
-        // can just conditionally update them here.
-        if (type != origType || key.proto != obj->getProto()) {
-            key.type = origType;
-            key.proto = obj->getProto();
-            p = arrayTypeTable->lookupForAdd(key);
-        }
-
-        if (!arrayTypeTable->relookupOrAdd(p, key, objType)) {
-            cx->compartment()->types.setPendingNukeTypes(cx);
-            return;
-        }
-    }
+    setTypeToHomogenousArray(cx, obj, type);
+}
+
+void
+types::FixRestArgumentsType(ExclusiveContext *cxArg, JSObject *obj)
+{
+    if (cxArg->isJSContext()) {
+        JSContext *cx = cxArg->asJSContext();
+        if (cx->typeInferenceEnabled())
+            cx->compartment()->types.fixRestArgumentsType(cx, obj);
+    }
+}
+
+void
+TypeCompartment::fixRestArgumentsType(JSContext *cx, JSObject *obj)
+{
+    AutoEnterAnalysis enter(cx);
+
+    /*
+     * Tracking element types for rest argument arrays is not worth it, but we
+     * still want it to be known that it's a dense array.
+     */
+    JS_ASSERT(obj->is<ArrayObject>());
+
+    setTypeToHomogenousArray(cx, obj, Type::UnknownType());
 }
 
 /*
  * 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.
  */
@@ -6377,21 +6396,21 @@ TypeCompartment::sweep(FreeOp *fop)
      * 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;
             JS_ASSERT(e.front().value->proto == key.proto);
-            JS_ASSERT(!key.type.isSingleObject());
+            JS_ASSERT(key.type.isUnknown() || !key.type.isSingleObject());
 
             bool remove = false;
             TypeObject *typeObject = NULL;
-            if (key.type.isTypeObject()) {
+            if (!key.type.isUnknown() && key.type.isTypeObject()) {
                 typeObject = key.type.typeObject();
                 if (IsTypeObjectAboutToBeFinalized(&typeObject))
                     remove = true;
             }
             if (IsTypeObjectAboutToBeFinalized(e.front().value.unsafeGet()))
                 remove = true;
 
             if (remove) {
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -1364,18 +1364,23 @@ struct TypeCompartment
     /* Table for referencing types of objects keyed to an allocation site. */
     AllocationSiteTable *allocationSiteTable;
 
     /* Tables for determining types of singleton/JSON objects. */
 
     ArrayTypeTable *arrayTypeTable;
     ObjectTypeTable *objectTypeTable;
 
+  private:
+    void setTypeToHomogenousArray(JSContext *cx, JSObject *obj, Type type);
+
+  public:
     void fixArrayType(JSContext *cx, JSObject *obj);
     void fixObjectType(JSContext *cx, JSObject *obj);
+    void fixRestArgumentsType(JSContext *cx, JSObject *obj);
 
     JSObject *newTypedObject(JSContext *cx, IdValuePair *properties, size_t nproperties);
 
     /* Logging fields */
 
     /* Counts of stack type sets with some number of possible operand types. */
     static const unsigned TYPE_COUNT_LIMIT = 4;
     unsigned typeCounts[TYPE_COUNT_LIMIT];
@@ -1428,16 +1433,18 @@ struct TypeCompartment
     void sweepShapes(FreeOp *fop);
     void sweepCompilerOutputs(FreeOp *fop, bool discardConstraints);
 
     void maybePurgeAnalysis(JSContext *cx, bool force = false);
 
     void finalizeObjects();
 };
 
+void FixRestArgumentsType(ExclusiveContext *cxArg, JSObject *obj);
+
 struct TypeZone
 {
     JS::Zone                     *zone_;
 
     /* Pool for type information in this zone. */
     static const size_t TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 8 * 1024;
     js::LifoAlloc                typeLifoAlloc;
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -184,17 +184,21 @@ StackFrame::copyRawFrameSlots(AutoValueV
 
 JSObject *
 StackFrame::createRestParameter(JSContext *cx)
 {
     JS_ASSERT(fun()->hasRest());
     unsigned nformal = fun()->nargs - 1, nactual = numActualArgs();
     unsigned nrest = (nactual > nformal) ? nactual - nformal : 0;
     Value *restvp = argv() + nformal;
-    return NewDenseCopiedArray(cx, nrest, restvp, NULL);
+    JSObject *obj = NewDenseCopiedArray(cx, nrest, restvp, NULL);
+    if (!obj)
+        return NULL;
+    types::FixRestArgumentsType(cx, obj);
+    return obj;
 }
 
 static inline void
 AssertDynamicScopeMatchesStaticScope(JSContext *cx, JSScript *script, JSObject *scope)
 {
 #ifdef DEBUG
     RootedObject enclosingScope(cx, script->enclosingStaticScope());
     for (StaticScopeIter i(cx, enclosingScope); !i.done(); i++) {