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 */