Bug 697343 - Introduce a slice hook to allow optimizing Array.prototype.slice for Proxies etc. r=jandem,bz
authorTom Schuster <evilpies@gmail.com>
Thu, 05 Dec 2013 20:07:24 +0100
changeset 159635 aa573b104bdfe1bf994d14dd0bb6de2465791953
parent 159634 e0803c4ddc9020cc46128326b8a2b82c96d1e790
child 159636 0fbdff3a10e38819be92320e95e8a98c93186662
push id25808
push usercbook@mozilla.com
push dateTue, 10 Dec 2013 12:03:31 +0000
treeherdermozilla-central@7fb91a422c5e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem, bz
bugs697343
milestone29.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 697343 - Introduce a slice hook to allow optimizing Array.prototype.slice for Proxies etc. r=jandem,bz
dom/permission/tests/file_framework.js
js/public/Class.h
js/src/builtin/TypedObject.cpp
js/src/jsarray.cpp
js/src/jsproxy.cpp
js/src/jsproxy.h
js/src/vm/ScopeObject.cpp
js/src/vm/TypedArrayObject.cpp
js/xpconnect/src/XPCWrappedNativeJSOps.cpp
js/xpconnect/src/xpcprivate.h
--- a/dom/permission/tests/file_framework.js
+++ b/dom/permission/tests/file_framework.js
@@ -164,17 +164,21 @@ function addPermissions(aPerms, aDoc, aC
   SpecialPowers.pushPermissions(permList, aCallback);
 }
 
 function expandPermissions(aPerms) {
   var perms = [];
   aPerms.forEach(function(el) {
     var access = permTable[el].access ? "readwrite" : null;
     var expanded = SpecialPowers.unwrap(expand(el, access));
-    perms = perms.concat(expanded.slice(0));
+    // COW arrays don't behave array-like enough, to allow
+    // using expanded.slice(0) here.
+    for (let i = 0; i < expanded.length; i++) {
+      perms.push(expanded[i]);
+    }
   });
 
   return perms;
 }
 
 function msgHandler(evt) {
   var data = evt.data;
   var test = pendingTests[data.id];
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -375,16 +375,20 @@ typedef bool
 (* DeleteSpecialOp)(JSContext *cx, JS::HandleObject obj, HandleSpecialId sid, bool *succeeded);
 
 typedef bool
 (* WatchOp)(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable);
 
 typedef bool
 (* UnwatchOp)(JSContext *cx, JS::HandleObject obj, JS::HandleId id);
 
+typedef bool
+(* SliceOp)(JSContext *cx, JS::HandleObject obj, uint32_t begin, uint32_t end,
+            JS::HandleObject result); // result is actually preallocted.
+
 typedef JSObject *
 (* ObjectOp)(JSContext *cx, JS::HandleObject obj);
 typedef void
 (* FinalizeOp)(FreeOp *fop, JSObject *obj);
 
 #define JS_CLASS_MEMBERS                                                      \
     const char          *name;                                                \
     uint32_t            flags;                                                \
@@ -463,16 +467,17 @@ struct ObjectOps
     StrictSpecialIdOp   setSpecial;
     GenericAttributesOp getGenericAttributes;
     GenericAttributesOp setGenericAttributes;
     DeletePropertyOp    deleteProperty;
     DeleteElementOp     deleteElement;
     DeleteSpecialOp     deleteSpecial;
     WatchOp             watch;
     UnwatchOp           unwatch;
+    SliceOp             slice; // Optimized slice, can be null.
 
     JSNewEnumerateOp    enumerate;
     ObjectOp            thisObject;
 };
 
 #define JS_NULL_OBJECT_OPS                                                    \
     {nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr, \
      nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr, \
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -2493,16 +2493,17 @@ const Class TypedObject::class_ = {
         TypedDatum::obj_setElement,
         TypedDatum::obj_setSpecial,
         TypedDatum::obj_getGenericAttributes,
         TypedDatum::obj_setGenericAttributes,
         TypedDatum::obj_deleteProperty,
         TypedDatum::obj_deleteElement,
         TypedDatum::obj_deleteSpecial,
         nullptr, nullptr, // watch/unwatch
+        nullptr,   /* slice */
         TypedDatum::obj_enumerate,
         nullptr, /* thisObject */
     }
 };
 
 /*static*/ TypedObject *
 TypedObject::createZeroed(JSContext *cx,
                           HandleObject typeObj,
@@ -2659,16 +2660,17 @@ const Class TypedHandle::class_ = {
         TypedDatum::obj_setElement,
         TypedDatum::obj_setSpecial,
         TypedDatum::obj_getGenericAttributes,
         TypedDatum::obj_setGenericAttributes,
         TypedDatum::obj_deleteProperty,
         TypedDatum::obj_deleteElement,
         TypedDatum::obj_deleteSpecial,
         nullptr, nullptr, // watch/unwatch
+        nullptr, // slice
         TypedDatum::obj_enumerate,
         nullptr, /* thisObject */
     }
 };
 
 const JSFunctionSpec TypedHandle::handleStaticMethods[] = {
     {"move", {nullptr, nullptr}, 3, 0, "HandleMove"},
     {"get", {nullptr, nullptr}, 1, 0, "HandleGet"},
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2668,30 +2668,28 @@ js::array_concat(JSContext *cx, unsigned
     }
 
     return SetLengthProperty(cx, narr, length);
 }
 
 static bool
 array_slice(JSContext *cx, unsigned argc, Value *vp)
 {
-    uint32_t length, begin, end, slot;
-    bool hole;
-
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
+    uint32_t length;
     if (!GetLengthProperty(cx, obj, &length))
         return false;
-    begin = 0;
-    end = length;
-
+
+    uint32_t begin = 0;
+    uint32_t end = length;
     if (args.length() > 0) {
         double d;
         if (!ToInteger(cx, args[0], &d))
             return false;
         if (d < 0) {
             d += length;
             if (d < 0)
                 d = 0;
@@ -2729,23 +2727,33 @@ array_slice(JSContext *cx, unsigned argc
             uint32_t initLength = Min(numSourceElements, end - begin);
             narr->setDenseInitializedLength(initLength);
             narr->initDenseElements(0, &obj->getDenseElement(begin), initLength);
         }
         args.rval().setObject(*narr);
         return true;
     }
 
+    if (js::SliceOp op = obj->getOps()->slice) {
+        if (!op(cx, obj, begin, end, narr))
+            return false;
+
+        args.rval().setObject(*narr);
+        return true;
+    }
+
     RootedValue value(cx);
-    for (slot = begin; slot < end; slot++) {
+    for (uint32_t slot = begin; slot < end; slot++) {
+        bool hole;
         if (!JS_CHECK_OPERATION_LIMIT(cx) ||
-            !GetElement(cx, obj, slot, &hole, &value)) {
+            !GetElement(cx, obj, slot, &hole, &value))
+        {
             return false;
         }
-        if (!hole && !SetArrayElement(cx, narr, slot - begin, value))
+        if (!hole && !JSObject::defineElement(cx, narr, slot - begin, value))
             return false;
     }
 
     args.rval().setObject(*narr);
     return true;
 }
 
 /* ES5 15.4.4.20. */
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -360,16 +360,44 @@ BaseProxyHandler::watch(JSContext *cx, H
 
 bool
 BaseProxyHandler::unwatch(JSContext *cx, HandleObject proxy, HandleId id)
 {
     return true;
 }
 
 bool
+BaseProxyHandler::slice(JSContext *cx, HandleObject proxy, uint32_t begin, uint32_t end,
+                        HandleObject result)
+{
+    assertEnteredPolicy(cx, proxy, JSID_VOID);
+
+    RootedId id(cx);
+    RootedValue value(cx);
+    for (uint32_t index = begin; index < end; index++) {
+        if (!IndexToId(cx, index, id.address()))
+            return false;
+
+        bool present;
+        if (!Proxy::has(cx, proxy, id, &present))
+            return false;
+
+        if (present) {
+            if (!Proxy::get(cx, proxy, proxy, id, &value))
+                return false;
+
+            if (!JSObject::defineElement(cx, result, index - begin, value))
+                return false;
+        }
+    }
+
+    return true;
+}
+
+bool
 DirectProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                           MutableHandle<PropertyDescriptor> desc, unsigned flags)
 {
     assertEnteredPolicy(cx, proxy, id);
     JS_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return JS_GetPropertyDescriptorById(cx, target, id, 0, desc);
 }
@@ -2715,16 +2743,28 @@ Proxy::watch(JSContext *cx, JS::HandleOb
 
 /* static */ bool
 Proxy::unwatch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id)
 {
     JS_CHECK_RECURSION(cx, return false);
     return proxy->as<ProxyObject>().handler()->unwatch(cx, proxy, id);
 }
 
+/* static */ bool
+Proxy::slice(JSContext *cx, HandleObject proxy, uint32_t begin, uint32_t end,
+             HandleObject result)
+{
+    JS_CHECK_RECURSION(cx, return false);
+    BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
+    AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
+    return handler->slice(cx, proxy, begin, end, result);
+}
+
 static JSObject *
 proxy_innerObject(JSContext *cx, HandleObject obj)
 {
     return obj->as<ProxyObject>().private_().toObjectOrNull();
 }
 
 static bool
 proxy_LookupGeneric(JSContext *cx, HandleObject obj, HandleId id,
@@ -3008,27 +3048,34 @@ proxy_Construct(JSContext *cx, unsigned 
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject proxy(cx, &args.callee());
     JS_ASSERT(proxy->is<ProxyObject>());
     return Proxy::construct(cx, proxy, args);
 }
 
 static bool
-proxy_Watch(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable)
+proxy_Watch(JSContext *cx, HandleObject obj, HandleId id, HandleObject callable)
 {
     return Proxy::watch(cx, obj, id, callable);
 }
 
 static bool
-proxy_Unwatch(JSContext *cx, JS::HandleObject obj, JS::HandleId id)
+proxy_Unwatch(JSContext *cx, HandleObject obj, HandleId id)
 {
     return Proxy::unwatch(cx, obj, id);
 }
 
+static bool
+proxy_Slice(JSContext *cx, HandleObject proxy, uint32_t begin, uint32_t end,
+            HandleObject result)
+{
+    return Proxy::slice(cx, proxy, begin, end, result);
+}
+
 #define PROXY_CLASS_EXT                             \
     {                                               \
         nullptr,             /* outerObject */      \
         nullptr,             /* innerObject */      \
         nullptr,             /* iteratorObject */   \
         false,               /* isWrappedNative */  \
         proxy_WeakmapKeyDelegate                    \
     }
@@ -3071,16 +3118,17 @@ proxy_Unwatch(JSContext *cx, JS::HandleO
         proxy_SetElement,                           \
         proxy_SetSpecial,                           \
         proxy_GetGenericAttributes,                 \
         proxy_SetGenericAttributes,                 \
         proxy_DeleteProperty,                       \
         proxy_DeleteElement,                        \
         proxy_DeleteSpecial,                        \
         proxy_Watch, proxy_Unwatch,                 \
+        proxy_Slice,                                \
         nullptr,             /* enumerate       */  \
         nullptr,             /* thisObject      */  \
     }                                               \
 }
 
 const Class js::ProxyObject::uncallableClass_ = PROXY_CLASS(nullptr, nullptr);
 const Class js::ProxyObject::callableClass_ = PROXY_CLASS(proxy_Call, proxy_Construct);
 
@@ -3128,16 +3176,17 @@ const Class js::OuterWindowProxyObject::
         proxy_SetElement,
         proxy_SetSpecial,
         proxy_GetGenericAttributes,
         proxy_SetGenericAttributes,
         proxy_DeleteProperty,
         proxy_DeleteElement,
         proxy_DeleteSpecial,
         proxy_Watch, proxy_Unwatch,
+        proxy_Slice,
         nullptr,             /* enumerate       */
         nullptr,             /* thisObject      */
     }
 };
 
 const Class* const js::OuterWindowProxyClassPtr = &OuterWindowProxyObject::class_;
 
 JS_FRIEND_API(JSObject *)
--- a/js/src/jsproxy.h
+++ b/js/src/jsproxy.h
@@ -169,16 +169,19 @@ class JS_FRIEND_API(BaseProxyHandler)
     virtual bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
 
     // These two hooks must be overridden, or not overridden, in tandem -- no
     // overriding just one!
     virtual bool watch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
                        JS::HandleObject callable);
     virtual bool unwatch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id);
 
+    virtual bool slice(JSContext *cx, HandleObject proxy, uint32_t begin, uint32_t end,
+                       HandleObject result);
+
     /* See comment for weakmapKeyDelegateOp in js/Class.h. */
     virtual JSObject *weakmapKeyDelegate(JSObject *proxy);
     virtual bool isScripted() { return false; }
 };
 
 /*
  * DirectProxyHandler includes a notion of a target object. All traps are
  * reimplemented such that they forward their behavior to the target. This
@@ -287,19 +290,21 @@ class Proxy
     static bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp);
     static bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx);
     static const char *className(JSContext *cx, HandleObject proxy);
     static JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent);
     static bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g);
     static bool defaultValue(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue vp);
     static bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
 
-    static bool watch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
-                      JS::HandleObject callable);
-    static bool unwatch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id);
+    static bool watch(JSContext *cx, HandleObject proxy, HandleId id, HandleObject callable);
+    static bool unwatch(JSContext *cx, HandleObject proxy, HandleId id);
+
+    static bool slice(JSContext *cx, HandleObject obj, uint32_t begin, uint32_t end,
+                      HandleObject result);
 
     /* IC entry path for handling __noSuchMethod__ on access. */
     static bool callProp(JSContext *cx, HandleObject proxy, HandleObject reveiver, HandleId id,
                          MutableHandleValue vp);
 
     static JSObject * const LazyProto;
 };
 
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -561,17 +561,18 @@ const Class WithObject::class_ = {
         with_SetProperty,
         with_SetElement,
         with_SetSpecial,
         with_GetGenericAttributes,
         with_SetGenericAttributes,
         with_DeleteProperty,
         with_DeleteElement,
         with_DeleteSpecial,
-        nullptr, nullptr, /* watch/unwatch */
+        nullptr, nullptr,    /* watch/unwatch */
+        nullptr,             /* slice */
         with_Enumerate,
         with_ThisObject,
     }
 };
 
 /*****************************************************************************/
 
 ClonedBlockObject *
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -3455,18 +3455,19 @@ const Class ArrayBufferObject::class_ = 
         ArrayBufferObject::obj_setElement,
         ArrayBufferObject::obj_setSpecial,
         ArrayBufferObject::obj_getGenericAttributes,
         ArrayBufferObject::obj_setGenericAttributes,
         ArrayBufferObject::obj_deleteProperty,
         ArrayBufferObject::obj_deleteElement,
         ArrayBufferObject::obj_deleteSpecial,
         nullptr, nullptr, /* watch/unwatch */
+        nullptr,          /* slice */
         ArrayBufferObject::obj_enumerate,
-        nullptr,       /* thisObject      */
+        nullptr,          /* thisObject      */
     }
 };
 
 const JSFunctionSpec ArrayBufferObject::jsfuncs[] = {
     JS_FN("slice", ArrayBufferObject::fun_slice, 2, JSFUN_GENERIC_NATIVE),
     JS_FS_END
 };
 
@@ -3617,18 +3618,19 @@ IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Flo
         _typedArray##Object::obj_setElement,                                   \
         _typedArray##Object::obj_setSpecial,                                   \
         _typedArray##Object::obj_getGenericAttributes,                         \
         _typedArray##Object::obj_setGenericAttributes,                         \
         _typedArray##Object::obj_deleteProperty,                               \
         _typedArray##Object::obj_deleteElement,                                \
         _typedArray##Object::obj_deleteSpecial,                                \
         nullptr, nullptr, /* watch/unwatch */                                  \
+        nullptr,          /* slice */                                          \
         _typedArray##Object::obj_enumerate,                                    \
-        nullptr,             /* thisObject  */                                 \
+        nullptr,          /* thisObject  */                                    \
     }                                                                          \
 }
 
 template<class ArrayType>
 static inline JSObject *
 InitTypedArrayClass(JSContext *cx)
 {
     Rooted<GlobalObject*> global(cx, cx->compartment()->maybeGlobal());
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -726,16 +726,17 @@ const XPCWrappedNativeJSClass XPC_WN_NoH
         nullptr, // setElement
         nullptr, // setSpecial
         nullptr, // getGenericAttributes
         nullptr, // setGenericAttributes
         nullptr, // deleteProperty
         nullptr, // deleteElement
         nullptr, // deleteSpecial
         nullptr, nullptr, // watch/unwatch
+        nullptr, // slice
         XPC_WN_JSOp_Enumerate,
         XPC_WN_JSOp_ThisObject,
     }
   },
   0 // interfacesBitmap
 };
 
 
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -987,16 +987,17 @@ XPC_WN_JSOp_ThisObject(JSContext *cx, JS
         nullptr, /* setElement    */                                          \
         nullptr, /* setSpecial    */                                          \
         nullptr, /* getGenericAttributes  */                                  \
         nullptr, /* setGenericAttributes  */                                  \
         nullptr, /* deleteProperty */                                         \
         nullptr, /* deleteElement */                                          \
         nullptr, /* deleteSpecial */                                          \
         nullptr, nullptr, /* watch/unwatch */                                 \
+        nullptr, /* slice */                                                  \
         XPC_WN_JSOp_Enumerate,                                                \
         XPC_WN_JSOp_ThisObject,                                               \
     }
 
 #define XPC_WN_NoCall_ObjectOps                                               \
     {                                                                         \
         nullptr, /* lookupGeneric */                                          \
         nullptr, /* lookupProperty */                                         \
@@ -1015,16 +1016,17 @@ XPC_WN_JSOp_ThisObject(JSContext *cx, JS
         nullptr, /* setElement    */                                          \
         nullptr, /* setSpecial    */                                          \
         nullptr, /* getGenericAttributes  */                                  \
         nullptr, /* setGenericAttributes  */                                  \
         nullptr, /* deleteProperty */                                         \
         nullptr, /* deleteElement */                                          \
         nullptr, /* deleteSpecial */                                          \
         nullptr, nullptr, /* watch/unwatch */                                 \
+        nullptr, /* slice */                                                  \
         XPC_WN_JSOp_Enumerate,                                                \
         XPC_WN_JSOp_ThisObject,                                               \
     }
 
 // Maybe this macro should check for class->enumerate ==
 // XPC_WN_Shared_Proto_Enumerate or something rather than checking for
 // 4 classes?
 static inline bool IS_PROTO_CLASS(const js::Class *clazz)