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