Title: [275363] trunk
Revision
275363
Author
[email protected]
Date
2021-04-01 09:07:08 -0700 (Thu, 01 Apr 2021)

Log Message

Optimize createListFromArrayLike() and Proxy's [[OwnPropertyKeys]] method
https://bugs.webkit.org/show_bug.cgi?id=223928

Reviewed by Yusuke Suzuki.

JSTests:

* microbenchmarks/json-stringify-array-replacer.js:
Reduce running time from over 350ms to ~60ms.

* microbenchmarks/reflect-own-keys-proxy-2.js: Added.
* microbenchmarks/reflect-own-keys-proxy.js: Added.

Source/_javascript_Core:

createListFromArrayLike() changes:

  1. Use toLength() / getIndex() methods that have fast paths.
  2. Remove RuntimeTypeMask and error messages from its signature: type checks are better
     performed in advance / inside a functor to keep the helper more versatile.
  3. Invert functor's return value to align with Structure::forEachProperty() and friends.
  4. Rename it to forEachInArrayLike() as no list is actually returned.

ProxyObject::performGetOwnPropertyNames() changes:

  1. Remove RuntimeTypeMask filtering as it's already performed by PropertyNameArray::add().
  2. Store target's keys in a HashSet for faster insertion / search.
  3. Don't populate `targetConfigurableKeys` for extensible target as it won't be used [1].
  4. Leverage return value of HashSet::remove() instead of using a helper.

This patch advances Proxy's [[OwnPropertyKeys]] microbenchmarks by 20-30%,
mainly due to createListFromArrayLike() changes. No behavior changes.

Also, utilizes forEachInArrayLike() for allow list of JSON.stringify().

[1]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys (step 20)

* runtime/JSONObject.cpp:
(JSC::Stringifier::Stringifier):
* runtime/JSObject.h:
(JSC::JSObject::getIndex const):
* runtime/JSObjectInlines.h:
(JSC::forEachInArrayLike):
(JSC::createListFromArrayLike): Deleted.
* runtime/ProxyObject.cpp:
(JSC::ProxyObject::performGetOwnPropertyNames):
* runtime/ReflectObject.cpp:
(JSC::JSC_DEFINE_HOST_FUNCTION):

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (275362 => 275363)


--- trunk/JSTests/ChangeLog	2021-04-01 15:48:11 UTC (rev 275362)
+++ trunk/JSTests/ChangeLog	2021-04-01 16:07:08 UTC (rev 275363)
@@ -1,3 +1,16 @@
+2021-04-01  Alexey Shvayka  <[email protected]>
+
+        Optimize createListFromArrayLike() and Proxy's [[OwnPropertyKeys]] method
+        https://bugs.webkit.org/show_bug.cgi?id=223928
+
+        Reviewed by Yusuke Suzuki.
+
+        * microbenchmarks/json-stringify-array-replacer.js:
+        Reduce running time from over 350ms to ~60ms.
+
+        * microbenchmarks/reflect-own-keys-proxy-2.js: Added.
+        * microbenchmarks/reflect-own-keys-proxy.js: Added.
+
 2021-03-31  Mark Lam  <[email protected]>
 
         Missing exception check in HashMapImpl::add().

Modified: trunk/JSTests/microbenchmarks/json-stringify-array-replacer.js (275362 => 275363)


--- trunk/JSTests/microbenchmarks/json-stringify-array-replacer.js	2021-04-01 15:48:11 UTC (rev 275362)
+++ trunk/JSTests/microbenchmarks/json-stringify-array-replacer.js	2021-04-01 16:07:08 UTC (rev 275363)
@@ -1,3 +1,3 @@
-const replacer = new Array(15).fill('key');
-for (let i = 0; i < 1e6; ++i)
+var replacer = new Array(25).fill('key');
+for (var i = 0; i < 1e5; ++i)
     JSON.stringify(null, replacer);

Added: trunk/JSTests/microbenchmarks/reflect-own-keys-proxy-2.js (0 => 275363)


--- trunk/JSTests/microbenchmarks/reflect-own-keys-proxy-2.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/reflect-own-keys-proxy-2.js	2021-04-01 16:07:08 UTC (rev 275363)
@@ -0,0 +1,16 @@
+(function() {
+    var target = {};
+    for (var i = 0; i < 10; i++)
+        target["k" + i] = i;
+    var trapResult = Reflect.ownKeys(target);
+    var proxy = new Proxy(target, {
+        ownKeys: function() { return trapResult; },
+    });
+
+    var j = 0, lengthSum = 0;
+    for (; j < 50_000; ++j)
+        lengthSum += Reflect.ownKeys(proxy).length;
+
+    if (lengthSum !== trapResult.length * j)
+        throw "Bad assertion!";
+})();

Added: trunk/JSTests/microbenchmarks/reflect-own-keys-proxy.js (0 => 275363)


--- trunk/JSTests/microbenchmarks/reflect-own-keys-proxy.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/reflect-own-keys-proxy.js	2021-04-01 16:07:08 UTC (rev 275363)
@@ -0,0 +1,13 @@
+(function() {
+    var trapResult = [];
+    var proxy = new Proxy({}, {
+        ownKeys: function() { return trapResult; },
+    });
+
+    var j = 0, lengthSum = 0;
+    for (; j < 200_000; ++j)
+        lengthSum += Reflect.ownKeys(proxy).length;
+
+    if (lengthSum !== trapResult.length * j)
+        throw "Bad assertion!";
+})();

Modified: trunk/Source/_javascript_Core/CMakeLists.txt (275362 => 275363)


--- trunk/Source/_javascript_Core/CMakeLists.txt	2021-04-01 15:48:11 UTC (rev 275362)
+++ trunk/Source/_javascript_Core/CMakeLists.txt	2021-04-01 16:07:08 UTC (rev 275363)
@@ -933,6 +933,7 @@
     runtime/IteratorOperations.h
     runtime/IteratorPrototype.h
     runtime/JSArray.h
+    runtime/JSArrayInlines.h
     runtime/JSArrayBuffer.h
     runtime/JSArrayBufferPrototype.h
     runtime/JSArrayBufferView.h

Modified: trunk/Source/_javascript_Core/ChangeLog (275362 => 275363)


--- trunk/Source/_javascript_Core/ChangeLog	2021-04-01 15:48:11 UTC (rev 275362)
+++ trunk/Source/_javascript_Core/ChangeLog	2021-04-01 16:07:08 UTC (rev 275363)
@@ -1,3 +1,44 @@
+2021-04-01  Alexey Shvayka  <[email protected]>
+
+        Optimize createListFromArrayLike() and Proxy's [[OwnPropertyKeys]] method
+        https://bugs.webkit.org/show_bug.cgi?id=223928
+
+        Reviewed by Yusuke Suzuki.
+
+        createListFromArrayLike() changes:
+
+          1. Use toLength() / getIndex() methods that have fast paths.
+          2. Remove RuntimeTypeMask and error messages from its signature: type checks are better
+             performed in advance / inside a functor to keep the helper more versatile.
+          3. Invert functor's return value to align with Structure::forEachProperty() and friends.
+          4. Rename it to forEachInArrayLike() as no list is actually returned.
+
+        ProxyObject::performGetOwnPropertyNames() changes:
+
+          1. Remove RuntimeTypeMask filtering as it's already performed by PropertyNameArray::add().
+          2. Store target's keys in a HashSet for faster insertion / search.
+          3. Don't populate `targetConfigurableKeys` for extensible target as it won't be used [1].
+          4. Leverage return value of HashSet::remove() instead of using a helper.
+
+        This patch advances Proxy's [[OwnPropertyKeys]] microbenchmarks by 20-30%,
+        mainly due to createListFromArrayLike() changes. No behavior changes.
+
+        Also, utilizes forEachInArrayLike() for allow list of JSON.stringify().
+
+        [1]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys (step 20)
+
+        * runtime/JSONObject.cpp:
+        (JSC::Stringifier::Stringifier):
+        * runtime/JSObject.h:
+        (JSC::JSObject::getIndex const):
+        * runtime/JSObjectInlines.h:
+        (JSC::forEachInArrayLike):
+        (JSC::createListFromArrayLike): Deleted.
+        * runtime/ProxyObject.cpp:
+        (JSC::ProxyObject::performGetOwnPropertyNames):
+        * runtime/ReflectObject.cpp:
+        (JSC::JSC_DEFINE_HOST_FUNCTION):
+
 2021-03-31  David Kilzer  <[email protected]>
 
         UBSan: JSC::Parser<LexerType>::parseProperty(): runtime error: load of value nnn, which is not a valid value for type 'bool'

Modified: trunk/Source/_javascript_Core/_javascript_Core.xcodeproj/project.pbxproj (275362 => 275363)


--- trunk/Source/_javascript_Core/_javascript_Core.xcodeproj/project.pbxproj	2021-04-01 15:48:11 UTC (rev 275362)
+++ trunk/Source/_javascript_Core/_javascript_Core.xcodeproj/project.pbxproj	2021-04-01 16:07:08 UTC (rev 275363)
@@ -1098,7 +1098,7 @@
 		539BFBAE22AD3C3A0023F4C0 /* WeakObjectRefPrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = 539BFBAD22AD3C3A0023F4C0 /* WeakObjectRefPrototype.h */; };
 		539BFBB022AD3CDC0023F4C0 /* JSWeakObjectRef.h in Headers */ = {isa = PBXBuildFile; fileRef = 539BFBAF22AD3CDC0023F4C0 /* JSWeakObjectRef.h */; };
 		539DD7F523C1BBB500905F13 /* JSArrayIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = 539DD7F423C1BBA900905F13 /* JSArrayIterator.h */; settings = {ATTRIBUTES = (Private, ); }; };
-		539FB8BA1C99DA7C00940FA1 /* JSArrayInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = 539FB8B91C99DA7C00940FA1 /* JSArrayInlines.h */; };
+		539FB8BA1C99DA7C00940FA1 /* JSArrayInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = 539FB8B91C99DA7C00940FA1 /* JSArrayInlines.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		53B4BD121F68B32500D2BEA3 /* WasmOps.h in Headers */ = {isa = PBXBuildFile; fileRef = 533B15DE1DC7F463004D500A /* WasmOps.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		53B601EC2034B8C5006BE667 /* JSCast.h in Headers */ = {isa = PBXBuildFile; fileRef = 53B601EB2034B8C5006BE667 /* JSCast.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		53C2CE9C22FCC3D6008B2853 /* AirHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 53C2CE9B22FCC3D6008B2853 /* AirHelpers.h */; };

Modified: trunk/Source/_javascript_Core/runtime/JSONObject.cpp (275362 => 275363)


--- trunk/Source/_javascript_Core/runtime/JSONObject.cpp	2021-04-01 15:48:11 UTC (rev 275362)
+++ trunk/Source/_javascript_Core/runtime/JSONObject.cpp	2021-04-01 16:07:08 UTC (rev 275363)
@@ -231,28 +231,22 @@
             RETURN_IF_EXCEPTION(scope, );
             if (isArrayReplacer) {
                 m_usingArrayReplacer = true;
-                uint64_t length = static_cast<uint64_t>(toLength(globalObject, replacerObject));
-                RETURN_IF_EXCEPTION(scope, );
-                for (uint64_t index = 0; index < length; ++index) {
-                    JSValue name;
-                    if (isJSArray(replacerObject) && replacerObject->canGetIndexQuickly(index))
-                        name = replacerObject->getIndexQuickly(static_cast<uint32_t>(index));
-                    else {
-                        name = replacerObject->get(globalObject, index);
-                        RETURN_IF_EXCEPTION(scope, );
-                    }
+                forEachInArrayLike(globalObject, replacerObject, [&] (JSValue name) -> bool {
                     if (name.isObject()) {
                         auto* nameObject = jsCast<JSObject*>(name);
                         if (!nameObject->inherits<NumberObject>(vm) && !nameObject->inherits<StringObject>(vm))
-                            continue;
+                            return true;
                     } else if (!name.isNumber() && !name.isString())
-                        continue;
+                        return true;
+
                     JSString* propertyNameString = name.toString(globalObject);
-                    RETURN_IF_EXCEPTION(scope, );
+                    RETURN_IF_EXCEPTION(scope, false);
                     auto propertyName = propertyNameString->toIdentifier(globalObject);
-                    RETURN_IF_EXCEPTION(scope, );
+                    RETURN_IF_EXCEPTION(scope, false);
                     m_arrayReplacerPropertyNames.add(WTFMove(propertyName));
-                }
+                    return true;
+                });
+                RETURN_IF_EXCEPTION(scope, );
             }
         }
     }

Modified: trunk/Source/_javascript_Core/runtime/JSObject.h (275362 => 275363)


--- trunk/Source/_javascript_Core/runtime/JSObject.h	2021-04-01 15:48:11 UTC (rev 275362)
+++ trunk/Source/_javascript_Core/runtime/JSObject.h	2021-04-01 16:07:08 UTC (rev 275363)
@@ -404,7 +404,7 @@
         return JSValue();
     }
         
-    JSValue getIndex(JSGlobalObject* globalObject, unsigned i) const
+    JSValue getIndex(JSGlobalObject* globalObject, uint64_t i) const
     {
         if (JSValue result = tryGetIndexQuickly(i))
             return result;

Modified: trunk/Source/_javascript_Core/runtime/JSObjectInlines.h (275362 => 275363)


--- trunk/Source/_javascript_Core/runtime/JSObjectInlines.h	2021-04-01 15:48:11 UTC (rev 275362)
+++ trunk/Source/_javascript_Core/runtime/JSObjectInlines.h	2021-04-01 16:07:08 UTC (rev 275363)
@@ -27,6 +27,7 @@
 #include "BrandedStructure.h"
 #include "ButterflyInlines.h"
 #include "Error.h"
+#include "JSArrayInlines.h"
 #include "JSFunction.h"
 #include "JSObject.h"
 #include "JSTypedArrays.h"
@@ -43,38 +44,19 @@
     return &vm.cellSpace;
 }
 
-// Section 7.3.17 of the spec.
-template <typename AddFunction> // Add function should have a type like: (JSValue, RuntimeType) -> bool
-void createListFromArrayLike(JSGlobalObject* globalObject, JSValue arrayLikeValue, RuntimeTypeMask legalTypesFilter, const String& notAnObjectErroMessage, const String& illegalTypeErrorMessage, AddFunction addFunction)
+// https://tc39.es/ecma262/#sec-createlistfromarraylike
+template <typename Functor> // A functor should have a type like: (JSValue) -> bool
+void forEachInArrayLike(JSGlobalObject* globalObject, JSObject* arrayLikeObject, Functor functor)
 {
     VM& vm = getVM(globalObject);
     auto scope = DECLARE_THROW_SCOPE(vm);
-
-    if (!arrayLikeValue.isObject()) {
-        throwTypeError(globalObject, scope, notAnObjectErroMessage);
-        return;
-    }
-    
-    Vector<JSValue> result;
-    JSValue lengthProperty = arrayLikeValue.get(globalObject, vm.propertyNames->length);
+    uint64_t length = static_cast<uint64_t>(toLength(globalObject, arrayLikeObject));
     RETURN_IF_EXCEPTION(scope, void());
-    double lengthAsDouble = lengthProperty.toLength(globalObject);
-    RETURN_IF_EXCEPTION(scope, void());
-    RELEASE_ASSERT(lengthAsDouble >= 0.0 && lengthAsDouble == std::trunc(lengthAsDouble));
-    uint64_t length = static_cast<uint64_t>(lengthAsDouble);
     for (uint64_t index = 0; index < length; index++) {
-        JSValue next = arrayLikeValue.get(globalObject, index);
+        JSValue value = arrayLikeObject->getIndex(globalObject, index);
         RETURN_IF_EXCEPTION(scope, void());
-        
-        RuntimeType type = runtimeTypeForValue(vm, next);
-        if (!(type & legalTypesFilter)) {
-            throwTypeError(globalObject, scope, illegalTypeErrorMessage);
+        if (!functor(value))
             return;
-        }
-        
-        bool exitEarly = addFunction(next, type);
-        if (exitEarly)
-            return;
     }
 }
 

Modified: trunk/Source/_javascript_Core/runtime/ProxyObject.cpp (275362 => 275363)


--- trunk/Source/_javascript_Core/runtime/ProxyObject.cpp	2021-04-01 15:48:11 UTC (rev 275362)
+++ trunk/Source/_javascript_Core/runtime/ProxyObject.cpp	2021-04-01 16:07:08 UTC (rev 275363)
@@ -923,45 +923,31 @@
     JSValue trapResult = call(globalObject, ownKeysMethod, callData, handler, arguments);
     RETURN_IF_EXCEPTION(scope, void());
 
+    if (!trapResult.isObject()) {
+        throwTypeError(globalObject, scope, "Proxy handler's 'ownKeys' method must return an object"_s);
+        return;
+    }
+
     HashSet<UniquedStringImpl*> uncheckedResultKeys;
-    {
-        RuntimeTypeMask resultFilter = 0;
-        switch (propertyNames.propertyNameMode()) {
-        case PropertyNameMode::Symbols:
-            resultFilter = TypeSymbol;
-            break;
-        case PropertyNameMode::Strings:
-            resultFilter = TypeString;
-            break;
-        case PropertyNameMode::StringsAndSymbols:
-            resultFilter = TypeSymbol | TypeString;
-            break;
+    forEachInArrayLike(globalObject, asObject(trapResult), [&] (JSValue value) -> bool {
+        if (!value.isString() && !value.isSymbol()) {
+            throwTypeError(globalObject, scope, "Proxy handler's 'ownKeys' method must return an array-like object containing only Strings and Symbols"_s);
+            return false;
         }
-        ASSERT(resultFilter);
 
-        auto addPropName = [&] (JSValue value, RuntimeType type) -> bool {
-            static constexpr bool doExitEarly = true;
-            static constexpr bool dontExitEarly = false;
+        Identifier ident = value.toPropertyKey(globalObject);
+        RETURN_IF_EXCEPTION(scope, false);
 
-            Identifier ident = value.toPropertyKey(globalObject);
-            RETURN_IF_EXCEPTION(scope, doExitEarly);
+        if (!uncheckedResultKeys.add(ident.impl()).isNewEntry) {
+            throwTypeError(globalObject, scope, "Proxy handler's 'ownKeys' trap result must not contain any duplicate names"_s);
+            return false;
+        }
 
-            if (!uncheckedResultKeys.add(ident.impl()).isNewEntry) {
-                throwTypeError(globalObject, scope, "Proxy handler's 'ownKeys' trap result must not contain any duplicate names"_s);
-                return doExitEarly;
-            }
+        propertyNames.add(ident);
+        return true;
+    });
+    RETURN_IF_EXCEPTION(scope, void());
 
-            if (type & resultFilter)
-                propertyNames.add(ident.impl());
-
-            return dontExitEarly;
-        };
-
-        RuntimeTypeMask dontThrowAnExceptionTypeFilter = TypeString | TypeSymbol;
-        createListFromArrayLike(globalObject, trapResult, dontThrowAnExceptionTypeFilter, "Proxy handler's 'ownKeys' method must return an object"_s, "Proxy handler's 'ownKeys' method must return an array-like object containing only Strings and Symbols"_s, addPropName);
-        RETURN_IF_EXCEPTION(scope, void());
-    }
-
     bool targetIsExensible = target->isExtensible(globalObject);
     RETURN_IF_EXCEPTION(scope, void());
 
@@ -968,30 +954,20 @@
     PropertyNameArray targetKeys(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude);
     target->methodTable(vm)->getOwnPropertyNames(target, globalObject, targetKeys, DontEnumPropertiesMode::Include);
     RETURN_IF_EXCEPTION(scope, void());
-    Vector<UniquedStringImpl*> targetConfigurableKeys;
-    Vector<UniquedStringImpl*> targetNonConfigurableKeys;
+    HashSet<UniquedStringImpl*> targetNonConfigurableKeys;
+    HashSet<UniquedStringImpl*> targetConfigurableKeys;
     for (const Identifier& ident : targetKeys) {
         PropertyDescriptor descriptor;
         bool isPropertyDefined = target->getOwnPropertyDescriptor(globalObject, ident.impl(), descriptor); 
         RETURN_IF_EXCEPTION(scope, void());
         if (isPropertyDefined && !descriptor.configurable())
-            targetNonConfigurableKeys.append(ident.impl());
-        else
-            targetConfigurableKeys.append(ident.impl());
+            targetNonConfigurableKeys.add(ident.impl());
+        else if (!targetIsExensible)
+            targetConfigurableKeys.add(ident.impl());
     }
 
-    enum ContainedIn { IsContainedIn, IsNotContainedIn };
-    auto removeIfContainedInUncheckedResultKeys = [&] (UniquedStringImpl* impl) -> ContainedIn {
-        auto iter = uncheckedResultKeys.find(impl);
-        if (iter == uncheckedResultKeys.end())
-            return IsNotContainedIn;
-
-        uncheckedResultKeys.remove(iter);
-        return IsContainedIn;
-    };
-
     for (UniquedStringImpl* impl : targetNonConfigurableKeys) {
-        if (removeIfContainedInUncheckedResultKeys(impl) == IsNotContainedIn) {
+        if (!uncheckedResultKeys.remove(impl)) {
             throwTypeError(globalObject, scope, makeString("Proxy object's 'target' has the non-configurable property '", String(impl), "' that was not in the result from the 'ownKeys' trap"));
             return;
         }
@@ -999,7 +975,7 @@
 
     if (!targetIsExensible) {
         for (UniquedStringImpl* impl : targetConfigurableKeys) {
-            if (removeIfContainedInUncheckedResultKeys(impl) == IsNotContainedIn) {
+            if (!uncheckedResultKeys.remove(impl)) {
                 throwTypeError(globalObject, scope, makeString("Proxy object's non-extensible 'target' has configurable property '", String(impl), "' that was not in the result from the 'ownKeys' trap"));
                 return;
             }

Modified: trunk/Source/_javascript_Core/runtime/ReflectObject.cpp (275362 => 275363)


--- trunk/Source/_javascript_Core/runtime/ReflectObject.cpp	2021-04-01 15:48:11 UTC (rev 275362)
+++ trunk/Source/_javascript_Core/runtime/ReflectObject.cpp	2021-04-01 16:07:08 UTC (rev 275363)
@@ -110,9 +110,9 @@
     if (!argumentsObject)
         return JSValue::encode(throwTypeError(globalObject, scope, "Reflect.construct requires the second argument be an object"_s));
 
-    createListFromArrayLike(globalObject, argumentsObject, RuntimeTypeMaskAllTypes, "This error must not be raised"_s, "This error must not be raised"_s, [&] (JSValue value, RuntimeType) -> bool {
+    forEachInArrayLike(globalObject, argumentsObject, [&] (JSValue value) -> bool {
         arguments.append(value);
-        return false;
+        return true;
     });
     RETURN_IF_EXCEPTION(scope, (arguments.overflowCheckNotNeeded(), encodedJSValue()));
     if (UNLIKELY(arguments.hasOverflowed())) {
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to