From 8e2f6a75990b2eb0df4c5e5af46162b8b1013a04 Mon Sep 17 00:00:00 2001 From: Gaming4JC <g4jc@hyperbola.info> Date: Fri, 13 Dec 2019 21:48:49 -0500 Subject: Bug 1331092 - Part 2: Implement Async Generator except yield*. Tag #1287 --- js/src/builtin/Promise.cpp | 253 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 251 insertions(+), 2 deletions(-) (limited to 'js/src/builtin') diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 8fd39e7bb..59e710935 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -12,10 +12,12 @@ #include "jscntxt.h" #include "jsexn.h" +#include "jsiter.h" #include "gc/Heap.h" #include "js/Debug.h" #include "vm/AsyncFunction.h" +#include "vm/AsyncIteration.h" #include "jsobjinlines.h" @@ -36,6 +38,16 @@ enum PromiseHandler { PromiseHandlerThrower, PromiseHandlerAsyncFunctionAwaitFulfilled, PromiseHandlerAsyncFunctionAwaitRejected, + PromiseHandlerAsyncGeneratorAwaitFulfilled, + PromiseHandlerAsyncGeneratorAwaitRejected, + + // Async Iteration proposal 6.1.1.2.1. + // Async iterator handlers take the resolved value and create new iterator + // objects. To do so it needs to forward whether the iterator is done. In + // spec, this is achieved via the [[Done]] internal slot. We enumerate both + // true and false cases here. + PromiseHandlerAsyncIteratorValueUnwrapDone, + PromiseHandlerAsyncIteratorValueUnwrapNotDone, }; enum ResolutionMode { @@ -178,6 +190,7 @@ enum ReactionRecordSlots { ReactionRecordSlot_IncumbentGlobalObject, ReactionRecordSlot_Flags, ReactionRecordSlot_HandlerArg, + ReactionRecordSlot_Generator, ReactionRecordSlots, }; @@ -185,6 +198,7 @@ enum ReactionRecordSlots { #define REACTION_FLAG_FULFILLED 0x2 #define REACTION_FLAG_IGNORE_DEFAULT_RESOLUTION 0x4 #define REACTION_FLAG_ASYNC_FUNCTION_AWAIT 0x8 +#define REACTION_FLAG_ASYNC_GENERATOR_AWAIT 0x10 // ES2016, 25.4.1.2. class PromiseReactionRecord : public NativeObject @@ -220,6 +234,22 @@ class PromiseReactionRecord : public NativeObject int32_t flags = this->flags(); return flags & REACTION_FLAG_ASYNC_FUNCTION_AWAIT; } + void setIsAsyncGeneratorAwait(Handle<AsyncGeneratorObject*> asyncGenObj) { + int32_t flags = this->flags(); + flags |= REACTION_FLAG_ASYNC_GENERATOR_AWAIT; + setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); + + setFixedSlot(ReactionRecordSlot_Generator, ObjectValue(*asyncGenObj)); + } + bool isAsyncGeneratorAwait() { + int32_t flags = this->flags(); + return flags & REACTION_FLAG_ASYNC_GENERATOR_AWAIT; + } + AsyncGeneratorObject* asyncGenerator() { + MOZ_ASSERT(isAsyncGeneratorAwait()); + return &getFixedSlot(ReactionRecordSlot_Generator).toObject() + .as<AsyncGeneratorObject>(); + } Value handler() { MOZ_ASSERT(targetState() != JS::PromiseState::Pending); uint32_t slot = targetState() == JS::PromiseState::Fulfilled @@ -846,6 +876,34 @@ AsyncFunctionAwaitPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord return true; } +static MOZ_MUST_USE bool +AsyncGeneratorAwaitPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction, + MutableHandleValue rval) +{ + MOZ_ASSERT(reaction->isAsyncGeneratorAwait()); + + RootedValue handlerVal(cx, reaction->handler()); + RootedValue argument(cx, reaction->handlerArg()); + Rooted<AsyncGeneratorObject*> asyncGenObj(cx, reaction->asyncGenerator()); + + int32_t handlerNum = int32_t(handlerVal.toNumber()); + MOZ_ASSERT(handlerNum == PromiseHandlerAsyncGeneratorAwaitFulfilled || + handlerNum == PromiseHandlerAsyncGeneratorAwaitRejected); + + // Await's handlers don't return a value, nor throw exception. + // They fail only on OOM. + if (handlerNum == PromiseHandlerAsyncGeneratorAwaitFulfilled) { + if (!AsyncGeneratorAwaitedFulfilled(cx, asyncGenObj, argument)) + return false; + } else { + if (!AsyncGeneratorAwaitedRejected(cx, asyncGenObj, argument)) + return false; + } + + rval.setUndefined(); + return true; +} + // ES2016, 25.4.2.1. /** * Callback triggering the fulfill/reject reaction for a resolved Promise, @@ -891,6 +949,8 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) Rooted<PromiseReactionRecord*> reaction(cx, &reactionObj->as<PromiseReactionRecord>()); if (reaction->isAsyncFunctionAwait()) return AsyncFunctionAwaitPromiseReactionJob(cx, reaction, args.rval()); + if (reaction->isAsyncGeneratorAwait()) + return AsyncGeneratorAwaitPromiseReactionJob(cx, reaction, args.rval()); // Step 3. RootedValue handlerVal(cx, reaction->handler()); @@ -907,11 +967,21 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) // Step 4. if (handlerNum == PromiseHandlerIdentity) { handlerResult = argument; - } else { + } else if (handlerNum == PromiseHandlerThrower) { // Step 5. - MOZ_ASSERT(handlerNum == PromiseHandlerThrower); resolutionMode = RejectMode; handlerResult = argument; + } else { + MOZ_ASSERT(handlerNum == PromiseHandlerAsyncIteratorValueUnwrapDone || + handlerNum == PromiseHandlerAsyncIteratorValueUnwrapNotDone); + + bool done = handlerNum == PromiseHandlerAsyncIteratorValueUnwrapDone; + // Async Iteration proposal 6.1.1.2.1 step 1. + RootedObject resultObj(cx, CreateIterResultObject(cx, argument, done)); + if (!resultObj) + return false; + + handlerResult = ObjectValue(*resultObj); } } else { // Step 6. @@ -2221,6 +2291,185 @@ js::AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, Hand return PerformPromiseThenWithReaction(cx, promise, reaction); } +// Async Iteration proposal 5.1 steps 2-9. +MOZ_MUST_USE bool +js::AsyncGeneratorAwait(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue value) +{ + // Step 2. + Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); + if (!promise) + return false; + + // Steps 3. + if (!ResolvePromiseInternal(cx, promise, value)) + return false; + + // Steps 4-5. + RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitFulfilled)); + RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitRejected)); + + RootedObject incumbentGlobal(cx); + if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal)) + return false; + + // Step 6 (skipped). + + // Steps 7-8. + Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, nullptr, + onFulfilled, onRejected, + nullptr, nullptr, + incumbentGlobal)); + if (!reaction) + return false; + + reaction->setIsAsyncGeneratorAwait(asyncGenObj); + + // Step 9. + return PerformPromiseThenWithReaction(cx, promise, reaction); +} + +// Async Iteration proposal 6.4.3.3. +MOZ_MUST_USE bool +js::AsyncGeneratorResolve(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue value, bool done) +{ + // Step 1 (implicit). + + // Steps 2-3. + MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); + + // Step 4. + Rooted<AsyncGeneratorRequest*> request( + cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj)); + if (!request) + return false; + + // Step 5. + RootedObject resultPromise(cx, request->promise()); + + // Step 6. + Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); + if (!promise) + return false; + + // Step 7. + if (!ResolvePromiseInternal(cx, promise, value)) + return false; + + // Steps 8-9. + RootedValue onFulfilled(cx, Int32Value(done + ? PromiseHandlerAsyncIteratorValueUnwrapDone + : PromiseHandlerAsyncIteratorValueUnwrapNotDone)); + + RootedObject incumbentGlobal(cx); + if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal)) + return false; + + // Step 10. + Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, resultPromise, onFulfilled, + UndefinedHandleValue, + nullptr, nullptr, + incumbentGlobal)); + if (!reaction) + return false; + + if (!PerformPromiseThenWithReaction(cx, promise, reaction)) + return false; + + // Step 11. + if (!AsyncGeneratorResumeNext(cx, asyncGenObj)) + return false; + + // Step 12. + return true; +} + +// Async Iteration proposal 6.4.3.4. +MOZ_MUST_USE bool +js::AsyncGeneratorReject(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue exception) +{ + // Step 1 (implicit). + + // Steps 2-3. + MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); + + // Step 4. + Rooted<AsyncGeneratorRequest*> request( + cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj)); + if (!request) + return false; + + // Step 5. + RootedObject resultPromise(cx, request->promise()); + + // Step 6. + if (!RejectMaybeWrappedPromise(cx, resultPromise, exception)) + return false; + + // Step 7. + if (!AsyncGeneratorResumeNext(cx, asyncGenObj)) + return false; + + // Step 8. + return true; +} + +// Async Iteration proposal 6.4.3.6. +MOZ_MUST_USE bool +js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, + CompletionKind completionKind, HandleValue completionValue, + MutableHandleValue result) +{ + // Step 1 (implicit). + + // Step 2. + RootedObject resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); + if (!resultPromise) + return false; + + // Step 3. + if (!asyncGenVal.isObject() || !asyncGenVal.toObject().is<AsyncGeneratorObject>()) { + // Step 3.a. + RootedValue badGeneratorError(cx); + if (!GetTypeError(cx, JSMSG_NOT_AN_ASYNC_GENERATOR, &badGeneratorError)) + return false; + + // Step 3.b. + if (!RejectMaybeWrappedPromise(cx, resultPromise, badGeneratorError)) + return false; + + // Step 3.c. + result.setObject(*resultPromise); + return true; + } + + Rooted<AsyncGeneratorObject*> asyncGenObj( + cx, &asyncGenVal.toObject().as<AsyncGeneratorObject>()); + + // Step 5 (reordered). + Rooted<AsyncGeneratorRequest*> request( + cx, AsyncGeneratorRequest::create(cx, completionKind, completionValue, resultPromise)); + if (!request) + return false; + + // Steps 4, 6. + if (!AsyncGeneratorObject::enqueueRequest(cx, asyncGenObj, request)) + return false; + + // Step 7. + if (!asyncGenObj->isExecuting()) { + // Step 8. + if (!AsyncGeneratorResumeNext(cx, asyncGenObj)) + return false; + } + + // Step 9. + result.setObject(*resultPromise); + return true; +} + // ES2016, 25.4.5.3. bool js::Promise_then(JSContext* cx, unsigned argc, Value* vp) -- cgit v1.2.3