Bug 952891 - Add PIC fast-path for ForOfIterator iteration over arrays. r=jimb r=jorendorff DONTBUILD
authorKannan Vijayan <kvijayan@mozilla.com>
Thu, 13 Feb 2014 14:29:00 -0500
changeset 185805 2aa18173159381443de62e880715d87e96f31f35
parent 185804 b7187c9c00d018d12641ae2757bff9b93945f330
child 185806 1cad5f5cc647a126e7805f59a9822c686b3d7611
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb, jorendorff
bugs952891
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 952891 - Add PIC fast-path for ForOfIterator iteration over arrays. r=jimb r=jorendorff DONTBUILD
js/public/Class.h
js/src/builtin/Array.js
js/src/jsapi.h
js/src/jscompartment.h
js/src/jsiter.cpp
js/src/moz.build
js/src/vm/CommonPropertyNames.h
js/src/vm/GlobalObject.cpp
js/src/vm/GlobalObject.h
js/src/vm/SelfHosting.cpp
js/src/vm/SelfHosting.h
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -578,17 +578,17 @@ struct JSClass {
 // member initial value.  The "original ... value" verbiage is there because
 // in ECMA-262, global properties naming class objects are read/write and
 // deleteable, for the most part.
 //
 // Implementing this efficiently requires that global objects have classes
 // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
 // previously allowed, but is now an ES5 violation and thus unsupported.
 //
-#define JSCLASS_GLOBAL_SLOT_COUNT      (3 + JSProto_LIMIT * 3 + 30)
+#define JSCLASS_GLOBAL_SLOT_COUNT      (3 + JSProto_LIMIT * 3 + 31)
 #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n)                                    \
     (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
 #define JSCLASS_GLOBAL_FLAGS                                                  \
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0)
 #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp)                              \
   (((clasp)->flags & JSCLASS_IS_GLOBAL)                                       \
    && JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
 
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -493,24 +493,27 @@ function ArrayFindIndex(predicate/*, thi
 #define ARRAY_ITERATOR_SLOT_NEXT_INDEX 1
 #define ARRAY_ITERATOR_SLOT_ITEM_KIND 2
 
 #define ITEM_KIND_VALUE 0
 #define ITEM_KIND_KEY_AND_VALUE 1
 #define ITEM_KIND_KEY 2
 
 // ES6 draft specification, section 22.1.5.1, version 2013-09-05.
-function CreateArrayIterator(obj, kind) {
+function CreateArrayIteratorAt(obj, kind, n) {
     var iteratedObject = ToObject(obj);
     var iterator = NewArrayIterator();
     UnsafeSetReservedSlot(iterator, ARRAY_ITERATOR_SLOT_ITERATED_OBJECT, iteratedObject);
-    UnsafeSetReservedSlot(iterator, ARRAY_ITERATOR_SLOT_NEXT_INDEX, 0);
+    UnsafeSetReservedSlot(iterator, ARRAY_ITERATOR_SLOT_NEXT_INDEX, n);
     UnsafeSetReservedSlot(iterator, ARRAY_ITERATOR_SLOT_ITEM_KIND, kind);
     return iterator;
 }
+function CreateArrayIterator(obj, kind) {
+    return CreateArrayIteratorAt(obj, kind, 0);
+}
 
 function ArrayIteratorIdentity() {
     return this;
 }
 
 function ArrayIteratorNext() {
     // FIXME: ArrayIterator prototype should not pass this test.  Bug 924059.
     if (!IsObject(this) || !IsArrayIterator(this))
@@ -539,16 +542,20 @@ function ArrayIteratorNext() {
         pair[1] = a[index];
         return { value: pair, done : false };
     }
 
     assert(itemKind === ITEM_KIND_KEY, itemKind);
     return { value: index, done: false };
 }
 
+function ArrayValuesAt(n) {
+    return CreateArrayIteratorAt(this, ITEM_KIND_VALUE, n);
+}
+
 function ArrayValues() {
     return CreateArrayIterator(this, ITEM_KIND_VALUE);
 }
 
 function ArrayEntries() {
     return CreateArrayIterator(this, ITEM_KIND_KEY_AND_VALUE);
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4856,23 +4856,40 @@ SetAsmJSCacheOps(JSRuntime *rt, const As
  *         break;
  *       if (!DoStuff(cx, val))
  *         return false;
  *     }
  */
 class MOZ_STACK_CLASS JS_PUBLIC_API(ForOfIterator) {
   protected:
     JSContext *cx_;
+    /*
+     * Use the ForOfPIC on the global object (see vm/GlobalObject.h) to try
+     * to optimize iteration across arrays.
+     *
+     *  Case 1: Regular Iteration
+     *      iterator - pointer to the iterator object.
+     *      index - fixed to NOT_ARRAY (== UINT32_MAX)
+     *
+     *  Case 2: Optimized Array Iteration
+     *      iterator - pointer to the array object.
+     *      index - current position in array.
+     *
+     * The cases are distinguished by whether or not |index| is equal to NOT_ARRAY.
+     */
     JS::RootedObject iterator;
+    uint32_t index;
+
+    static const uint32_t NOT_ARRAY = UINT32_MAX;
 
     ForOfIterator(const ForOfIterator &) MOZ_DELETE;
     ForOfIterator &operator=(const ForOfIterator &) MOZ_DELETE;
 
   public:
-    ForOfIterator(JSContext *cx) : cx_(cx), iterator(cx) { }
+    ForOfIterator(JSContext *cx) : cx_(cx), iterator(cx_), index(NOT_ARRAY) { }
 
     enum NonIterableBehavior {
         ThrowOnNonIterable,
         AllowNonIterable
     };
 
     /*
      * Initialize the iterator.  If AllowNonIterable is passed then if iterable
@@ -4891,13 +4908,17 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(ForO
 
     /*
      * If initialized with throwOnNonCallable = false, check whether
      * the value is iterable.
      */
     bool valueIsIterable() const {
         return iterator;
     }
+
+  private:
+    inline bool nextFromOptimizedArray(MutableHandleValue val, bool *done);
+    bool materializeArrayIterator();
 };
 
 } /* namespace JS */
 
 #endif /* jsapi_h */
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -8,16 +8,17 @@
 #define jscompartment_h
 
 #include "mozilla/MemoryReporting.h"
 
 #include "builtin/TypedObject.h"
 #include "builtin/TypeRepresentation.h"
 #include "gc/Zone.h"
 #include "vm/GlobalObject.h"
+#include "vm/PIC.h"
 
 namespace js {
 
 namespace jit {
 class JitCompartment;
 }
 
 namespace gc {
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -1273,16 +1273,38 @@ const Class StopIterationObject::class_ 
 bool
 ForOfIterator::init(HandleValue iterable, NonIterableBehavior nonIterableBehavior)
 {
     JSContext *cx = cx_;
     RootedObject iterableObj(cx, ToObject(cx, iterable));
     if (!iterableObj)
         return false;
 
+    JS_ASSERT(index == NOT_ARRAY);
+
+    // Check the PIC first for a match.
+    if (iterableObj->is<ArrayObject>()) {
+        ForOfPIC::Chain *stubChain = ForOfPIC::getOrCreate(cx);
+        if (!stubChain)
+            return false;
+
+        bool optimized;
+        if (!stubChain->tryOptimizeArray(cx, iterableObj, &optimized))
+            return false;
+
+        if (optimized) {
+            // Got optimized stub.  Array is optimizable.
+            index = 0;
+            iterator = iterableObj;
+            return true;
+        }
+    }
+
+    JS_ASSERT(index == NOT_ARRAY);
+
     // The iterator is the result of calling obj[@@iterator]().
     InvokeArgs args(cx);
     if (!args.init(0))
         return false;
     args.setThis(ObjectValue(*iterableObj));
 
     RootedValue callee(cx);
     if (!JSObject::getProperty(cx, iterableObj, iterableObj, cx->names().std_iterator, &callee))
@@ -1309,47 +1331,120 @@ ForOfIterator::init(HandleValue iterable
 
     iterator = ToObject(cx, args.rval());
     if (!iterator)
         return false;
 
     return true;
 }
 
+inline bool
+ForOfIterator::nextFromOptimizedArray(MutableHandleValue vp, bool *done)
+{
+    JS_ASSERT(index != NOT_ARRAY);
+
+    if (!JS_CHECK_OPERATION_LIMIT(cx_))
+        return false;
+
+    JS_ASSERT(iterator->isNative());
+    JS_ASSERT(iterator->is<ArrayObject>());
+
+    if (index >= iterator->as<ArrayObject>().length()) {
+        vp.setUndefined();
+        *done = true;
+        return true;
+    }
+    *done = false;
+
+    // Try to get array element via direct access.
+    if (index < iterator->getDenseInitializedLength()) {
+        vp.set(iterator->getDenseElement(index));
+        if (!vp.isMagic(JS_ELEMENTS_HOLE)) {
+            ++index;
+            return true;
+        }
+    }
+
+    return JSObject::getElement(cx_, iterator, iterator, index++, vp);
+}
+
 bool
 ForOfIterator::next(MutableHandleValue vp, bool *done)
 {
     JS_ASSERT(iterator);
 
-    JSContext *cx = cx_;
-    RootedValue method(cx);
-    if (!JSObject::getProperty(cx, iterator, iterator, cx->names().next, &method))
+    if (index != NOT_ARRAY) {
+        ForOfPIC::Chain *stubChain = ForOfPIC::getOrCreate(cx_);
+        if (!stubChain)
+            return false;
+
+        if (stubChain->isArrayNextStillSane())
+            return nextFromOptimizedArray(vp, done);
+
+        // ArrayIterator.prototype.next changed, materialize a proper
+        // ArrayIterator instance and fall through to slowpath case.
+        if (!materializeArrayIterator())
+            return false;
+    }
+
+    RootedValue method(cx_);
+    if (!JSObject::getProperty(cx_, iterator, iterator, cx_->names().next, &method))
         return false;
 
-    InvokeArgs args(cx);
+    InvokeArgs args(cx_);
     if (!args.init(1))
         return false;
     args.setCallee(method);
     args.setThis(ObjectValue(*iterator));
     args[0].setUndefined();
-    if (!Invoke(cx, args))
+    if (!Invoke(cx_, args))
         return false;
 
-    RootedObject resultObj(cx, ToObject(cx, args.rval()));
+    RootedObject resultObj(cx_, ToObject(cx_, args.rval()));
     if (!resultObj)
         return false;
-    RootedValue doneVal(cx);
-    if (!JSObject::getProperty(cx, resultObj, resultObj, cx->names().done, &doneVal))
+    RootedValue doneVal(cx_);
+    if (!JSObject::getProperty(cx_, resultObj, resultObj, cx_->names().done, &doneVal))
         return false;
     *done = ToBoolean(doneVal);
     if (*done) {
         vp.setUndefined();
         return true;
     }
-    return JSObject::getProperty(cx, resultObj, resultObj, cx->names().value, vp);
+    return JSObject::getProperty(cx_, resultObj, resultObj, cx_->names().value, vp);
+}
+
+bool
+ForOfIterator::materializeArrayIterator()
+{
+    JS_ASSERT(index != NOT_ARRAY);
+
+    const char *nameString = "ArrayValuesAt";
+
+    RootedAtom name(cx_, Atomize(cx_, nameString, strlen(nameString)));
+    if (!name)
+        return false;
+
+    RootedValue val(cx_);
+    if (!cx_->global()->getSelfHostedFunction(cx_, name, name, 1, &val))
+        return false;
+
+    InvokeArgs args(cx_);
+    if (!args.init(1))
+        return false;
+    args.setCallee(val);
+    args.setThis(ObjectValue(*iterator));
+    args[0].set(Int32Value(index));
+    if (!Invoke(cx_, args))
+        return false;
+
+    index = NOT_ARRAY;
+    // Result of call to ArrayValuesAt must be an object.
+    iterator = &args.rval().toObject();
+    return true;
 }
 
 /*** Generators **********************************************************************************/
 
 template<typename T>
 static void
 FinalizeGenerator(FreeOp *fop, JSObject *obj)
 {
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -164,16 +164,17 @@ UNIFIED_SOURCES += [
     'vm/ForkJoin.cpp',
     'vm/GlobalObject.cpp',
     'vm/Id.cpp',
     'vm/Interpreter.cpp',
     'vm/MemoryMetrics.cpp',
     'vm/Monitor.cpp',
     'vm/ObjectImpl.cpp',
     'vm/OldDebugAPI.cpp',
+    'vm/PIC.cpp',
     'vm/Probes.cpp',
     'vm/PropertyKey.cpp',
     'vm/ProxyObject.cpp',
     'vm/RegExpObject.cpp',
     'vm/RegExpStatics.cpp',
     'vm/Runtime.cpp',
     'vm/ScopeObject.cpp',
     'vm/SelfHosting.cpp',
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -12,17 +12,19 @@
 #include "jsprototypes.h"
 
 #define FOR_EACH_COMMON_PROPERTYNAME(macro) \
     macro(anonymous, anonymous, "anonymous") \
     macro(Any, Any, "Any") \
     macro(apply, apply, "apply") \
     macro(arguments, arguments, "arguments") \
     macro(as, as, "as") \
+    macro(ArrayIteratorNext, ArrayIteratorNext, "ArrayIteratorNext") \
     macro(ArrayType, ArrayType, "ArrayType") \
+    macro(ArrayValues, ArrayValues, "ArrayValues") \
     macro(buffer, buffer, "buffer") \
     macro(builder, builder, "builder") \
     macro(byteLength, byteLength, "byteLength") \
     macro(byteAlignment, byteAlignment, "byteAlignment") \
     macro(byteOffset, byteOffset, "byteOffset") \
     macro(bytes, bytes, "bytes") \
     macro(BYTES_PER_ELEMENT, BYTES_PER_ELEMENT, "BYTES_PER_ELEMENT") \
     macro(call, call, "call") \
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -19,16 +19,17 @@
 #if EXPOSE_INTL_API
 # include "builtin/Intl.h"
 #endif
 #include "builtin/MapObject.h"
 #include "builtin/Object.h"
 #include "builtin/RegExp.h"
 #include "builtin/SIMD.h"
 #include "builtin/TypedObject.h"
+#include "vm/PIC.h"
 #include "vm/RegExpStatics.h"
 #include "vm/StopIterationObject.h"
 #include "vm/WeakMapObject.h"
 
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
@@ -736,16 +737,31 @@ GlobalObject::addDebugger(JSContext *cx,
         return false;
     if (!debuggers->append(dbg)) {
         global->compartment()->removeDebuggee(cx->runtime()->defaultFreeOp(), global);
         return false;
     }
     return true;
 }
 
+/* static */ JSObject *
+GlobalObject::getOrCreateForOfPICObject(JSContext *cx, Handle<GlobalObject *> global)
+{
+    assertSameCompartment(cx, global);
+    JSObject *forOfPIC = global->getForOfPICObject();
+    if (forOfPIC)
+        return forOfPIC;
+
+    forOfPIC = ForOfPIC::createForOfPICObject(cx, global);
+    if (!forOfPIC)
+        return nullptr;
+    global->setReservedSlot(FOR_OF_PIC_CHAIN, ObjectValue(*forOfPIC));
+    return forOfPIC;
+}
+
 bool
 GlobalObject::getSelfHostedFunction(JSContext *cx, HandleAtom selfHostedName, HandleAtom name,
                                     unsigned nargs, MutableHandleValue funVal)
 {
     RootedId shId(cx, AtomToId(selfHostedName));
     RootedObject holder(cx, cx->global()->intrinsicsHolder());
 
     if (cx->global()->maybeGetIntrinsicValue(shId, funVal.address()))
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -106,19 +106,20 @@ class GlobalObject : public JSObject
     static const unsigned REGEXP_STATICS          = DATE_TIME_FORMAT_PROTO + 1;
     static const unsigned WARNED_WATCH_DEPRECATED = REGEXP_STATICS + 1;
     static const unsigned WARNED_PROTO_SETTING_SLOW = WARNED_WATCH_DEPRECATED + 1;
     static const unsigned RUNTIME_CODEGEN_ENABLED = WARNED_PROTO_SETTING_SLOW + 1;
     static const unsigned DEBUGGERS               = RUNTIME_CODEGEN_ENABLED + 1;
     static const unsigned INTRINSICS              = DEBUGGERS + 1;
     static const unsigned FLOAT32X4_TYPE_DESCR   = INTRINSICS + 1;
     static const unsigned INT32X4_TYPE_DESCR     = FLOAT32X4_TYPE_DESCR + 1;
+    static const unsigned FOR_OF_PIC_CHAIN        = INT32X4_TYPE_DESCR + 1;
 
     /* Total reserved-slot count for global objects. */
-    static const unsigned RESERVED_SLOTS = INT32X4_TYPE_DESCR + 1;
+    static const unsigned RESERVED_SLOTS = FOR_OF_PIC_CHAIN + 1;
 
     /*
      * The slot count must be in the public API for JSCLASS_GLOBAL_FLAGS, and
      * we won't expose GlobalObject, so just assert that the two values are
      * synchronized.
      */
     static_assert(JSCLASS_GLOBAL_SLOT_COUNT == RESERVED_SLOTS,
                   "global object slot counts are inconsistent");
@@ -670,16 +671,24 @@ class GlobalObject : public JSObject
     DebuggerVector *getDebuggers();
 
     /*
      * The same, but create the empty vector if one does not already
      * exist. Returns nullptr only on OOM.
      */
     static DebuggerVector *getOrCreateDebuggers(JSContext *cx, Handle<GlobalObject*> global);
 
+    inline JSObject *getForOfPICObject() {
+        Value forOfPIC = getReservedSlot(FOR_OF_PIC_CHAIN);
+        if (forOfPIC.isUndefined())
+            return nullptr;
+        return &forOfPIC.toObject();
+    }
+    static JSObject *getOrCreateForOfPICObject(JSContext *cx, Handle<GlobalObject*> global);
+
     static bool addDebugger(JSContext *cx, Handle<GlobalObject*> global, Debugger *dbg);
 };
 
 template<>
 inline void
 GlobalObject::setCreateArrayFromBuffer<uint8_t>(Handle<JSFunction*> fun)
 {
     setCreateArrayFromBufferHelper(FROM_BUFFER_UINT8, fun);
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -15,16 +15,17 @@
 #include "selfhosted.out.h"
 
 #include "builtin/Intl.h"
 #include "builtin/TypedObject.h"
 #include "gc/Marking.h"
 #include "vm/Compression.h"
 #include "vm/ForkJoin.h"
 #include "vm/Interpreter.h"
+#include "vm/String.h"
 
 #include "jsfuninlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/BooleanObject-inl.h"
 #include "vm/NumberObject-inl.h"
 #include "vm/StringObject-inl.h"
 
@@ -1161,8 +1162,14 @@ js::SelfHostedFunction(JSContext *cx, Ha
     RootedValue func(cx);
     if (!GlobalObject::getIntrinsicValue(cx, cx->global(), propName, &func))
         return nullptr;
 
     JS_ASSERT(func.isObject());
     JS_ASSERT(func.toObject().is<JSFunction>());
     return &func.toObject().as<JSFunction>();
 }
+
+bool
+js::IsSelfHostedFunctionWithName(JSFunction *fun, JSAtom *name)
+{
+    return fun->isSelfHostedBuiltin() && fun->getExtendedSlot(0).toString() == name;
+}
--- a/js/src/vm/SelfHosting.h
+++ b/js/src/vm/SelfHosting.h
@@ -4,22 +4,30 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef vm_SelfHosting_h_
 #define vm_SelfHosting_h_
 
 #include "jsapi.h"
 
+class JSAtom;
+
 namespace js {
 
 /*
  * When wrapping objects for use by self-hosted code, we skip all checks and
  * always create an OpaqueWrapper that allows nothing.
  * When wrapping functions from the self-hosting compartment for use in other
  * compartments, we create an OpaqueWrapperWithCall that only allows calls,
  * nothing else.
  */
 extern const JSWrapObjectCallbacks SelfHostingWrapObjectCallbacks;
 
+/*
+ * Check whether the given JSFunction is a self-hosted function whose
+ * self-hosted name is the given name.
+ */
+bool IsSelfHostedFunctionWithName(JSFunction *fun, JSAtom *name);
+
 } /* namespace js */
 
 #endif /* vm_SelfHosting_h_ */