[JSC] Optimize Promise runtime functions
authorysuzuki@apple.com <ysuzuki@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 28 Oct 2019 21:13:57 +0000 (21:13 +0000)
committerysuzuki@apple.com <ysuzuki@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 28 Oct 2019 21:13:57 +0000 (21:13 +0000)
https://bugs.webkit.org/show_bug.cgi?id=203454

Reviewed by Keith Miller.

JSTests:

* microbenchmarks/promise-reject.js: Added.
* microbenchmarks/promise-resolve.js: Added.

Source/JavaScriptCore:

This patch optimizes Promise runtime functions a bit.

1. Add fast paths to Promise.resolve / Promise.reject.
2. Remove state check in async-functions. Unlike generators, async-function's next function is not exposed to users.
   It is called by runtime so we can control state perfectly.
3. Add "enqueueJob" name to make sampling profiler work for this function.
4. Make Promise/InternalPromise constructor inlinable size

                                      ToT                     Patched

    promise-creation-many       25.5794+-0.3681     ^     22.5410+-0.3229        ^ definitely 1.1348x faster
    promise-resolve             32.3793+-0.4252     ^      9.4219+-0.1114        ^ definitely 3.4366x faster
    promise-reject             108.5968+-0.7741     ^     36.9383+-0.3770        ^ definitely 2.9400x faster

* builtins/AsyncFunctionPrototype.js:
(globalPrivate.asyncFunctionResume):
* builtins/PromiseConstructor.js:
(reject):
(resolve):
(nakedConstructor.Promise.reject):
(nakedConstructor.Promise):
(nakedConstructor.InternalPromise.reject):
(nakedConstructor.InternalPromise):
(nakedConstructor.Promise.resolve): Deleted.
(nakedConstructor.InternalPromise.resolve): Deleted.
* builtins/PromiseOperations.js:
(globalPrivate.newPromiseCapability.resolve):
(globalPrivate.newPromiseCapability.reject):
(globalPrivate.newPromiseCapability):
(globalPrivate.promiseResolveSlow):
(globalPrivate.promiseRejectSlow):
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init):

LayoutTests:

* inspector/console/message-stack-trace-expected.txt:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@251671 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/microbenchmarks/promise-reject.js [new file with mode: 0644]
JSTests/microbenchmarks/promise-resolve.js [new file with mode: 0644]
LayoutTests/ChangeLog
LayoutTests/inspector/console/message-stack-trace-expected.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/builtins/AsyncFunctionPrototype.js
Source/JavaScriptCore/builtins/PromiseConstructor.js
Source/JavaScriptCore/builtins/PromiseOperations.js
Source/JavaScriptCore/runtime/JSGlobalObject.cpp

index 670cc5c..6acd4a2 100644 (file)
@@ -1,3 +1,13 @@
+2019-10-28  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] Optimize Promise runtime functions
+        https://bugs.webkit.org/show_bug.cgi?id=203454
+
+        Reviewed by Keith Miller.
+
+        * microbenchmarks/promise-reject.js: Added.
+        * microbenchmarks/promise-resolve.js: Added.
+
 2019-10-25  Ross Kirsling  <ross.kirsling@sony.com>
 
         test262-runner should be able to pass JSC a feature flag
diff --git a/JSTests/microbenchmarks/promise-reject.js b/JSTests/microbenchmarks/promise-reject.js
new file mode 100644 (file)
index 0000000..4f37981
--- /dev/null
@@ -0,0 +1,2 @@
+for (var i = 0; i < 1e6; ++i)
+    Promise.reject(42);
diff --git a/JSTests/microbenchmarks/promise-resolve.js b/JSTests/microbenchmarks/promise-resolve.js
new file mode 100644 (file)
index 0000000..f3e887c
--- /dev/null
@@ -0,0 +1,2 @@
+for (var i = 0; i < 1e6; ++i)
+    Promise.resolve(42);
index f67a513..7ab425e 100644 (file)
@@ -1,3 +1,12 @@
+2019-10-28  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] Optimize Promise runtime functions
+        https://bugs.webkit.org/show_bug.cgi?id=203454
+
+        Reviewed by Keith Miller.
+
+        * inspector/console/message-stack-trace-expected.txt:
+
 2019-10-28  Truitt Savell  <tsavell@apple.com>
 
         Two imported tests from r251591 are failing
index 5bad95a..90a4423 100644 (file)
@@ -27,8 +27,9 @@ CALL STACK:
 CALL STACK:
 0: [N] (anonymous function)
 1: [N] rejectPromise
-2: [N] reject
-3: [F] triggerUnhandledRejectionPromiseReject
+2: [N] rejectPromiseWithFirstResolvingFunctionCallCheck
+3: [N] reject
+4: [F] triggerUnhandledRejectionPromiseReject
 
 -- Running test case: Console.StackTrace.UnhandledPromiseRejection.ExplicitReject
 CALL STACK:
index 349ebf2..95110f6 100644 (file)
@@ -1,5 +1,46 @@
 2019-10-28  Yusuke Suzuki  <ysuzuki@apple.com>
 
+        [JSC] Optimize Promise runtime functions
+        https://bugs.webkit.org/show_bug.cgi?id=203454
+
+        Reviewed by Keith Miller.
+
+        This patch optimizes Promise runtime functions a bit.
+
+        1. Add fast paths to Promise.resolve / Promise.reject.
+        2. Remove state check in async-functions. Unlike generators, async-function's next function is not exposed to users.
+           It is called by runtime so we can control state perfectly.
+        3. Add "enqueueJob" name to make sampling profiler work for this function.
+        4. Make Promise/InternalPromise constructor inlinable size
+
+                                              ToT                     Patched
+
+            promise-creation-many       25.5794+-0.3681     ^     22.5410+-0.3229        ^ definitely 1.1348x faster
+            promise-resolve             32.3793+-0.4252     ^      9.4219+-0.1114        ^ definitely 3.4366x faster
+            promise-reject             108.5968+-0.7741     ^     36.9383+-0.3770        ^ definitely 2.9400x faster
+
+        * builtins/AsyncFunctionPrototype.js:
+        (globalPrivate.asyncFunctionResume):
+        * builtins/PromiseConstructor.js:
+        (reject):
+        (resolve):
+        (nakedConstructor.Promise.reject):
+        (nakedConstructor.Promise):
+        (nakedConstructor.InternalPromise.reject):
+        (nakedConstructor.InternalPromise):
+        (nakedConstructor.Promise.resolve): Deleted.
+        (nakedConstructor.InternalPromise.resolve): Deleted.
+        * builtins/PromiseOperations.js:
+        (globalPrivate.newPromiseCapability.resolve):
+        (globalPrivate.newPromiseCapability.reject):
+        (globalPrivate.newPromiseCapability):
+        (globalPrivate.promiseResolveSlow):
+        (globalPrivate.promiseRejectSlow):
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::init):
+
+2019-10-28  Yusuke Suzuki  <ysuzuki@apple.com>
+
         [JSC] Use FTLOutput::callWithoutSideEffects if operation does not have side effects
         https://bugs.webkit.org/show_bug.cgi?id=203485
 
index 968cf1e..16b0082 100644 (file)
@@ -34,26 +34,23 @@ function asyncFunctionResume(generator, promise, sentValue, resumeMode)
     var state = @getGeneratorInternalField(generator, @generatorFieldState);
     var value = @undefined;
 
-    if (state === @GeneratorStateCompleted || (resumeMode !== @GeneratorResumeModeNormal && resumeMode !== @GeneratorResumeModeThrow))
-        @throwTypeError("Async function illegally resumed");
-
     try {
         @putGeneratorInternalField(generator, @generatorFieldState, @GeneratorStateExecuting);
         value = @getGeneratorInternalField(generator, @generatorFieldNext).@call(@getGeneratorInternalField(generator, @generatorFieldThis), generator, state, sentValue, resumeMode, @getGeneratorInternalField(generator, @generatorFieldFrame));
         if (@getGeneratorInternalField(generator, @generatorFieldState) === @GeneratorStateExecuting) {
-            @putGeneratorInternalField(generator, @generatorFieldState, @GeneratorStateCompleted);
             @resolvePromiseWithFirstResolvingFunctionCallCheck(promise, value);
             return promise;
         }
     } catch (error) {
-        @putGeneratorInternalField(generator, @generatorFieldState, @GeneratorStateCompleted);
         @rejectPromiseWithFirstResolvingFunctionCallCheck(promise, error);
         return promise;
     }
 
+    var capturedGenerator = generator;
+    var capturedPromise = promise;
     @resolveWithoutPromise(value,
-        function(value) { @asyncFunctionResume(generator, promise, value, @GeneratorResumeModeNormal); },
-        function(error) { @asyncFunctionResume(generator, promise, error, @GeneratorResumeModeThrow); });
+        function(value) { @asyncFunctionResume(capturedGenerator, capturedPromise, value, @GeneratorResumeModeNormal); },
+        function(error) { @asyncFunctionResume(capturedGenerator, capturedPromise, error, @GeneratorResumeModeThrow); });
 
     return promise;
 }
index e386b4a..30fc66e 100644 (file)
@@ -195,11 +195,13 @@ function reject(reason)
     if (!@isObject(this))
         @throwTypeError("|this| is not an object");
 
-    var promiseCapability = @newPromiseCapability(this);
-
-    promiseCapability.@reject.@call(@undefined, reason);
+    if (this === @Promise) {
+        var promise = @newPromise();
+        @rejectPromiseWithFirstResolvingFunctionCallCheck(promise, reason);
+        return promise;
+    }
 
-    return promiseCapability.@promise;
+    return @promiseRejectSlow(this, reason);
 }
 
 function resolve(value)
@@ -215,11 +217,13 @@ function resolve(value)
             return value;
     }
 
-    var promiseCapability = @newPromiseCapability(this);
-
-    promiseCapability.@resolve.@call(@undefined, value);
+    if (this === @Promise) {
+        var promise = @newPromise();
+        @resolvePromiseWithFirstResolvingFunctionCallCheck(promise, value);
+        return promise;
+    }
 
-    return promiseCapability.@promise;
+    return @promiseResolveSlow(this, value);
 }
 
 @nakedConstructor
@@ -230,19 +234,20 @@ function Promise(executor)
     if (typeof executor !== "function")
         @throwTypeError("Promise constructor takes a function argument");
 
-    var promise = @createPromise(new.target, /* isInternalPromise */ false);
+    var promise = @createPromise(this, /* isInternalPromise */ false);
     var capturedPromise = promise;
 
-    function @resolve(resolution) {
-        return @resolvePromiseWithFirstResolvingFunctionCallCheck(capturedPromise, resolution);
-    }
-
-    function @reject(reason) {
+    // FIXME: We should allow using function-declaration here.
+    // https://bugs.webkit.org/show_bug.cgi?id=203502
+    var @reject = function @reject(reason) {
         return @rejectPromiseWithFirstResolvingFunctionCallCheck(capturedPromise, reason);
-    }
+    };
 
     try {
-        executor(@resolve, @reject);
+        executor(
+            function @resolve(resolution) {
+                return @resolvePromiseWithFirstResolvingFunctionCallCheck(capturedPromise, resolution);
+            }, @reject);
     } catch (error) {
         @reject(error);
     }
@@ -258,19 +263,20 @@ function InternalPromise(executor)
     if (typeof executor !== "function")
         @throwTypeError("InternalPromise constructor takes a function argument");
 
-    var promise = @createPromise(new.target, /* isInternalPromise */ true);
+    var promise = @createPromise(this, /* isInternalPromise */ true);
     var capturedPromise = promise;
 
-    function @resolve(resolution) {
-        return @resolvePromiseWithFirstResolvingFunctionCallCheck(capturedPromise, resolution);
-    }
-
-    function @reject(reason) {
+    // FIXME: We should allow using function-declaration here.
+    // https://bugs.webkit.org/show_bug.cgi?id=203502
+    var @reject = function @reject(reason) {
         return @rejectPromiseWithFirstResolvingFunctionCallCheck(capturedPromise, reason);
-    }
+    };
 
     try {
-        executor(@resolve, @reject);
+        executor(
+            function @resolve(resolution) {
+                return @resolvePromiseWithFirstResolvingFunctionCallCheck(capturedPromise, resolution);
+            }, @reject);
     } catch (error) {
         @reject(error);
     }
index 2477e62..a32fd00 100644 (file)
@@ -82,15 +82,38 @@ function newPromiseCapability(constructor)
 
     if (constructor === @Promise) {
         var promise = @newPromise();
-        var resolvingFunctions = @createResolvingFunctions(promise);
-        @putByIdDirectPrivate(resolvingFunctions, "promise", promise);
-        return resolvingFunctions;
+        var capturedPromise = promise;
+        function @resolve(resolution) {
+            return @resolvePromiseWithFirstResolvingFunctionCallCheck(capturedPromise, resolution);
+        }
+        function @reject(reason) {
+            return @rejectPromiseWithFirstResolvingFunctionCallCheck(capturedPromise, reason);
+        }
+        return { @resolve, @reject, @promise: promise };
     }
 
     return @newPromiseCapabilitySlow(constructor);
 }
 
 @globalPrivate
+function promiseResolveSlow(constructor, value)
+{
+    @assert(constructor !== @Promise);
+    var promiseCapability = @newPromiseCapabilitySlow(constructor);
+    promiseCapability.@resolve.@call(@undefined, value);
+    return promiseCapability.@promise;
+}
+
+@globalPrivate
+function promiseRejectSlow(constructor, reason)
+{
+    @assert(constructor !== @Promise);
+    var promiseCapability = @newPromiseCapabilitySlow(constructor);
+    promiseCapability.@reject.@call(@undefined, reason);
+    return promiseCapability.@promise;
+}
+
+@globalPrivate
 function newHandledRejectedPromise(error)
 {
     "use strict";
index 0e64a3e..3587aa6 100644 (file)
@@ -990,7 +990,7 @@ capitalName ## Constructor* lowerName ## Constructor = featureFlag ? capitalName
         GlobalPropertyInfo(vm.propertyNames->builtinNames().propertyIsEnumerablePrivateName(), privateFuncPropertyIsEnumerable, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->builtinNames().ownKeysPrivateName(), privateFuncOwnKeys, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->builtinNames().importModulePrivateName(), privateFuncImportModule, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
-        GlobalPropertyInfo(vm.propertyNames->builtinNames().enqueueJobPrivateName(), JSFunction::create(vm, this, 0, String(), enqueueJob), PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
+        GlobalPropertyInfo(vm.propertyNames->builtinNames().enqueueJobPrivateName(), JSFunction::create(vm, this, 0, "enqueueJob"_s, enqueueJob), PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->builtinNames().makeTypeErrorPrivateName(), privateFuncMakeTypeError, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->builtinNames().typedArrayLengthPrivateName(), privateFuncTypedArrayLength, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->builtinNames().typedArrayGetOriginalConstructorPrivateName(), privateFuncTypedArrayGetOriginalConstructor, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),