[JSC] Optimize Promise runtime functions
[WebKit-https.git] / Source / JavaScriptCore / builtins / PromiseOperations.js
1 /*
2  * Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>.
3  * Copyright (C) 2016-2019 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 // @internal
28
29 @globalPrivate
30 function newPromiseReaction(promiseOrCapability, onFulfilled, onRejected)
31 {
32     "use strict";
33
34     return {
35         @promiseOrCapability: promiseOrCapability,
36         @onFulfilled: onFulfilled,
37         @onRejected: onRejected,
38         @next: @undefined,
39     };
40 }
41
42 @globalPrivate
43 function newPromiseCapabilitySlow(constructor)
44 {
45     var promiseCapability = {
46         @resolve: @undefined,
47         @reject: @undefined,
48         @promise: @undefined,
49     };
50
51     if (!@isConstructor(constructor))
52         @throwTypeError("promise capability requires a constructor function");
53
54     function @executor(resolve, reject)
55     {
56         if (promiseCapability.@resolve !== @undefined)
57             @throwTypeError("resolve function is already set");
58         if (promiseCapability.@reject !== @undefined)
59             @throwTypeError("reject function is already set");
60
61         promiseCapability.@resolve = resolve;
62         promiseCapability.@reject = reject;
63     }
64
65     var promise = new constructor(@executor);
66
67     if (typeof promiseCapability.@resolve !== "function")
68         @throwTypeError("executor did not take a resolve function");
69
70     if (typeof promiseCapability.@reject !== "function")
71         @throwTypeError("executor did not take a reject function");
72
73     promiseCapability.@promise = promise;
74
75     return promiseCapability;
76 }
77
78 @globalPrivate
79 function newPromiseCapability(constructor)
80 {
81     "use strict";
82
83     if (constructor === @Promise) {
84         var promise = @newPromise();
85         var capturedPromise = promise;
86         function @resolve(resolution) {
87             return @resolvePromiseWithFirstResolvingFunctionCallCheck(capturedPromise, resolution);
88         }
89         function @reject(reason) {
90             return @rejectPromiseWithFirstResolvingFunctionCallCheck(capturedPromise, reason);
91         }
92         return { @resolve, @reject, @promise: promise };
93     }
94
95     return @newPromiseCapabilitySlow(constructor);
96 }
97
98 @globalPrivate
99 function promiseResolveSlow(constructor, value)
100 {
101     @assert(constructor !== @Promise);
102     var promiseCapability = @newPromiseCapabilitySlow(constructor);
103     promiseCapability.@resolve.@call(@undefined, value);
104     return promiseCapability.@promise;
105 }
106
107 @globalPrivate
108 function promiseRejectSlow(constructor, reason)
109 {
110     @assert(constructor !== @Promise);
111     var promiseCapability = @newPromiseCapabilitySlow(constructor);
112     promiseCapability.@reject.@call(@undefined, reason);
113     return promiseCapability.@promise;
114 }
115
116 @globalPrivate
117 function newHandledRejectedPromise(error)
118 {
119     "use strict";
120     var promise = @newPromise();
121     @rejectPromiseWithFirstResolvingFunctionCallCheck(promise, error);
122     @putPromiseInternalField(promise, @promiseFieldFlags, @getPromiseInternalField(promise, @promiseFieldFlags) | @promiseFlagsIsHandled);
123     return promise;
124 }
125
126 @globalPrivate
127 function triggerPromiseReactions(state, reactions, argument)
128 {
129     "use strict";
130
131     // Reverse the order of singly-linked-list.
132     var previous = @undefined;
133     var current = reactions;
134     while (current) {
135         var next = current.@next;
136         current.@next = previous;
137         previous = current;
138         current = next;
139     }
140     reactions = previous;
141
142     current = reactions;
143     while (current) {
144         @enqueueJob(@promiseReactionJob, state, current, argument);
145         current = current.@next;
146     }
147 }
148
149 @globalPrivate
150 function resolvePromise(promise, resolution)
151 {
152     "use strict";
153
154     @assert(@isPromise(promise));
155
156     if (resolution === promise)
157         return @rejectPromise(promise, @makeTypeError("Cannot resolve a promise with itself"));
158
159     if (!@isObject(resolution))
160         return @fulfillPromise(promise, resolution);
161
162     var then;
163     try {
164         then = resolution.then;
165     } catch (error) {
166         return @rejectPromise(promise, error);
167     }
168
169     if (@isPromise(resolution) && then === @defaultPromiseThen) {
170         @enqueueJob(@promiseResolveThenableJobFast, resolution, promise);
171         return;
172     }
173
174     if (typeof then !== 'function')
175         return @fulfillPromise(promise, resolution);
176
177     @enqueueJob(@promiseResolveThenableJob, resolution, then, @createResolvingFunctions(promise));
178 }
179
180 @globalPrivate
181 function rejectPromise(promise, reason)
182 {
183     "use strict";
184
185     @assert(@isPromise(promise));
186     @assert((@getPromiseInternalField(promise, @promiseFieldFlags) & @promiseStateMask) == @promiseStatePending);
187
188     var flags = @getPromiseInternalField(promise, @promiseFieldFlags);
189     var reactions = @getPromiseInternalField(promise, @promiseFieldReactionsOrResult);
190     @putPromiseInternalField(promise, @promiseFieldReactionsOrResult, reason);
191     @putPromiseInternalField(promise, @promiseFieldFlags, flags | @promiseStateRejected);
192
193     @InspectorInstrumentation.promiseRejected(promise, reason, reactions);
194
195     if (!(flags & @promiseFlagsIsHandled))
196         @hostPromiseRejectionTracker(promise, @promiseRejectionReject);
197
198     @triggerPromiseReactions(@promiseStateRejected, reactions, reason);
199 }
200
201 @globalPrivate
202 function fulfillPromise(promise, value)
203 {
204     "use strict";
205
206     @assert(@isPromise(promise));
207     @assert((@getPromiseInternalField(promise, @promiseFieldFlags) & @promiseStateMask) == @promiseStatePending);
208
209     var flags = @getPromiseInternalField(promise, @promiseFieldFlags);
210     var reactions = @getPromiseInternalField(promise, @promiseFieldReactionsOrResult);
211     @putPromiseInternalField(promise, @promiseFieldReactionsOrResult, value);
212     @putPromiseInternalField(promise, @promiseFieldFlags, flags | @promiseStateFulfilled);
213
214     @InspectorInstrumentation.promiseFulfilled(promise, value, reactions);
215
216     @triggerPromiseReactions(@promiseStateFulfilled, reactions, value);
217 }
218
219 @globalPrivate
220 function resolvePromiseWithFirstResolvingFunctionCallCheck(promise, value)
221 {
222     @assert(@isPromise(promise));
223     var flags = @getPromiseInternalField(promise, @promiseFieldFlags);
224     if (flags & @promiseFlagsIsFirstResolvingFunctionCalled)
225         return;
226     @putPromiseInternalField(promise, @promiseFieldFlags, flags | @promiseFlagsIsFirstResolvingFunctionCalled);
227     return @resolvePromise(promise, value);
228 }
229
230 @globalPrivate
231 function rejectPromiseWithFirstResolvingFunctionCallCheck(promise, reason)
232 {
233     @assert(@isPromise(promise));
234     var flags = @getPromiseInternalField(promise, @promiseFieldFlags);
235     if (flags & @promiseFlagsIsFirstResolvingFunctionCalled)
236         return;
237     @putPromiseInternalField(promise, @promiseFieldFlags, flags | @promiseFlagsIsFirstResolvingFunctionCalled);
238     return @rejectPromise(promise, reason);
239 }
240
241 @globalPrivate
242 function createResolvingFunctions(promise)
243 {
244     "use strict";
245
246     @assert(@isPromise(promise));
247
248     var alreadyResolved = false;
249
250     function @resolve(resolution) {
251         if (alreadyResolved)
252             return @undefined;
253         alreadyResolved = true;
254
255         return @resolvePromise(promise, resolution);
256     }
257
258     function @reject(reason) {
259         if (alreadyResolved)
260             return @undefined;
261         alreadyResolved = true;
262
263         return @rejectPromise(promise, reason);
264     }
265
266     return { @resolve, @reject };
267 }
268
269 @globalPrivate
270 function promiseReactionJobWithoutPromise(handler, argument)
271 {
272     "use strict";
273
274     try {
275         handler(argument);
276     } catch {
277         // This is user-uncatchable promise. We just ignore the error here.
278     }
279 }
280
281 // This function has strong guarantee that each handler function (onFulfilled and onRejected) will be called at most once.
282 @globalPrivate
283 function resolveWithoutPromise(resolution, onFulfilled, onRejected)
284 {
285     "use strict";
286
287     if (!@isObject(resolution)) {
288         @fulfillWithoutPromise(resolution, onFulfilled, onRejected);
289         return;
290     }
291
292     var then;
293     try {
294         then = resolution.then;
295     } catch (error) {
296         @rejectWithoutPromise(error, onFulfilled, onRejected);
297         return;
298     }
299
300     if (@isPromise(resolution) && then === @defaultPromiseThen) {
301         @enqueueJob(@promiseResolveThenableJobWithoutPromiseFast, resolution, onFulfilled, onRejected);
302         return;
303     }
304
305     if (typeof then !== 'function') {
306         @fulfillWithoutPromise(resolution, onFulfilled, onRejected);
307         return;
308     }
309
310     // Wrap onFulfilled and onRejected with @createResolvingFunctionsWithoutPromise to ensure that each function will be called at most once.
311     @enqueueJob(@promiseResolveThenableJob, resolution, then, @createResolvingFunctionsWithoutPromise(onFulfilled, onRejected));
312 }
313
314 // This function has strong guarantee that each handler function (onFulfilled and onRejected) will be called at most once.
315 @globalPrivate
316 function rejectWithoutPromise(reason, onFulfilled, onRejected)
317 {
318     "use strict";
319
320     @enqueueJob(@promiseReactionJobWithoutPromise, onRejected, reason);
321 }
322
323 // This function has strong guarantee that each handler function (onFulfilled and onRejected) will be called at most once.
324 @globalPrivate
325 function fulfillWithoutPromise(value, onFulfilled, onRejected)
326 {
327     "use strict";
328
329     @enqueueJob(@promiseReactionJobWithoutPromise, onFulfilled, value);
330 }
331
332 @globalPrivate
333 function createResolvingFunctionsWithoutPromise(onFulfilled, onRejected)
334 {
335     "use strict";
336
337     var alreadyResolved = false;
338
339     function @resolve(resolution) {
340         if (alreadyResolved)
341             return @undefined;
342         alreadyResolved = true;
343
344         @resolveWithoutPromise(resolution, onFulfilled, onRejected);
345     }
346
347     function @reject(reason) {
348         if (alreadyResolved)
349             return @undefined;
350         alreadyResolved = true;
351
352         @rejectWithoutPromise(reason, onFulfilled, onRejected);
353     }
354
355     return { @resolve, @reject };
356 }
357
358 @globalPrivate
359 function promiseReactionJob(state, reaction, argument)
360 {
361     // Promise Reaction has four types.
362     // 1. @promiseOrCapability is PromiseCapability, and having handlers.
363     //     The most generic one.
364     // 2. @promiseOrCapability is Promise, and having handlers.
365     //     We just have promise.
366     // 3. @promiseOrCapability is Promise, and not having handlers.
367     //     It only has promise. Just resolving it with the value.
368     // 4. Only having @onFulfilled and @onRejected
369     //     It does not have promise capability. Just handlers are passed.
370     "use strict";
371
372     var promiseOrCapability = reaction.@promiseOrCapability;
373
374     // Case (3).
375     if (!reaction.@onRejected) {
376         @assert(!reaction.@onFulfilled);
377         try {
378             @assert(@isPromise(promiseOrCapability));
379             if (state === @promiseStateFulfilled)
380                 @resolvePromise(promiseOrCapability, argument);
381             else
382                 @rejectPromise(promiseOrCapability, argument);
383         } catch {
384             // This is user-uncatchable promise. We just ignore the error here.
385         }
386         return;
387     }
388
389     var handler = (state === @promiseStateFulfilled) ? reaction.@onFulfilled: reaction.@onRejected;
390
391     // Case (4).
392     if (!promiseOrCapability) {
393         @promiseReactionJobWithoutPromise(handler, argument);
394         return;
395     }
396
397     // Case (1), or (2).
398     var result;
399     try {
400         result = handler(argument);
401     } catch (error) {
402         if (@isPromise(promiseOrCapability)) {
403             @rejectPromise(promiseOrCapability, error);
404             return;
405         }
406         promiseOrCapability.@reject.@call(@undefined, error);
407         return;
408     }
409
410     if (@isPromise(promiseOrCapability)) {
411         @resolvePromise(promiseOrCapability, result);
412         return;
413     }
414     promiseOrCapability.@resolve.@call(@undefined, result);
415 }
416
417 @globalPrivate
418 function promiseResolveThenableJobFast(thenable, promiseToResolve)
419 {
420     "use strict";
421
422     @assert(@isPromise(thenable));
423     @assert(@isPromise(promiseToResolve));
424
425     var flags = @getPromiseInternalField(thenable, @promiseFieldFlags);
426     var state = flags & @promiseStateMask;
427     var reaction = @newPromiseReaction(promiseToResolve, @undefined, @undefined);
428     if (state === @promiseStatePending) {
429         reaction.@next = @getPromiseInternalField(thenable, @promiseFieldReactionsOrResult);
430         @putPromiseInternalField(thenable, @promiseFieldReactionsOrResult, reaction);
431     } else {
432         if (state === @promiseStateRejected && !(flags & @promiseFlagsIsHandled))
433             @hostPromiseRejectionTracker(thenable, @promiseRejectionHandle);
434         @enqueueJob(@promiseReactionJob, state, reaction, @getPromiseInternalField(thenable, @promiseFieldReactionsOrResult));
435     }
436     @putPromiseInternalField(thenable, @promiseFieldFlags, @getPromiseInternalField(thenable, @promiseFieldFlags) | @promiseFlagsIsHandled);
437 }
438
439 @globalPrivate
440 function promiseResolveThenableJobWithoutPromiseFast(thenable, onFulfilled, onRejected)
441 {
442     "use strict";
443
444     @assert(@isPromise(thenable));
445
446     var flags = @getPromiseInternalField(thenable, @promiseFieldFlags);
447     var state = flags & @promiseStateMask;
448     if (state === @promiseStatePending) {
449         var reaction = @newPromiseReaction(@undefined, onFulfilled, onRejected);
450         reaction.@next = @getPromiseInternalField(thenable, @promiseFieldReactionsOrResult);
451         @putPromiseInternalField(thenable, @promiseFieldReactionsOrResult, reaction);
452     } else {
453         var result = @getPromiseInternalField(thenable, @promiseFieldReactionsOrResult);
454         if (state === @promiseStateRejected) {
455             if (!(flags & @promiseFlagsIsHandled))
456                 @hostPromiseRejectionTracker(thenable, @promiseRejectionHandle);
457             @rejectWithoutPromise(result, onFulfilled, onRejected);
458         } else
459             @fulfillWithoutPromise(result, onFulfilled, onRejected);
460     }
461     @putPromiseInternalField(thenable, @promiseFieldFlags, @getPromiseInternalField(thenable, @promiseFieldFlags) | @promiseFlagsIsHandled);
462 }
463
464 @globalPrivate
465 function promiseResolveThenableJob(thenable, then, resolvingFunctions)
466 {
467     "use strict";
468
469     try {
470         return then.@call(thenable, resolvingFunctions.@resolve, resolvingFunctions.@reject);
471     } catch (error) {
472         return resolvingFunctions.@reject.@call(@undefined, error);
473     }
474 }