/* -*- 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_AsyncIteration_h #define vm_AsyncIteration_h #include "js/Class.h" #include "vm/GeneratorObject.h" #include "vm/JSContext.h" #include "vm/JSObject.h" #include "vm/List.h" #include "vm/PromiseObject.h" namespace js { class AsyncGeneratorObject; extern const JSClass AsyncGeneratorFunctionClass; // Resume the async generator when the `await` operand fulfills to `value`. MOZ_MUST_USE bool AsyncGeneratorAwaitedFulfilled( JSContext* cx, Handle asyncGenObj, HandleValue value); // Resume the async generator when the `await` operand rejects with `reason`. MOZ_MUST_USE bool AsyncGeneratorAwaitedRejected( JSContext* cx, Handle asyncGenObj, HandleValue reason); // Resume the async generator after awaiting on the value passed to // AsyncGenerator#return, when the async generator was still executing. // Split into two functions depending on whether the awaited value was // fulfilled or rejected. MOZ_MUST_USE bool AsyncGeneratorYieldReturnAwaitedFulfilled( JSContext* cx, Handle asyncGenObj, HandleValue value); MOZ_MUST_USE bool AsyncGeneratorYieldReturnAwaitedRejected( JSContext* cx, Handle asyncGenObj, HandleValue reason); // AsyncGeneratorRequest record in the spec. // Stores the info from AsyncGenerator#{next,return,throw}. // // This object is reused across multiple requests as an optimization, and // stored in the Slot_CachedRequest slot. class AsyncGeneratorRequest : public NativeObject { private: enum AsyncGeneratorRequestSlots { // Int32 value with CompletionKind. // Normal: next // Return: return // Throw: throw Slot_CompletionKind = 0, // The value passed to AsyncGenerator#{next,return,throw}. Slot_CompletionValue, // The promise returned by AsyncGenerator#{next,return,throw}. Slot_Promise, Slots, }; void init(CompletionKind completionKind, const Value& completionValue, PromiseObject* promise) { setFixedSlot(Slot_CompletionKind, Int32Value(static_cast(completionKind))); setFixedSlot(Slot_CompletionValue, completionValue); setFixedSlot(Slot_Promise, ObjectValue(*promise)); } // Clear the request data for reuse. void clearData() { setFixedSlot(Slot_CompletionValue, NullValue()); setFixedSlot(Slot_Promise, NullValue()); } friend AsyncGeneratorObject; public: static const JSClass class_; static AsyncGeneratorRequest* create(JSContext* cx, CompletionKind completionKind, HandleValue completionValue, Handle promise); CompletionKind completionKind() const { return static_cast( getFixedSlot(Slot_CompletionKind).toInt32()); } JS::Value completionValue() const { return getFixedSlot(Slot_CompletionValue); } PromiseObject* promise() const { return &getFixedSlot(Slot_Promise).toObject().as(); } }; class AsyncGeneratorObject : public AbstractGeneratorObject { private: enum AsyncGeneratorObjectSlots { // Int32 value containing one of the |State| fields from below. Slot_State = AbstractGeneratorObject::RESERVED_SLOTS, // * null value if this async generator has no requests // * AsyncGeneratorRequest if this async generator has only one request // * list object if this async generator has 2 or more requests Slot_QueueOrRequest, // Cached AsyncGeneratorRequest for later use. // undefined if there's no cache. Slot_CachedRequest, Slots }; public: enum State { // "suspendedStart" in the spec. // Suspended after invocation. State_SuspendedStart, // "suspendedYield" in the spec // Suspended with `yield` expression. State_SuspendedYield, // "executing" in the spec. // Resumed from initial suspend or yield, and either running the script // or awaiting for `await` expression. State_Executing, // Part of "executing" in the spec. // Awaiting on the value passed by AsyncGenerator#return which is called // while executing. State_AwaitingYieldReturn, // "awaiting-return" in the spec. // Awaiting on the value passed by AsyncGenerator#return which is called // after completed. State_AwaitingReturn, // "completed" in the spec. // The generator is completed. State_Completed }; State state() const { return static_cast(getFixedSlot(Slot_State).toInt32()); } void setState(State state_) { setFixedSlot(Slot_State, Int32Value(state_)); } private: // Queue is implemented in 2 ways. If only one request is queued ever, // request is stored directly to the slot. Once 2 requests are queued, a // list is created and requests are appended into it, and the list is // stored to the slot. bool isSingleQueue() const { return getFixedSlot(Slot_QueueOrRequest).isNull() || getFixedSlot(Slot_QueueOrRequest) .toObject() .is(); } bool isSingleQueueEmpty() const { return getFixedSlot(Slot_QueueOrRequest).isNull(); } void setSingleQueueRequest(AsyncGeneratorRequest* request) { setFixedSlot(Slot_QueueOrRequest, ObjectValue(*request)); } void clearSingleQueueRequest() { setFixedSlot(Slot_QueueOrRequest, NullValue()); } AsyncGeneratorRequest* singleQueueRequest() const { return &getFixedSlot(Slot_QueueOrRequest) .toObject() .as(); } ListObject* queue() const { return &getFixedSlot(Slot_QueueOrRequest).toObject().as(); } void setQueue(ListObject* queue_) { setFixedSlot(Slot_QueueOrRequest, ObjectValue(*queue_)); } public: static const JSClass class_; static const JSClassOps classOps_; static AsyncGeneratorObject* create(JSContext* cx, HandleFunction asyncGen); bool isSuspendedStart() const { return state() == State_SuspendedStart; } bool isSuspendedYield() const { return state() == State_SuspendedYield; } bool isExecuting() const { return state() == State_Executing; } bool isAwaitingYieldReturn() const { return state() == State_AwaitingYieldReturn; } bool isAwaitingReturn() const { return state() == State_AwaitingReturn; } bool isCompleted() const { return state() == State_Completed; } void setSuspendedStart() { setState(State_SuspendedStart); } void setSuspendedYield() { setState(State_SuspendedYield); } void setExecuting() { setState(State_Executing); } void setAwaitingYieldReturn() { setState(State_AwaitingYieldReturn); } void setAwaitingReturn() { setState(State_AwaitingReturn); } void setCompleted() { setState(State_Completed); } static MOZ_MUST_USE bool enqueueRequest( JSContext* cx, Handle asyncGenObj, Handle request); static AsyncGeneratorRequest* dequeueRequest( JSContext* cx, Handle asyncGenObj); static AsyncGeneratorRequest* peekRequest( Handle asyncGenObj); bool isQueueEmpty() const { if (isSingleQueue()) { return isSingleQueueEmpty(); } return queue()->getDenseInitializedLength() == 0; } // This function does either of the following: // * return a cached request object with the slots updated // * create a new request object with the slots set static AsyncGeneratorRequest* createRequest( JSContext* cx, Handle asyncGenObj, CompletionKind completionKind, HandleValue completionValue, Handle promise); // Stores the given request to the generator's cache after clearing its data // slots. The cached request will be reused in the subsequent createRequest // call. void cacheRequest(AsyncGeneratorRequest* request) { if (hasCachedRequest()) { return; } request->clearData(); setFixedSlot(Slot_CachedRequest, ObjectValue(*request)); } private: bool hasCachedRequest() const { return getFixedSlot(Slot_CachedRequest).isObject(); } AsyncGeneratorRequest* takeCachedRequest() { auto request = &getFixedSlot(Slot_CachedRequest) .toObject() .as(); clearCachedRequest(); return request; } void clearCachedRequest() { setFixedSlot(Slot_CachedRequest, NullValue()); } }; JSObject* CreateAsyncFromSyncIterator(JSContext* cx, HandleObject iter, HandleValue nextMethod); class AsyncFromSyncIteratorObject : public NativeObject { private: enum AsyncFromSyncIteratorObjectSlots { // Object that implements the sync iterator protocol. Slot_Iterator = 0, // The `next` property of the iterator object. Slot_NextMethod = 1, Slots }; void init(JSObject* iterator, const Value& nextMethod) { setFixedSlot(Slot_Iterator, ObjectValue(*iterator)); setFixedSlot(Slot_NextMethod, nextMethod); } public: static const JSClass class_; static JSObject* create(JSContext* cx, HandleObject iter, HandleValue nextMethod); JSObject* iterator() const { return &getFixedSlot(Slot_Iterator).toObject(); } const Value& nextMethod() const { return getFixedSlot(Slot_NextMethod); } }; MOZ_MUST_USE bool AsyncGeneratorResume( JSContext* cx, Handle asyncGenObj, CompletionKind completionKind, HandleValue argument); class AsyncIteratorObject : public NativeObject { public: static const JSClass class_; static const JSClass protoClass_; }; } // namespace js #endif /* vm_AsyncIteration_h */