tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AsyncFunction.h (12493B)


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