1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef vm_AsyncFunction_h
8 #define vm_AsyncFunction_h
9 
10 #include "js/Class.h"
11 #include "vm/AsyncFunctionResolveKind.h"  // AsyncFunctionResolveKind
12 #include "vm/GeneratorObject.h"
13 #include "vm/JSContext.h"
14 #include "vm/JSObject.h"
15 #include "vm/PromiseObject.h"
16 
17 // [SMDOC] Async functions
18 //
19 // # Implementation
20 //
21 // Async functions are implemented based on generators, in terms of
22 // suspend/resume.
23 // Instead of returning the generator object itself, they return the async
24 // function's result promise to the caller.
25 //
26 // The async function's result promise is stored in the generator object
27 // (js::AsyncFunctionGeneratorObject) and retrieved from it whenever the
28 // execution needs it.
29 //
30 //
31 // # Start
32 //
33 // When an async function is called, it synchronously runs until the first
34 // `await` or `return`.  This works just like a normal function.
35 //
36 // This corresponds to steps 1-3, 5-9 of AsyncFunctionStart.
37 //
38 // AsyncFunctionStart ( promiseCapability, asyncFunctionBody )
39 // https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
40 //
41 //   1. Let runningContext be the running execution context.
42 //   2. Let asyncContext be a copy of runningContext.
43 //   3. NOTE: Copying the execution state is required for the step below to
44 //      resume its execution. It is ill-defined to resume a currently executing
45 //      context.
46 //   ...
47 //   5. Push asyncContext onto the execution context stack; asyncContext is now
48 //      the running execution context.
49 //   6. Resume the suspended evaluation of asyncContext. Let result be the value
50 //      returned by the resumed computation.
51 //   7. Assert: When we return here, asyncContext has already been removed from
52 //      the execution context stack and runningContext is the currently running
53 //      execution context.
54 //   8. Assert: result is a normal completion with a value of undefined. The
55 //      possible sources of completion values are Await or, if the async
56 //      function doesn't await anything, step 4.g above.
57 //   9. Return.
58 //
59 // Unlike generators, async functions don't contain JSOp::InitialYield and
60 // don't suspend immediately when call.
61 //
62 //
63 // # Return
64 //
65 // Explicit/implicit `return` is implemented with the following bytecode
66 // sequence:
67 //
68 // ```
69 //   GetAliasedVar ".generator"      # VALUE .generator
70 //   AsyncResolve 0                  # PROMISE
71 //   SetRval                         #
72 //   GetAliasedVar ".generator"      # .generator
73 //   FinalYieldRval                  #
74 // ```
75 //
76 // JSOp::Resolve (js::AsyncFunctionResolve) resolves the current async
77 // function's result promise. Then this sets it as the function's return value.
78 // (The return value is observable if the caller is still on the stack--
79 // that is, the async function is returning without ever awaiting.
80 // Otherwise we're returning to the microtask loop, which ignores the
81 // return value.)
82 //
83 // This corresponds to AsyncFunctionStart steps 4.a-e. 4.g.
84 //
85 //   4. Set the code evaluation state of asyncContext such that when evaluation
86 //      is resumed for that execution context the following steps will be
87 //      performed:
88 //     a. Let result be the result of evaluating asyncFunctionBody.
89 //     b. Assert: If we return here, the async function either threw an
90 //        exception or performed an implicit or explicit return; all awaiting
91 //        is done.
92 //     c. Remove asyncContext from the execution context stack and restore the
93 //        execution context that is at the top of the execution context stack as
94 //        the running execution context.
95 //     d. If result.[[Type]] is normal, then
96 //       i. Perform
97 //          ! Call(promiseCapability.[[Resolve]], undefined, «undefined»).
98 //     e. Else if result.[[Type]] is return, then
99 //       i. Perform
100 //          ! Call(promiseCapability.[[Resolve]], undefined,
101 //                 «result.[[Value]]»).
102 //     ...
103 //     g. Return.
104 //
105 //
106 // # Throw
107 //
108 // The body part of an async function is enclosed by an implicit try-catch
109 // block, to catch `throw` completion of the function body.
110 //
111 // If an exception is thrown by the function body, the catch block catches it
112 // and rejects the async function's result promise.
113 //
114 // If there's an expression in parameters, the entire parameters part is also
115 // enclosed by a separate implicit try-catch block.
116 //
117 // ```
118 //   Try                             #
119 //   (parameter expressions here)    #
120 //   Goto BODY                       #
121 //
122 //   JumpTarget from try             #
123 //   Exception                       # EXCEPTION
124 //   GetAliasedVar ".generator"      # EXCEPTION .generator
125 //   AsyncResolve 1                  # PROMISE
126 //   SetRval                         #
127 //   GetAliasedVar ".generator"      # .generator
128 //   FinalYieldRval                  #
129 //
130 // BODY:
131 //   JumpTarget                      #
132 //   Try                             #
133 //   (body here)                     #
134 //
135 //   JumpTarget from try             #
136 //   Exception                       # EXCEPTION
137 //   GetAliasedVar ".generator"      # EXCEPTION .generator
138 //   AsyncResolve 1                  # PROMISE
139 //   SetRval                         #
140 //   GetAliasedVar ".generator"      # .generator
141 //   FinalYieldRval                  #
142 // ```
143 //
144 // This corresponds to AsyncFunctionStart steps 4.f-g.
145 //
146 //   4. ...
147 //     f. Else,
148 //       i. Assert: result.[[Type]] is throw.
149 //       ii. Perform
150 //           ! Call(promiseCapability.[[Reject]], undefined,
151 //                  «result.[[Value]]»).
152 //     g. Return.
153 //
154 //
155 // # Await
156 //
157 // `await` is implemented with the following bytecode sequence:
158 // (ignoring CanSkipAwait for now, see "Optimization for await" section)
159 //
160 // ```
161 //   (operand here)                  # VALUE
162 //   GetAliasedVar ".generator"      # VALUE .generator
163 //   AsyncAwait                      # PROMISE
164 //
165 //   GetAliasedVar ".generator"      # PROMISE .generator
166 //   Await 0                         # RVAL GENERATOR RESUMEKIND
167 //
168 //   AfterYield                      # RVAL GENERATOR RESUMEKIND
169 //   CheckResumeKind                 # RVAL
170 // ```
171 //
172 // JSOp::AsyncAwait corresponds to Await steps 1-9, and JSOp::Await corresponds
173 // to Await steps 10-12 in the spec.
174 //
175 // See the next section for JSOp::CheckResumeKind.
176 //
177 // After them, the async function is suspended, and if this is the first await
178 // in the execution, the async function's result promise is returned to the
179 // caller.
180 //
181 // Await
182 // https://tc39.es/ecma262/#await
183 //
184 //   1. Let asyncContext be the running execution context.
185 //   2. Let promise be ? PromiseResolve(%Promise%, value).
186 //   3. Let stepsFulfilled be the algorithm steps defined in Await Fulfilled
187 //      Functions.
188 //   4. Let onFulfilled be ! CreateBuiltinFunction(stepsFulfilled, «
189 //      [[AsyncContext]] »).
190 //   5. Set onFulfilled.[[AsyncContext]] to asyncContext.
191 //   6. Let stepsRejected be the algorithm steps defined in Await Rejected
192 //      Functions.
193 //   7. Let onRejected be ! CreateBuiltinFunction(stepsRejected, «
194 //      [[AsyncContext]] »).
195 //   8. Set onRejected.[[AsyncContext]] to asyncContext.
196 //   9. Perform ! PerformPromiseThen(promise, onFulfilled, onRejected).
197 //   10. Remove asyncContext from the execution context stack and restore the
198 //       execution context that is at the top of the execution context stack as
199 //       the running execution context.
200 //   11. Set the code evaluation state of asyncContext such that when evaluation
201 //       is resumed with a Completion completion, the following steps of the
202 //       algorithm that invoked Await will be performed, with completion
203 //       available.
204 //   12. Return.
205 //   13. NOTE: This returns to the evaluation of the operation that had most
206 //       previously resumed evaluation of asyncContext.
207 //
208 // (See comments above AsyncAwait and Await in js/src/vm/Opcodes.h for more
209 //  details)
210 //
211 //
212 // # Reaction jobs and resume after await
213 //
214 // When an async function performs `await` and the operand becomes settled, a
215 // new reaction job for the operand is enqueued to the job queue.
216 //
217 // The reaction record for the job is marked as "this is for async function"
218 // (see js::AsyncFunctionAwait), and handled specially in
219 // js::PromiseReactionJob.
220 //
221 // When the await operand resolves (either with fulfillment or rejection),
222 // the async function is resumed from the job queue, by calling
223 // js::AsyncFunctionAwaitedFulfilled or js::AsyncFunctionAwaitedRejected
224 // from js::AsyncFunctionPromiseReactionJob.
225 //
226 // The execution resumes from JSOp::AfterYield, with the resolved value
227 // and the resume kind, either normal or throw, corresponds to fulfillment or
228 // rejection, on the stack.
229 //
230 // The resume kind is handled by JSOp::CheckResumeKind after that.
231 //
232 // If the resume kind is normal (=fulfillment), the async function resumes
233 // the execution with the resolved value as the result of `await`.
234 //
235 // If the resume kind is throw (=rejection), it throws the resolved value,
236 // and it will be caught by the try-catch explained above.
237 //
238 //
239 // # Optimization for await
240 //
241 // Suspending the execution and going into the embedding's job queue is slow
242 // and hard to optimize.
243 //
244 // If the following conditions are met, we don't have to perform the above
245 // but just use the await operand as the result of await.
246 //
247 //   1. The await operand is either non-promise or already-fulfilled promise,
248 //      so that the result value is already known
249 //   2. There's no jobs in the job queue,
250 //      so that we don't have to perform other jobs before resuming from
251 //      await
252 //   3. Promise constructor/prototype are not modified,
253 //      so that the optimization isn't visible to the user code
254 //
255 // This is implemented by the following bytecode sequence:
256 //
257 // ```
258 //   (operand here)                  # VALUE
259 //
260 //   CanSkipAwait                    # VALUE, CAN_SKIP
261 //   MaybeExtractAwaitValue          # VALUE_OR_RVAL, CAN_SKIP
262 //   JumpIfTrue END                  # VALUE
263 //
264 //   JumpTarget                      # VALUE
265 //   GetAliasedVar ".generator"      # VALUE .generator
266 //   Await 0                         # RVAL GENERATOR RESUMEKIND
267 //   AfterYield                      # RVAL GENERATOR RESUMEKIND
268 //   CheckResumeKind                 # RVAL
269 //
270 // END:
271 //   JumpTarget                      # RVAL
272 // ```
273 //
274 // JSOp::CanSkipAwait checks the above conditions. MaybeExtractAwaitValue will
275 // replace Value if it can be skipped, and then the await is jumped over.
276 
277 namespace js {
278 
279 class AsyncFunctionGeneratorObject;
280 
281 extern const JSClass AsyncFunctionClass;
282 
283 // Resume the async function when the `await` operand resolves.
284 // Split into two functions depending on whether the awaited value was
285 // fulfilled or rejected.
286 [[nodiscard]] bool AsyncFunctionAwaitedFulfilled(
287     JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
288     HandleValue value);
289 
290 [[nodiscard]] bool AsyncFunctionAwaitedRejected(
291     JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
292     HandleValue reason);
293 
294 // Resolve the async function's promise object with the given value and then
295 // return the promise object.
296 JSObject* AsyncFunctionResolve(JSContext* cx,
297                                Handle<AsyncFunctionGeneratorObject*> generator,
298                                HandleValue valueOrReason,
299                                AsyncFunctionResolveKind resolveKind);
300 
301 class AsyncFunctionGeneratorObject : public AbstractGeneratorObject {
302  public:
303   enum {
304     PROMISE_SLOT = AbstractGeneratorObject::RESERVED_SLOTS,
305 
306     RESERVED_SLOTS
307   };
308 
309   static const JSClass class_;
310   static const JSClassOps classOps_;
311 
312   static AsyncFunctionGeneratorObject* create(JSContext* cx,
313                                               HandleFunction asyncGen);
314 
315   static AsyncFunctionGeneratorObject* create(JSContext* cx,
316                                               HandleModuleObject module);
317 
promise()318   PromiseObject* promise() {
319     return &getFixedSlot(PROMISE_SLOT).toObject().as<PromiseObject>();
320   }
321 };
322 
323 }  // namespace js
324 
325 #endif /* vm_AsyncFunction_h */
326