Bug 1232639 - Implement Object.{values,entries} in C++ to avoid native call overhead in tight loop. r=jorendorff
authorTill Schneidereit <till@tillschneidereit.net>
Sun, 06 Mar 2016 21:12:39 +0100
changeset 286993 6eaaef0677d4000359a26445aec25016273a2d46
parent 286992 c94c22d14ce7181dcc4af8033e9b5f8c14750e7d
child 286994 fe2ad6c8ba91a85463c753d17478e2fc5563a344
push id18032
push usercbook@mozilla.com
push dateMon, 07 Mar 2016 10:38:51 +0000
treeherderfx-team@087905ffec78 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1232639
milestone47.0a1
Bug 1232639 - Implement Object.{values,entries} in C++ to avoid native call overhead in tight loop. r=jorendorff
js/src/builtin/Object.cpp
js/src/builtin/Object.js
js/src/jsarray.cpp
js/src/jsarray.h
js/src/jsiter.cpp
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -673,24 +673,144 @@ js::obj_getOwnPropertyDescriptor(JSConte
         return false;
 
     // Steps 5-7.
     Rooted<PropertyDescriptor> desc(cx);
     return GetOwnPropertyDescriptor(cx, obj, id, &desc) &&
            FromPropertyDescriptor(cx, desc, args.rval());
 }
 
-// ES6 draft rev27 (2014/08/24) 19.1.2.14 Object.keys(O)
+enum EnumerableOwnPropertiesKind {
+    Keys,
+    Values,
+    KeysAndValues
+};
+
+// ES7 proposal 2015-12-14
+// http://tc39.github.io/proposal-object-values-entries/#EnumerableOwnProperties
+static bool
+EnumerableOwnProperties(JSContext* cx, const JS::CallArgs& args, EnumerableOwnPropertiesKind kind)
+{
+    // Step 1. (Step 1 of Object.{keys,values,entries}, really.)
+    RootedObject obj(cx, ToObject(cx, args.get(0)));
+    if (!obj)
+        return false;
+
+    // Step 2.
+    AutoIdVector ids(cx);
+    if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &ids))
+        return false;
+
+    // Step 3.
+    AutoValueVector properties(cx);
+    size_t len = ids.length();
+    if (!properties.resize(len))
+        return false;
+
+    RootedId id(cx);
+    RootedValue key(cx);
+    RootedValue value(cx);
+    RootedNativeObject nobj(cx);
+    if (obj->is<NativeObject>())
+        nobj = &obj->as<NativeObject>();
+    RootedShape shape(cx);
+    Rooted<PropertyDescriptor> desc(cx);
+
+    // Step 4.
+    size_t out = 0;
+    for (size_t i = 0; i < len; i++) {
+        id = ids[i];
+
+        // Step 4.a. (Symbols were filtered out in step 2.)
+        MOZ_ASSERT(!JSID_IS_SYMBOL(id));
+
+        if (kind != Values) {
+            if (!IdToStringOrSymbol(cx, id, &key))
+                return false;
+        }
+
+        // Step 4.a.i.
+        if (nobj) {
+            if (JSID_IS_INT(id) && nobj->containsDenseElement(JSID_TO_INT(id))) {
+                value = nobj->getDenseOrTypedArrayElement(JSID_TO_INT(id));
+            } else {
+                shape = nobj->lookup(cx, id);
+                if (!shape || !(GetShapeAttributes(nobj, shape) & JSPROP_ENUMERATE))
+                    continue;
+                if (!shape->isAccessorShape()) {
+                    if (!NativeGetExistingProperty(cx, nobj, nobj, shape, &value))
+                        return false;
+                } else if (!GetProperty(cx, obj, obj, id, &value)) {
+                    return false;
+                }
+            }
+        } else {
+            if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
+                return false;
+
+            // Step 4.a.ii. (inverted.)
+            if (!desc.object() || !desc.enumerable())
+                continue;
+
+            // Step 4.a.ii.1.
+            // (Omitted because Object.keys doesn't use this implementation.)
+
+            // Step 4.a.ii.2.a.
+            if (obj->isNative() && desc.hasValue())
+                value = desc.value();
+            else if (!GetProperty(cx, obj, obj, id, &value))
+                return false;
+        }
+
+        // Steps 4.a.ii.2.b-c.
+        if (kind == Values)
+            properties[out++].set(value);
+        else if (!NewValuePair(cx, key, value, properties[out++]))
+            return false;
+    }
+
+    // Step 5.
+    // (Implemented in step 2.)
+
+    // Step 3 of Object.{keys,values,entries}
+    JSObject* aobj = NewDenseCopiedArray(cx, out, properties.begin());
+    if (!aobj)
+        return false;
+
+    args.rval().setObject(*aobj);
+    return true;
+}
+
+// ES7 proposal 2015-12-14
+// http://tc39.github.io/proposal-object-values-entries/#Object.keys
 static bool
 obj_keys(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY);
 }
 
+// ES7 proposal 2015-12-14
+// http://tc39.github.io/proposal-object-values-entries/#Object.values
+static bool
+obj_values(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return EnumerableOwnProperties(cx, args, Values);
+}
+
+// ES7 proposal 2015-12-14
+// http://tc39.github.io/proposal-object-values-entries/#Object.entries
+static bool
+obj_entries(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return EnumerableOwnProperties(cx, args, KeysAndValues);
+}
+
 /* ES6 draft 15.2.3.16 */
 static bool
 obj_is(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     bool same;
     if (!SameValue(cx, args.get(0), args.get(1), &same))
@@ -1002,20 +1122,18 @@ static const JSPropertySpec object_prope
 };
 
 static const JSFunctionSpec object_static_methods[] = {
     JS_SELF_HOSTED_FN("assign",        "ObjectStaticAssign",        2, JSPROP_DEFINE_LATE),
     JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf",     1, JSPROP_DEFINE_LATE),
     JS_FN("setPrototypeOf",            obj_setPrototypeOf,          2, 0),
     JS_FN("getOwnPropertyDescriptor",  obj_getOwnPropertyDescriptor,2, 0),
     JS_FN("keys",                      obj_keys,                    1, 0),
-#ifndef RELEASE_BUILD
-    JS_SELF_HOSTED_FN("values",        "ObjectValues",              1, JSPROP_DEFINE_LATE),
-    JS_SELF_HOSTED_FN("entries",       "ObjectEntries",             1, JSPROP_DEFINE_LATE),
-#endif
+    JS_FN("values",                    obj_values,                  1, 0),
+    JS_FN("entries",                   obj_entries,                 1, 0),
     JS_FN("is",                        obj_is,                      2, 0),
     JS_FN("defineProperty",            obj_defineProperty,          3, 0),
     JS_FN("defineProperties",          obj_defineProperties,        2, 0),
     JS_INLINABLE_FN("create",          obj_create,                  2, 0, ObjectCreate),
     JS_FN("getOwnPropertyNames",       obj_getOwnPropertyNames,     1, 0),
     JS_FN("getOwnPropertySymbols",     obj_getOwnPropertySymbols,   1, 0),
     JS_SELF_HOSTED_FN("isExtensible",  "ObjectIsExtensible",        1, JSPROP_DEFINE_LATE),
     JS_FN("preventExtensions",         obj_preventExtensions,       1, 0),
--- a/js/src/builtin/Object.js
+++ b/js/src/builtin/Object.js
@@ -134,54 +134,8 @@ function ObjectLookupGetter(name) {
         if (desc) {
             if (callFunction(std_Object_hasOwnProperty, desc, "get"))
                 return desc.get;
             return undefined;
         }
         object = std_Reflect_getPrototypeOf(object);
     } while (object !== null);
 }
-
-// Draft proposal http://tc39.github.io/proposal-object-values-entries/#Object.values
-function ObjectValues(O) {
-    // Steps 1-2.
-    var object = ToObject(O);
-
-    // Steps 3-4.
-    // EnumerableOwnProperties is inlined here.
-    var keys = OwnPropertyKeys(object, JSITER_OWNONLY | JSITER_HIDDEN);
-    var values = [];
-    var valuesCount = 0;
-    for (var i = 0; i < keys.length; i++) {
-        var key = keys[i];
-        if (!callFunction(std_Object_propertyIsEnumerable, object, key))
-            continue;
-
-        var value = object[key];
-        _DefineDataProperty(values, valuesCount++, value);
-    }
-
-    // Step 5.
-    return values;
-}
-
-// Draft proposal http://tc39.github.io/proposal-object-values-entries/#Object.entries
-function ObjectEntries(O) {
-    // Steps 1-2.
-    var object = ToObject(O);
-
-    // Steps 3-4.
-    // EnumerableOwnProperties is inlined here.
-    var keys = OwnPropertyKeys(object, JSITER_OWNONLY | JSITER_HIDDEN);
-    var entries = [];
-    var entriesCount = 0;
-    for (var i = 0; i < keys.length; i++) {
-        var key = keys[i];
-        if (!callFunction(std_Object_propertyIsEnumerable, object, key))
-            continue;
-
-        var value = object[key];
-        _DefineDataProperty(entries, entriesCount++, [key, value]);
-    }
-
-    // Step 5.
-    return entries;
-}
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3674,16 +3674,30 @@ js::NewCopiedArrayForCallingAllocationSi
                                            HandleObject proto /* = nullptr */)
 {
     RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array, proto));
     if (!group)
         return nullptr;
     return NewCopiedArrayTryUseGroup(cx, group, vp, length);
 }
 
+bool
+js::NewValuePair(JSContext* cx, const Value& val1, const Value& val2, MutableHandleValue rval)
+{
+    JS::AutoValueArray<2> vec(cx);
+    vec[0].set(val1);
+    vec[1].set(val2);
+
+    JSObject* aobj = js::NewDenseCopiedArray(cx, 2, vec.begin());
+    if (!aobj)
+        return false;
+    rval.setObject(*aobj);
+    return true;
+}
+
 #ifdef DEBUG
 bool
 js::ArrayInfo(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     JSObject* obj;
 
     for (unsigned i = 0; i < args.length(); i++) {
--- a/js/src/jsarray.h
+++ b/js/src/jsarray.h
@@ -114,16 +114,19 @@ NewCopiedArrayTryUseGroup(ExclusiveConte
                           const Value* vp, size_t length,
                           NewObjectKind newKind = GenericObject,
                           ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
 
 extern JSObject*
 NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length,
                                        HandleObject proto = nullptr);
 
+extern bool
+NewValuePair(JSContext* cx, const Value& val1, const Value& val2, MutableHandleValue rval);
+
 /*
  * Determines whether a write to the given element on |obj| should fail because
  * |obj| is an Array with a non-writable length, and writing that element would
  * increase the length of the array.
  */
 extern bool
 WouldDefinePastNonwritableLength(HandleNativeObject obj, uint32_t index);
 
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -79,25 +79,17 @@ struct IdHashPolicy {
     }
 };
 
 typedef HashSet<jsid, IdHashPolicy> IdSet;
 
 static inline bool
 NewKeyValuePair(JSContext* cx, jsid id, const Value& val, MutableHandleValue rval)
 {
-    JS::AutoValueArray<2> vec(cx);
-    vec[0].set(IdToValue(id));
-    vec[1].set(val);
-
-    JSObject* aobj = NewDenseCopiedArray(cx, 2, vec.begin());
-    if (!aobj)
-        return false;
-    rval.setObject(*aobj);
-    return true;
+    return NewValuePair(cx, IdToValue(id), val, rval);
 }
 
 static inline bool
 Enumerate(JSContext* cx, HandleObject pobj, jsid id,
           bool enumerable, unsigned flags, Maybe<IdSet>& ht, AutoIdVector* props)
 {
     // Allow duplicate properties from Proxy's [[OwnPropertyKeys]].
     bool proxyOwnProperty = pobj->is<ProxyObject>() && (flags & JSITER_OWNONLY);