summaryrefslogtreecommitdiffstats
path: root/js/src/vm/AsyncFunction.h
blob: 8e31450f06f530798d72296af186a778aff3d97f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef vm_AsyncFunction_h
#define vm_AsyncFunction_h

#include "js/Class.h"
#include "vm/GeneratorObject.h"
#include "vm/JSObject.h"
#include "vm/PromiseObject.h"

// [SMDOC] Async functions
//
// # Implementation
//
// Async functions are implemented based on generators, in terms of
// suspend/resume.
// Instead of returning the generator object itself, they return the async
// function's result promise to the caller.
//
// The async function's result promise is stored in the generator object
// (js::AsyncFunctionGeneratorObject) and retrieved from it whenever the
// execution needs it.
//
//
// # Start
//
// When an async function is called, it synchronously runs until the first
// `await` or `return`.  This works just like a normal function.
//
// This corresponds to steps 1-3, 5-9 of AsyncFunctionStart.
//
// AsyncFunctionStart ( promiseCapability, asyncFunctionBody )
// https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
//
//   1. Let runningContext be the running execution context.
//   2. Let asyncContext be a copy of runningContext.
//   3. NOTE: Copying the execution state is required for the step below to
//      resume its execution. It is ill-defined to resume a currently executing
//      context.
//   ...
//   5. Push asyncContext onto the execution context stack; asyncContext is now
//      the running execution context.
//   6. Resume the suspended evaluation of asyncContext. Let result be the value
//      returned by the resumed computation.
//   7. Assert: When we return here, asyncContext has already been removed from
//      the execution context stack and runningContext is the currently running
//      execution context.
//   8. Assert: result is a normal completion with a value of undefined. The
//      possible sources of completion values are Await or, if the async
//      function doesn't await anything, step 4.g above.
//   9. Return.
//
// Unlike generators, async functions don't contain JSOp::InitialYield and
// don't suspend immediately when call.
//
//
// # Return
//
// Explicit/implicit `return` is implemented with the following bytecode
// sequence:
//
// ```
//   GetAliasedVar ".generator"      # VALUE .generator
//   AsyncResolve                    # PROMISE
//   SetRval                         #
//   GetAliasedVar ".generator"      # .generator
//   FinalYieldRval                  #
// ```
//
// JSOp::Resolve (js::AsyncFunctionResolve) resolves the current async
// function's result promise. Then this sets it as the function's return value.
// (The return value is observable if the caller is still on the stack--
// that is, the async function is returning without ever awaiting.
// Otherwise we're returning to the microtask loop, which ignores the
// return value.)
//
// This corresponds to AsyncFunctionStart steps 4.a-e. 4.g.
//
//   4. Set the code evaluation state of asyncContext such that when evaluation
//      is resumed for that execution context the following steps will be
//      performed:
//     a. Let result be the result of evaluating asyncFunctionBody.
//     b. Assert: If we return here, the async function either threw an
//        exception or performed an implicit or explicit return; all awaiting
//        is done.
//     c. Remove asyncContext from the execution context stack and restore the
//        execution context that is at the top of the execution context stack as
//        the running execution context.
//     d. If result.[[Type]] is normal, then
//       i. Perform
//          ! Call(promiseCapability.[[Resolve]], undefined, «undefined»).
//     e. Else if result.[[Type]] is return, then
//       i. Perform
//          ! Call(promiseCapability.[[Resolve]], undefined,
//                 «result.[[Value]]»).
//     ...
//     g. Return.
//
//
// # Throw
//
// The body part of an async function is enclosed by an implicit try-catch
// block, to catch `throw` completion of the function body.
//
// If an exception is thrown by the function body, the catch block catches it
// and rejects the async function's result promise.
//
// If there's an expression in parameters, the entire parameters part is also
// enclosed by a separate implicit try-catch block.
//
// ```
//   Try                             #
//   (parameter expressions here)    #
//   Goto BODY                       #
//
//   JumpTarget from try             #
//   ExceptionAndStack               # EXCEPTION STACK
//   GetAliasedVar ".generator"      # EXCEPTION STACK .generator
//   AsyncReject                     # PROMISE
//   SetRval                         #
//   GetAliasedVar ".generator"      # .generator
//   FinalYieldRval                  #
//
// BODY:
//   JumpTarget                      #
//   Try                             #
//   (body here)                     #
//
//   JumpTarget from try             #
//   ExceptionAndStack               # EXCEPTION STACK
//   GetAliasedVar ".generator"      # EXCEPTION STACK .generator
//   AsyncReject                     # PROMISE
//   SetRval                         #
//   GetAliasedVar ".generator"      # .generator
//   FinalYieldRval                  #
// ```
//
// This corresponds to AsyncFunctionStart steps 4.f-g.
//
//   4. ...
//     f. Else,
//       i. Assert: result.[[Type]] is throw.
//       ii. Perform
//           ! Call(promiseCapability.[[Reject]], undefined,
//                  «result.[[Value]]»).
//     g. Return.
//
//
// # Await
//
// `await` is implemented with the following bytecode sequence:
// (ignoring CanSkipAwait for now, see "Optimization for await" section)
//
// ```
//   (operand here)                  # VALUE
//   GetAliasedVar ".generator"      # VALUE .generator
//   AsyncAwait                      # PROMISE
//
//   GetAliasedVar ".generator"      # PROMISE .generator
//   Await 0                         # RVAL GENERATOR RESUMEKIND
//
//   AfterYield                      # RVAL GENERATOR RESUMEKIND
//   CheckResumeKind                 # RVAL
// ```
//
// JSOp::AsyncAwait corresponds to Await steps 1-9, and JSOp::Await corresponds
// to Await steps 10-12 in the spec.
//
// See the next section for JSOp::CheckResumeKind.
//
// After them, the async function is suspended, and if this is the first await
// in the execution, the async function's result promise is returned to the
// caller.
//
// Await
// https://tc39.es/ecma262/#await
//
//   1. Let asyncContext be the running execution context.
//   2. Let promise be ? PromiseResolve(%Promise%, value).
//   3. Let stepsFulfilled be the algorithm steps defined in Await Fulfilled
//      Functions.
//   4. Let onFulfilled be ! CreateBuiltinFunction(stepsFulfilled, «
//      [[AsyncContext]] »).
//   5. Set onFulfilled.[[AsyncContext]] to asyncContext.
//   6. Let stepsRejected be the algorithm steps defined in Await Rejected
//      Functions.
//   7. Let onRejected be ! CreateBuiltinFunction(stepsRejected, «
//      [[AsyncContext]] »).
//   8. Set onRejected.[[AsyncContext]] to asyncContext.
//   9. Perform ! PerformPromiseThen(promise, onFulfilled, onRejected).
//   10. Remove asyncContext from the execution context stack and restore the
//       execution context that is at the top of the execution context stack as
//       the running execution context.
//   11. Set the code evaluation state of asyncContext such that when evaluation
//       is resumed with a Completion completion, the following steps of the
//       algorithm that invoked Await will be performed, with completion
//       available.
//   12. Return.
//   13. NOTE: This returns to the evaluation of the operation that had most
//       previously resumed evaluation of asyncContext.
//
// (See comments above AsyncAwait and Await in js/src/vm/Opcodes.h for more
//  details)
//
//
// # Reaction jobs and resume after await
//
// When an async function performs `await` and the operand becomes settled, a
// new reaction job for the operand is enqueued to the job queue.
//
// The reaction record for the job is marked as "this is for async function"
// (see js::AsyncFunctionAwait), and handled specially in
// js::PromiseReactionJob.
//
// When the await operand resolves (either with fulfillment or rejection),
// the async function is resumed from the job queue, by calling
// js::AsyncFunctionAwaitedFulfilled or js::AsyncFunctionAwaitedRejected
// from js::AsyncFunctionPromiseReactionJob.
//
// The execution resumes from JSOp::AfterYield, with the resolved value
// and the resume kind, either normal or throw, corresponds to fulfillment or
// rejection, on the stack.
//
// The resume kind is handled by JSOp::CheckResumeKind after that.
//
// If the resume kind is normal (=fulfillment), the async function resumes
// the execution with the resolved value as the result of `await`.
//
// If the resume kind is throw (=rejection), it throws the resolved value,
// and it will be caught by the try-catch explained above.
//
//
// # Optimization for await
//
// Suspending the execution and going into the embedding's job queue is slow
// and hard to optimize.
//
// If the following conditions are met, we don't have to perform the above
// but just use the await operand as the result of await.
//
//   1. The await operand is either non-promise or already-fulfilled promise,
//      so that the result value is already known
//   2. There's no jobs in the job queue,
//      so that we don't have to perform other jobs before resuming from
//      await
//   3. Promise constructor/prototype are not modified,
//      so that the optimization isn't visible to the user code
//
// This is implemented by the following bytecode sequence:
//
// ```
//   (operand here)                  # VALUE
//
//   CanSkipAwait                    # VALUE, CAN_SKIP
//   MaybeExtractAwaitValue          # VALUE_OR_RVAL, CAN_SKIP
//   JumpIfTrue END                  # VALUE
//
//   JumpTarget                      # VALUE
//   GetAliasedVar ".generator"      # VALUE .generator
//   Await 0                         # RVAL GENERATOR RESUMEKIND
//   AfterYield                      # RVAL GENERATOR RESUMEKIND
//   CheckResumeKind                 # RVAL
//
// END:
//   JumpTarget                      # RVAL
// ```
//
// JSOp::CanSkipAwait checks the above conditions. MaybeExtractAwaitValue will
// replace Value if it can be skipped, and then the await is jumped over.

namespace js {

class AsyncFunctionGeneratorObject;

extern const JSClass AsyncFunctionClass;

// Resume the async function when the `await` operand resolves.
// Split into two functions depending on whether the awaited value was
// fulfilled or rejected.
[[nodiscard]] bool AsyncFunctionAwaitedFulfilled(
    JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
    HandleValue value);

[[nodiscard]] bool AsyncFunctionAwaitedRejected(
    JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
    HandleValue reason);

// Resolve the async function's promise object with the given value and then
// return the promise object.
JSObject* AsyncFunctionResolve(JSContext* cx,
                               Handle<AsyncFunctionGeneratorObject*> generator,
                               HandleValue value);

// Reject the async function's promise object with the given value and then
// return the promise object.
JSObject* AsyncFunctionReject(JSContext* cx,
                              Handle<AsyncFunctionGeneratorObject*> generator,
                              HandleValue reason, HandleValue stack);

class AsyncFunctionGeneratorObject : public AbstractGeneratorObject {
 public:
  enum {
    PROMISE_SLOT = AbstractGeneratorObject::RESERVED_SLOTS,

    RESERVED_SLOTS
  };

  static const JSClass class_;
  static const JSClassOps classOps_;

  static AsyncFunctionGeneratorObject* create(JSContext* cx,
                                              HandleFunction asyncGen);

  static AsyncFunctionGeneratorObject* create(JSContext* cx,
                                              Handle<ModuleObject*> module);

  PromiseObject* promise() {
    return &getFixedSlot(PROMISE_SLOT).toObject().as<PromiseObject>();
  }
};

}  // namespace js

#endif /* vm_AsyncFunction_h */