AsyncIteration.h (20987B)
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_AsyncIteration_h 8 #define vm_AsyncIteration_h 9 10 #include "builtin/Promise.h" // js::PromiseHandler 11 #include "builtin/SelfHostingDefines.h" 12 #include "js/Class.h" 13 #include "vm/GeneratorObject.h" 14 #include "vm/JSObject.h" 15 #include "vm/List.h" 16 #include "vm/PromiseObject.h" 17 18 // [SMDOC] Async generators 19 // 20 // # Start 21 // 22 // When an async generator is called, it synchronously runs until the 23 // JSOp::InitialYield and then suspends, just like a sync generator, and returns 24 // an async generator object (js::AsyncGeneratorObject). 25 // 26 // 27 // # Request queue 28 // 29 // When next/return/throw is called on the async generator object: 30 // * AsyncGeneratorEnqueue creates a new AsyncGeneratorRequest and enqueues 31 // it in the generator object's request queue. 32 // * AsyncGeneratorResume resumes the generator with the oldest request, 33 // if the generator is suspended (see "Resume" section below) 34 // 35 // The returned promise is resolved when the resumption for the request 36 // completes with yield/throw/return, in AsyncGeneratorCompleteStepNormal and 37 // AsyncGeneratorCompleteStepThrow. 38 // They correspond to AsyncGeneratorCompleteStep in the spec. 39 // 40 // 41 // # Await 42 // 43 // Async generator's `await` is implemented differently than async function's 44 // `await`. 45 // 46 // The bytecode is the following: 47 // (ignoring CanSkipAwait; see the comment in AsyncFunction.h for more details) 48 // 49 // ``` 50 // (operand here) # VALUE 51 // GetAliasedVar ".generator" # VALUE .generator 52 // Await 0 # RVAL GENERATOR RESUMEKIND 53 // 54 // AfterYield # RVAL GENERATOR RESUMEKIND 55 // CheckResumeKind # RVAL 56 // ``` 57 // 58 // Async generators don't use JSOp::AsyncAwait, and that part is handled 59 // in AsyncGeneratorResume, and AsyncGeneratorAwait called there. 60 // 61 // Both JSOp::Await and JSOp::Yield behave in the exactly same way, 62 // and AsyncGeneratorResume checks the last opcode and branches for 63 // await/yield/return cases. 64 // 65 // 66 // # Reaction jobs and resume after await 67 // 68 // This is almost same as for async functions (see AsyncFunction.h). 69 // 70 // The reaction record for the job is marked as "this is for async generator" 71 // (see AsyncGeneratorAwait), and handled specially in 72 // PromiseReactionJob, which calls js::AsyncGeneratorPromiseReactionJob. 73 // 74 // 75 // # Yield 76 // 77 // `yield` is implemented with the following bytecode sequence: 78 // (Ignoring CanSkipAwait for simplicity) 79 // 80 // ``` 81 // (operand here) # VALUE 82 // GetAliasedVar ".generator" # VALUE .generator 83 // Await 1 # RVAL GENERATOR RESUMEKIND 84 // AfterYield # RVAL GENERATOR RESUMEKIND 85 // CheckResumeKind # RVAL 86 // 87 // GetAliasedVar ".generator" # RVAL .generator 88 // Yield 2 # RVAL2 GENERATOR RESUMEKIND 89 // 90 // AfterYield # RVAL2 GENERATOR RESUMEKIND 91 // CheckResumeKind # RVAL2 92 // ``` 93 // 94 // The 1st part (JSOp::Await + JSOp::CheckResumeKind) performs an implicit 95 // `await`, as specified in Yield step 2. 96 // 97 // Yield ( value ) 98 // https://tc39.es/ecma262/#sec-yield 99 // 100 // 2. If generatorKind is async, return 101 // ? AsyncGeneratorYield(? Await(value)). 102 // 103 // The 2nd part (JSOp::Yield) suspends execution and yields the result of 104 // `await`, as specified in AsyncGeneratorYield. 105 // 106 // AsyncGeneratorYield ( value ) 107 // https://tc39.es/ecma262/#sec-asyncgeneratoryield 108 // 109 // 1. Let genContext be the running execution context. 110 // 2. Assert: genContext is the execution context of a generator. 111 // 3. Let generator be the value of the Generator component of genContext. 112 // 4. Assert: GetGeneratorKind() is async. 113 // 5. Let completion be NormalCompletion(value). 114 // 6. Assert: The execution context stack has at least two elements. 115 // 7. Let previousContext be the second to top element of the execution 116 // context stack. 117 // 8. Let previousRealm be previousContext's Realm. 118 // 9. Perform AsyncGeneratorCompleteStep(generator, completion, false, 119 // previousRealm). 120 // 10. Let queue be generator.[[AsyncGeneratorQueue]]. 121 // 11. If queue is not empty, then 122 // a. NOTE: Execution continues without suspending the generator. 123 // b. Let toYield be the first element of queue. 124 // c. Let resumptionValue be Completion(toYield.[[Completion]]). 125 // d. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue). 126 // 12. Else, 127 // a. Set generator.[[AsyncGeneratorState]] to suspended-yield. 128 // b. Remove genContext from the execution context stack and restore the 129 // execution context that is at the top of the execution context stack 130 // as the running execution context. 131 // c. Let callerContext be the running execution context. 132 // d. Resume callerContext passing undefined. If genContext is ever 133 // resumed again, let resumptionValue be the Completion Record with 134 // which it is resumed. 135 // e. Assert: If control reaches here, then genContext is the running 136 // execution context again. 137 // f. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue). 138 // 139 // The last part (JSOp::CheckResumeKind) checks the resumption type and 140 // resumes/throws/returns the execution, as specified in 141 // AsyncGeneratorUnwrapYieldResumption 142 // 143 // AsyncGeneratorUnwrapYieldResumption ( resumptionValue ) 144 // https://tc39.es/ecma262/#sec-asyncgeneratorunwrapyieldresumption 145 // 146 // 1. If resumptionValue is not a return completion, 147 // return ? resumptionValue. 148 // 2. Let awaited be Completion(Await(resumptionValue.[[Value]])). 149 // 3. If awaited is a throw completion, return ? awaited. 150 // 4. Assert: awaited is a normal completion. 151 // 5. Return ReturnCompletion(awaited.[[Value]]). 152 // 153 // Resumption with `AsyncGenerator.prototype.return` is handled differently. 154 // See "Resumption with return" section below. 155 // 156 // 157 // # Return 158 // 159 // `return` with operand is implemented with the following bytecode sequence: 160 // (Ignoring CanSkipAwait for simplicity) 161 // 162 // ``` 163 // (operand here) # VALUE 164 // GetAliasedVar ".generator" # VALUE .generator 165 // Await 0 # RVAL GENERATOR RESUMEKIND 166 // AfterYield # RVAL GENERATOR RESUMEKIND 167 // CheckResumeKind # RVAL 168 // 169 // SetRval # 170 // GetAliasedVar ".generator" # .generator 171 // FinalYieldRval # 172 // ``` 173 // 174 // The 1st part (JSOp::Await + JSOp::CheckResumeKind) performs implicit 175 // `await`, as specified in ReturnStatement's Evaluation step 3. 176 // 177 // ReturnStatement: return Expression; 178 // https://tc39.es/ecma262/#sec-return-statement-runtime-semantics-evaluation 179 // 180 // 3. If GetGeneratorKind() is async, set exprValue to ? Await(exprValue). 181 // 182 // And the 2nd part corresponds to AsyncGeneratorStart steps 4.g-l. 183 // 184 // AsyncGeneratorStart ( generator, generatorBody ) 185 // https://tc39.es/ecma262/#sec-asyncgeneratorstart 186 // 187 // 4. Let closure be a new Abstract Closure with no parameters that captures 188 // generatorBody and performs the following steps when called: 189 // ... 190 // g. Set acGenerator.[[AsyncGeneratorState]] to draining-queue. 191 // h. If result is a normal completion, set result to 192 // NormalCompletion(undefined). 193 // i. If result is a return completion, set result to 194 // NormalCompletion(result.[[Value]]). 195 // j. Perform AsyncGeneratorCompleteStep(acGenerator, result, true). 196 // k. Perform AsyncGeneratorDrainQueue(acGenerator). 197 // l. Return undefined. 198 // 199 // `return` without operand or implicit return is implicit with the following 200 // bytecode sequence: 201 // 202 // ``` 203 // Undefined # undefined 204 // SetRval # 205 // GetAliasedVar ".generator" # .generator 206 // FinalYieldRval # 207 // ``` 208 // 209 // This is also AsyncGeneratorStart steps 4.g-l. 210 // 211 // 212 // # Throw 213 // 214 // Unlike async function, async generator doesn't use implicit try-catch, 215 // but the throw completion is handled by AsyncGeneratorResume, 216 // and AsyncGeneratorThrown is called there. 217 // 218 // AsyncGeneratorStart ( generator, generatorBody ) 219 // https://tc39.es/ecma262/#sec-asyncgeneratorstart 220 // 221 // 4. Let closure be a new Abstract Closure with no parameters that captures 222 // generatorBody and performs the following steps when called: 223 // ... 224 // g. Set acGenerator.[[AsyncGeneratorState]] to draining-queue. 225 // h. If result is a normal completion, set result to 226 // NormalCompletion(undefined). 227 // i. If result is a return completion, set result to 228 // NormalCompletion(result.[[Value]]). 229 // j. Perform AsyncGeneratorCompleteStep(acGenerator, result, true). 230 // k. Perform AsyncGeneratorDrainQueue(acGenerator). 231 // l. Return undefined. 232 // 233 // AsyncGeneratorCompleteStep ( generator, completion, done [ , realm ] ) 234 // https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep 235 // 236 // 1. Assert: generator.[[AsyncGeneratorQueue]] is not empty. 237 // 2. Let next be the first element of generator.[[AsyncGeneratorQueue]]. 238 // 3. Remove the first element from generator.[[AsyncGeneratorQueue]]. 239 // 4. Let promiseCapability be next.[[Capability]]. 240 // 5. Let value be completion.[[Value]]. 241 // 6. If completion is a throw completion, then 242 // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »). 243 // 244 // 245 // # Resumption with return 246 // 247 // If the generator is in "suspended-yield" state, it doesn't immediately 248 // resume the generator script itself, but it handles implicit `await` it in 249 // AsyncGeneratorUnwrapYieldResumption. 250 // (See PromiseHandler::AsyncGeneratorYieldReturnAwaitedFulfilled and 251 // PromiseHandler::AsyncGeneratorYieldReturnAwaitedRejected), and resumes the 252 // generator with the result of await. 253 // 254 // The return completion is finally handled in JSOp::CheckResumeKind 255 // after JSOp::Yield. 256 // 257 // AsyncGeneratorUnwrapYieldResumption ( resumptionValue ) 258 // https://tc39.es/ecma262/#sec-asyncgeneratorunwrapyieldresumption 259 // 260 // 1. If resumptionValue is not a return completion, return ? 261 // resumptionValue. 262 // 2. Let awaited be Completion(Await(resumptionValue.[[Value]])). 263 // 3. If awaited is a throw completion, return ? awaited. 264 // 4. Assert: awaited is a normal completion. 265 // 5. Return ReturnCompletion(awaited.[[Value]]). 266 // 267 // If the generator is already completed, it awaits on the return value in 268 // AsyncGeneratorAwaitReturn. 269 // (See PromiseHandler::AsyncGeneratorAwaitReturnFulfilled and 270 // PromiseHandler::AsyncGeneratorAwaitReturnRejected), and resolves the 271 // request's promise with the value. 272 273 namespace js { 274 275 class AsyncGeneratorObject; 276 enum class CompletionKind : uint8_t; 277 278 extern const JSClass AsyncGeneratorFunctionClass; 279 280 [[nodiscard]] bool AsyncGeneratorPromiseReactionJob( 281 JSContext* cx, PromiseHandler handler, 282 Handle<AsyncGeneratorObject*> generator, HandleValue argument); 283 284 bool AsyncGeneratorNext(JSContext* cx, unsigned argc, Value* vp); 285 bool AsyncGeneratorReturn(JSContext* cx, unsigned argc, Value* vp); 286 bool AsyncGeneratorThrow(JSContext* cx, unsigned argc, Value* vp); 287 288 // AsyncGeneratorRequest record in the spec. 289 // Stores the info from AsyncGenerator#{next,return,throw}. 290 // 291 // This object is reused across multiple requests as an optimization, and 292 // stored in the Slot_CachedRequest slot. 293 class AsyncGeneratorRequest : public NativeObject { 294 private: 295 enum AsyncGeneratorRequestSlots { 296 // Int32 value with CompletionKind. 297 // Normal: next 298 // Return: return 299 // Throw: throw 300 Slot_CompletionKind = 0, 301 302 // The value passed to AsyncGenerator#{next,return,throw}. 303 Slot_CompletionValue, 304 305 // The promise returned by AsyncGenerator#{next,return,throw}. 306 Slot_Promise, 307 308 Slots, 309 }; 310 311 void init(CompletionKind completionKind, const Value& completionValue, 312 PromiseObject* promise) { 313 setFixedSlot(Slot_CompletionKind, 314 Int32Value(static_cast<int32_t>(completionKind))); 315 setFixedSlot(Slot_CompletionValue, completionValue); 316 setFixedSlot(Slot_Promise, ObjectValue(*promise)); 317 } 318 319 // Clear the request data for reuse. 320 void clearData() { 321 setFixedSlot(Slot_CompletionValue, NullValue()); 322 setFixedSlot(Slot_Promise, NullValue()); 323 } 324 325 friend AsyncGeneratorObject; 326 327 public: 328 static const JSClass class_; 329 330 static AsyncGeneratorRequest* create(JSContext* cx, 331 CompletionKind completionKind, 332 HandleValue completionValue, 333 Handle<PromiseObject*> promise); 334 335 CompletionKind completionKind() const { 336 return static_cast<CompletionKind>( 337 getFixedSlot(Slot_CompletionKind).toInt32()); 338 } 339 JS::Value completionValue() const { 340 return getFixedSlot(Slot_CompletionValue); 341 } 342 PromiseObject* promise() const { 343 return &getFixedSlot(Slot_Promise).toObject().as<PromiseObject>(); 344 } 345 }; 346 347 class AsyncGeneratorObject : public AbstractGeneratorObject { 348 private: 349 enum AsyncGeneratorObjectSlots { 350 // Int32 value containing one of the |State| fields from below. 351 Slot_State = AbstractGeneratorObject::RESERVED_SLOTS, 352 353 // * null value if this async generator has no requests 354 // * AsyncGeneratorRequest if this async generator has only one request 355 // * list object if this async generator has 2 or more requests 356 Slot_QueueOrRequest, 357 358 // Cached AsyncGeneratorRequest for later use. 359 // undefined if there's no cache. 360 Slot_CachedRequest, 361 362 Slots 363 }; 364 365 public: 366 enum State { 367 // "suspended-start" in the spec. 368 // Suspended after invocation. 369 State_SuspendedStart, 370 371 // "suspended-yield" in the spec 372 // Suspended with `yield` expression. 373 State_SuspendedYield, 374 375 // "executing" in the spec. 376 // Resumed from initial suspend or yield, and either running the script 377 // or awaiting for `await` expression. 378 State_Executing, 379 380 // Part of "executing" in the spec. 381 // Awaiting on the value passed by AsyncGenerator#return which is called 382 // while executing. 383 State_Executing_AwaitingYieldReturn, 384 385 // "draining-queue" in the spec. 386 // It's performing AsyncGeneratorDrainQueue. 387 State_DrainingQueue, 388 389 // Part of "draining-queue" in the spec. 390 // Awaiting on the value passed by AsyncGenerator#return which is called 391 // after completed. 392 State_DrainingQueue_AwaitingReturn, 393 394 // "completed" in the spec. 395 // The generator is completed. 396 State_Completed 397 }; 398 399 State state() const { 400 return static_cast<State>(getFixedSlot(Slot_State).toInt32()); 401 } 402 void setState(State state_) { setFixedSlot(Slot_State, Int32Value(state_)); } 403 404 private: 405 // Queue is implemented in 2 ways. If only one request is queued ever, 406 // request is stored directly to the slot. Once 2 requests are queued, a 407 // list is created and requests are appended into it, and the list is 408 // stored to the slot. 409 410 bool isSingleQueue() const { 411 return getFixedSlot(Slot_QueueOrRequest).isNull() || 412 getFixedSlot(Slot_QueueOrRequest) 413 .toObject() 414 .is<AsyncGeneratorRequest>(); 415 } 416 bool isSingleQueueEmpty() const { 417 return getFixedSlot(Slot_QueueOrRequest).isNull(); 418 } 419 void setSingleQueueRequest(AsyncGeneratorRequest* request) { 420 setFixedSlot(Slot_QueueOrRequest, ObjectValue(*request)); 421 } 422 void clearSingleQueueRequest() { 423 setFixedSlot(Slot_QueueOrRequest, NullValue()); 424 } 425 AsyncGeneratorRequest* singleQueueRequest() const { 426 return &getFixedSlot(Slot_QueueOrRequest) 427 .toObject() 428 .as<AsyncGeneratorRequest>(); 429 } 430 431 ListObject* queue() const { 432 return &getFixedSlot(Slot_QueueOrRequest).toObject().as<ListObject>(); 433 } 434 void setQueue(ListObject* queue_) { 435 setFixedSlot(Slot_QueueOrRequest, ObjectValue(*queue_)); 436 } 437 438 public: 439 static const JSClass class_; 440 static const JSClassOps classOps_; 441 442 static AsyncGeneratorObject* create(JSContext* cx, HandleFunction asyncGen); 443 444 bool isSuspendedStart() const { return state() == State_SuspendedStart; } 445 bool isSuspendedYield() const { return state() == State_SuspendedYield; } 446 bool isExecuting() const { return state() == State_Executing; } 447 bool isExecuting_AwaitingYieldReturn() const { 448 return state() == State_Executing_AwaitingYieldReturn; 449 } 450 bool isDrainingQueue() const { return state() == State_DrainingQueue; } 451 bool isDrainingQueue_AwaitingReturn() const { 452 return state() == State_DrainingQueue_AwaitingReturn; 453 } 454 bool isCompleted() const { return state() == State_Completed; } 455 456 void setSuspendedStart() { setState(State_SuspendedStart); } 457 void setSuspendedYield() { setState(State_SuspendedYield); } 458 void setExecuting() { setState(State_Executing); } 459 void setExecuting_AwaitingYieldReturn() { 460 setState(State_Executing_AwaitingYieldReturn); 461 } 462 void setDrainingQueue() { setState(State_DrainingQueue); } 463 void setDrainingQueue_AwaitingReturn() { 464 setState(State_DrainingQueue_AwaitingReturn); 465 } 466 void setCompleted() { setState(State_Completed); } 467 468 [[nodiscard]] static bool enqueueRequest( 469 JSContext* cx, Handle<AsyncGeneratorObject*> generator, 470 Handle<AsyncGeneratorRequest*> request); 471 472 static AsyncGeneratorRequest* dequeueRequest( 473 JSContext* cx, Handle<AsyncGeneratorObject*> generator); 474 475 static AsyncGeneratorRequest* peekRequest( 476 Handle<AsyncGeneratorObject*> generator); 477 478 bool isQueueEmpty() const { 479 if (isSingleQueue()) { 480 return isSingleQueueEmpty(); 481 } 482 return queue()->getDenseInitializedLength() == 0; 483 } 484 485 #ifdef DEBUG 486 bool isQueueLengthOne() const { 487 if (isSingleQueue()) { 488 return !isSingleQueueEmpty(); 489 } 490 return queue()->getDenseInitializedLength() == 1; 491 } 492 #endif 493 494 // This function does either of the following: 495 // * return a cached request object with the slots updated 496 // * create a new request object with the slots set 497 static AsyncGeneratorRequest* createRequest( 498 JSContext* cx, Handle<AsyncGeneratorObject*> generator, 499 CompletionKind completionKind, HandleValue completionValue, 500 Handle<PromiseObject*> promise); 501 502 // Stores the given request to the generator's cache after clearing its data 503 // slots. The cached request will be reused in the subsequent createRequest 504 // call. 505 void cacheRequest(AsyncGeneratorRequest* request) { 506 if (hasCachedRequest()) { 507 return; 508 } 509 510 request->clearData(); 511 setFixedSlot(Slot_CachedRequest, ObjectValue(*request)); 512 } 513 514 private: 515 bool hasCachedRequest() const { 516 return getFixedSlot(Slot_CachedRequest).isObject(); 517 } 518 519 AsyncGeneratorRequest* takeCachedRequest() { 520 auto request = &getFixedSlot(Slot_CachedRequest) 521 .toObject() 522 .as<AsyncGeneratorRequest>(); 523 clearCachedRequest(); 524 return request; 525 } 526 527 void clearCachedRequest() { setFixedSlot(Slot_CachedRequest, NullValue()); } 528 }; 529 530 JSObject* CreateAsyncFromSyncIterator(JSContext* cx, HandleObject iter, 531 HandleValue nextMethod); 532 533 class AsyncFromSyncIteratorObject : public NativeObject { 534 private: 535 enum AsyncFromSyncIteratorObjectSlots { 536 // Object that implements the sync iterator protocol. 537 Slot_Iterator = 0, 538 539 // The `next` property of the iterator object. 540 Slot_NextMethod = 1, 541 542 Slots 543 }; 544 545 void init(JSObject* iterator, const Value& nextMethod) { 546 setFixedSlot(Slot_Iterator, ObjectValue(*iterator)); 547 setFixedSlot(Slot_NextMethod, nextMethod); 548 } 549 550 public: 551 static const JSClass class_; 552 553 static JSObject* create(JSContext* cx, HandleObject iter, 554 HandleValue nextMethod); 555 556 JSObject* iterator() const { return &getFixedSlot(Slot_Iterator).toObject(); } 557 558 const Value& nextMethod() const { return getFixedSlot(Slot_NextMethod); } 559 }; 560 561 class AsyncIteratorObject : public NativeObject { 562 public: 563 static const JSClass class_; 564 static const JSClass protoClass_; 565 }; 566 567 // Iterator Helpers proposal 568 class AsyncIteratorHelperObject : public NativeObject { 569 public: 570 static const JSClass class_; 571 572 enum { GeneratorSlot, SlotCount }; 573 574 static_assert(GeneratorSlot == ASYNC_ITERATOR_HELPER_GENERATOR_SLOT, 575 "GeneratorSlot must match self-hosting define for generator " 576 "object slot."); 577 }; 578 579 AsyncIteratorHelperObject* NewAsyncIteratorHelper(JSContext* cx); 580 581 } // namespace js 582 583 #endif /* vm_AsyncIteration_h */