diff --git a/js/public/Class.h b/js/public/Class.h index 2e4071066a42..7b4531d8eb5a 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -852,7 +852,7 @@ struct JSClass { // application. #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5 #define JSCLASS_GLOBAL_SLOT_COUNT \ - (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 45) + (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 46) #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n))) #define JSCLASS_GLOBAL_FLAGS \ diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index d41f566cbf7c..7f2ff041bf5e 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -2299,6 +2299,165 @@ js::AsyncGeneratorAwait(JSContext* cx, Handle asyncGenObj return PerformPromiseThenWithReaction(cx, promise, reaction); } +// Async Iteration proposal 6.1.3.2.1 %AsyncFromSyncIteratorPrototype%.next +// Async Iteration proposal 6.1.3.2.2 %AsyncFromSyncIteratorPrototype%.return +// Async Iteration proposal 6.1.3.2.3 %AsyncFromSyncIteratorPrototype%.throw +bool +js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind completionKind) +{ + // Step 1. + RootedValue thisVal(cx, args.thisv()); + + // Step 2. + RootedObject resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); + if (!resultPromise) + return false; + + // Step 3. + if (!thisVal.isObject() || !thisVal.toObject().is()) { + // Step 3.a. + RootedValue badGeneratorError(cx); + if (!GetTypeError(cx, JSMSG_NOT_AN_ASYNC_ITERATOR, &badGeneratorError)) + return false; + + // Step 3.b. + if (!RejectMaybeWrappedPromise(cx, resultPromise, badGeneratorError)) + return false; + + // Step 3.c. + args.rval().setObject(*resultPromise); + return true; + } + + Rooted asyncIter( + cx, &thisVal.toObject().as()); + + // Step 4. + RootedObject iter(cx, asyncIter->iterator()); + + RootedValue resultVal(cx); + RootedValue func(cx); + if (completionKind == CompletionKind::Normal) { + // 6.1.3.2.1 steps 5-6 (partially). + if (!GetProperty(cx, iter, iter, cx->names().next, &func)) + return AbruptRejectPromise(cx, args, resultPromise, nullptr); + } else if (completionKind == CompletionKind::Return) { + // 6.1.3.2.2 steps 5-6. + if (!GetProperty(cx, iter, iter, cx->names().return_, &func)) + return AbruptRejectPromise(cx, args, resultPromise, nullptr); + + // Step 7. + if (func.isNullOrUndefined()) { + // Step 7.a. + RootedObject resultObj(cx, CreateIterResultObject(cx, args.get(0), true)); + if (!resultObj) + return AbruptRejectPromise(cx, args, resultPromise, nullptr); + + RootedValue resultVal(cx, ObjectValue(*resultObj)); + + // Step 7.b. + if (!ResolvePromiseInternal(cx, resultPromise, resultVal)) + return AbruptRejectPromise(cx, args, resultPromise, nullptr); + + // Step 7.c. + args.rval().setObject(*resultPromise); + return true; + } + } else { + // 6.1.3.2.3 steps 5-6. + MOZ_ASSERT(completionKind == CompletionKind::Throw); + if (!GetProperty(cx, iter, iter, cx->names().throw_, &func)) + return AbruptRejectPromise(cx, args, resultPromise, nullptr); + + // Step 7. + if (func.isNullOrUndefined()) { + // Step 7.a. + if (!RejectMaybeWrappedPromise(cx, resultPromise, args.get(0))) + return AbruptRejectPromise(cx, args, resultPromise, nullptr); + + // Step 7.b. + args.rval().setObject(*resultPromise); + return true; + } + } + + // 6.1.3.2.1 steps 5-6 (partially). + // 6.1.3.2.2, 6.1.3.2.3 steps 8-9. + RootedValue iterVal(cx, ObjectValue(*iter)); + FixedInvokeArgs<1> args2(cx); + args2[0].set(args.get(0)); + if (!js::Call(cx, func, iterVal, args2, &resultVal)) + return AbruptRejectPromise(cx, args, resultPromise, nullptr); + + // 6.1.3.2.1 steps 5-6 (partially). + // 6.1.3.2.2, 6.1.3.2.3 steps 10. + if (!resultVal.isObject()) { + CheckIsObjectKind kind; + switch (completionKind) { + case CompletionKind::Normal: + kind = CheckIsObjectKind::IteratorNext; + break; + case CompletionKind::Throw: + kind = CheckIsObjectKind::IteratorThrow; + break; + case CompletionKind::Return: + kind = CheckIsObjectKind::IteratorReturn; + break; + } + MOZ_ALWAYS_FALSE(ThrowCheckIsObject(cx, kind)); + return AbruptRejectPromise(cx, args, resultPromise, nullptr); + } + + RootedObject resultObj(cx, &resultVal.toObject()); + + // Following step numbers are for 6.1.3.2.1. + // For 6.1.3.2.2 and 6.1.3.2.3, steps 7-16 corresponds to steps 11-20. + + // Steps 7-8. + RootedValue value(cx); + if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value)) + return AbruptRejectPromise(cx, args, resultPromise, nullptr); + + // Steps 9-10. + RootedValue doneVal(cx); + if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal)) + return AbruptRejectPromise(cx, args, resultPromise, nullptr); + bool done = ToBoolean(doneVal); + + // Step 11. + Rooted promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); + if (!promise) + return false; + + // Step 12. + if (!ResolvePromiseInternal(cx, promise, value)) + return false; + + // Steps 13-14. + RootedValue onFulfilled(cx, Int32Value(done + ? PromiseHandlerAsyncIteratorValueUnwrapDone + : PromiseHandlerAsyncIteratorValueUnwrapNotDone)); + + RootedObject incumbentGlobal(cx); + if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal)) + return false; + + // Step 15. + Rooted reaction(cx, NewReactionRecord(cx, resultPromise, onFulfilled, + UndefinedHandleValue, + nullptr, nullptr, + incumbentGlobal)); + if (!reaction) + return false; + + if (!PerformPromiseThenWithReaction(cx, promise, reaction)) + return false; + + // Step 16. + args.rval().setObject(*resultPromise); + return true; +} + // Async Iteration proposal 6.4.3.3. MOZ_MUST_USE bool js::AsyncGeneratorResolve(JSContext* cx, Handle asyncGenObj, diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h index b9abb7a27fab..1438769facb1 100644 --- a/js/src/builtin/Promise.h +++ b/js/src/builtin/Promise.h @@ -152,6 +152,9 @@ MOZ_MUST_USE bool AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, CompletionKind completionKind, HandleValue completionValue, MutableHandleValue result); +bool +AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind completionKind); + /** * A PromiseTask represents a task that can be dispatched to a helper thread * (via StartPromiseTask), executed (by implementing PromiseTask::execute()), diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 1f980b240265..8316e062e15f 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2064,7 +2064,7 @@ class ForOfLoopControl : public LoopControl bool emitIteratorClose(BytecodeEmitter* bce, CompletionKind completionKind = CompletionKind::Normal) { ptrdiff_t start = bce->offset(); - if (!bce->emitIteratorClose(completionKind, allowSelfHosted_)) + if (!bce->emitIteratorClose(IteratorKind::Sync, completionKind, allowSelfHosted_)) return false; ptrdiff_t end = bce->offset(); return bce->tryNoteList.append(JSTRY_FOR_OF_ITERCLOSE, 0, start, end); @@ -3676,6 +3676,18 @@ BytecodeEmitter::emitFinishIteratorResult(bool done) return true; } +bool +BytecodeEmitter::emitToIteratorResult(bool done) +{ + if (!emitPrepareIteratorResult()) // VALUE OBJ + return false; + if (!emit1(JSOP_SWAP)) // OBJ VALUE + return false; + if (!emitFinishIteratorResult(done)) // RESULT + return false; + return true; +} + bool BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc, bool callContext) { @@ -5231,7 +5243,8 @@ BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted /* = false } bool -BytecodeEmitter::emitIteratorClose(CompletionKind completionKind /* = CompletionKind::Normal */, +BytecodeEmitter::emitIteratorClose(IteratorKind iterKind /* = IteratorKind::Sync */, + CompletionKind completionKind /* = CompletionKind::Normal */, bool allowSelfHosted /* = false */) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, @@ -5316,6 +5329,11 @@ BytecodeEmitter::emitIteratorClose(CompletionKind completionKind /* = Completion return false; checkTypeSet(JSOP_CALL); + if (iterKind == IteratorKind::Async) { + if (!emitAwait()) // ... ... RESULT + return false; + } + if (completionKind == CompletionKind::Throw) { if (!emit1(JSOP_SWAP)) // ... RET ITER RESULT UNDEF return false; @@ -5325,7 +5343,7 @@ BytecodeEmitter::emitIteratorClose(CompletionKind completionKind /* = Completion if (!tryCatch->emitCatch()) // ... RET ITER RESULT return false; - // Just ignore the exception thrown by call. + // Just ignore the exception thrown by call and await. if (!emit1(JSOP_EXCEPTION)) // ... RET ITER RESULT EXC return false; if (!emit1(JSOP_POP)) // ... RET ITER RESULT @@ -6746,6 +6764,63 @@ BytecodeEmitter::emitIterator() return true; } +bool +BytecodeEmitter::emitAsyncIterator() +{ + // Convert iterable to iterator. + if (!emit1(JSOP_DUP)) // OBJ OBJ + return false; + if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::asyncIterator))) // OBJ OBJ @@ASYNCITERATOR + return false; + if (!emitElemOpBase(JSOP_CALLELEM)) // OBJ ITERFN + return false; + + IfThenElseEmitter ifAsyncIterIsUndefined(this); + if (!emit1(JSOP_DUP)) // OBJ ITERFN ITERFN + return false; + if (!emit1(JSOP_UNDEFINED)) // OBJ ITERFN ITERFN UNDEF + return false; + if (!emit1(JSOP_EQ)) // OBJ ITERFN EQ + return false; + if (!ifAsyncIterIsUndefined.emitIfElse()) // OBJ ITERFN + return false; + + if (!emit1(JSOP_POP)) // OBJ + return false; + if (!emit1(JSOP_DUP)) // OBJ OBJ + return false; + if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::iterator))) // OBJ OBJ @@ITERATOR + return false; + if (!emitElemOpBase(JSOP_CALLELEM)) // OBJ ITERFN + return false; + if (!emit1(JSOP_SWAP)) // ITERFN OBJ + return false; + if (!emitCall(JSOP_CALLITER, 0)) // ITER + return false; + checkTypeSet(JSOP_CALLITER); + if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) // ITER + return false; + + if (!emit1(JSOP_TOASYNCITER)) // ITER + return false; + + if (!ifAsyncIterIsUndefined.emitElse()) // OBJ ITERFN + return false; + + if (!emit1(JSOP_SWAP)) // ITERFN OBJ + return false; + if (!emitCall(JSOP_CALLITER, 0)) // ITER + return false; + checkTypeSet(JSOP_CALLITER); + if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) // ITER + return false; + + if (!ifAsyncIterIsUndefined.emitEnd()) // ITER + return false; + + return true; +} + bool BytecodeEmitter::emitSpread(bool allowSelfHosted) { @@ -8367,13 +8442,8 @@ BytecodeEmitter::emitYield(ParseNode* pn) } bool -BytecodeEmitter::emitAwait(ParseNode* pn) +BytecodeEmitter::emitAwait() { - MOZ_ASSERT(sc->isFunctionBox()); - MOZ_ASSERT(pn->getOp() == JSOP_AWAIT); - - if (!emitTree(pn->pn_kid)) - return false; if (!emitGetDotGenerator()) return false; if (!emitYieldOp(JSOP_AWAIT)) @@ -8381,16 +8451,34 @@ BytecodeEmitter::emitAwait(ParseNode* pn) return true; } +bool +BytecodeEmitter::emitAwait(ParseNode* pn) +{ + MOZ_ASSERT(sc->isFunctionBox()); + MOZ_ASSERT(pn->getOp() == JSOP_AWAIT); + + if (!emitTree(pn->pn_kid)) + return false; + return emitAwait(); +} + bool BytecodeEmitter::emitYieldStar(ParseNode* iter) { MOZ_ASSERT(sc->isFunctionBox()); MOZ_ASSERT(sc->asFunctionBox()->isStarGenerator()); + bool isAsyncGenerator = sc->asFunctionBox()->isAsync(); + if (!emitTree(iter)) // ITERABLE return false; - if (!emitIterator()) // ITER - return false; + if (isAsyncGenerator) { + if (!emitAsyncIterator()) // ITER + return false; + } else { + if (!emitIterator()) // ITER + return false; + } // Initial send value is undefined. if (!emit1(JSOP_UNDEFINED)) // ITER RECEIVED @@ -8448,7 +8536,8 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter) // // If the iterator does not have a "throw" method, it calls IteratorClose // and then throws a TypeError. - if (!emitIteratorClose()) // ITER RESULT EXCEPTION + IteratorKind iterKind = isAsyncGenerator ? IteratorKind::Async : IteratorKind::Sync; + if (!emitIteratorClose(iterKind)) // ITER RESULT EXCEPTION return false; if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_ITERATOR_NO_THROW)) // throw return false; @@ -8464,6 +8553,12 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter) if (!emitCall(JSOP_CALL, 1, iter)) // ITER OLDRESULT RESULT return false; checkTypeSet(JSOP_CALL); + + if (isAsyncGenerator) { + if (!emitAwait()) // ITER OLDRESULT RESULT + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) // ITER OLDRESULT RESULT return false; if (!emit1(JSOP_SWAP)) // ITER RESULT OLDRESULT @@ -8530,6 +8625,11 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter) return false; checkTypeSet(JSOP_CALL); + if (iterKind == IteratorKind::Async) { + if (!emitAwait()) // ... FTYPE FVALUE RESULT + return false; + } + // Step v. if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ITER OLDRESULT FTYPE FVALUE RESULT return false; @@ -8602,9 +8702,15 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter) return false; if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT return false; + checkTypeSet(JSOP_CALL); + + if (isAsyncGenerator) { + if (!emitAwait()) // ITER RESULT RESULT + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ITER RESULT return false; - checkTypeSet(JSOP_CALL); MOZ_ASSERT(this->stackDepth == startDepth); if (!emitJumpTargetAndPatch(checkResult)) // checkResult: diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 46019548530b..597b38ee7236 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -10,6 +10,7 @@ #define frontend_BytecodeEmitter_h #include "jscntxt.h" +#include "jsiter.h" #include "jsopcode.h" #include "jsscript.h" @@ -605,6 +606,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitPrepareIteratorResult(); MOZ_MUST_USE bool emitFinishIteratorResult(bool done); MOZ_MUST_USE bool iteratorResultShape(unsigned* shape); + MOZ_MUST_USE bool emitToIteratorResult(bool done); MOZ_MUST_USE bool emitGetDotGenerator(); @@ -612,6 +614,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitYield(ParseNode* pn); MOZ_MUST_USE bool emitYieldOp(JSOp op); MOZ_MUST_USE bool emitYieldStar(ParseNode* iter); + MOZ_MUST_USE bool emitAwait(); MOZ_MUST_USE bool emitAwait(ParseNode* pn); MOZ_MUST_USE bool emitPropLHS(ParseNode* pn); @@ -694,10 +697,13 @@ struct MOZ_STACK_CLASS BytecodeEmitter // It will replace that stack value with the corresponding iterator MOZ_MUST_USE bool emitIterator(); + MOZ_MUST_USE bool emitAsyncIterator(); + // Pops iterator from the top of the stack. Pushes the result of |.next()| // onto the stack. MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, bool allowSelfHosted = false); - MOZ_MUST_USE bool emitIteratorClose(CompletionKind completionKind = CompletionKind::Normal, + MOZ_MUST_USE bool emitIteratorClose(IteratorKind iterKind = IteratorKind::Sync, + CompletionKind completionKind = CompletionKind::Normal, bool allowSelfHosted = false); template diff --git a/js/src/js.msg b/js/src/js.msg index b191fdc60073..fb7b262a7345 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -594,3 +594,5 @@ MSG_DEF(JSMSG_ITERATOR_NO_THROW, 0, JSEXN_TYPEERR, "iterator does not have // Async Iteration MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR, 0, JSEXN_TYPEERR, "Not an async generator") +MSG_DEF(JSMSG_NOT_AN_ASYNC_ITERATOR, 0, JSEXN_TYPEERR, "Not an async from sync iterator") +MSG_DEF(JSMSG_GET_ASYNC_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.asyncIterator]() returned a non-object value") diff --git a/js/src/jsiter.h b/js/src/jsiter.h index 350c758bb3a9..ada903dbb6d7 100644 --- a/js/src/jsiter.h +++ b/js/src/jsiter.h @@ -221,6 +221,8 @@ InitLegacyIteratorClass(JSContext* cx, HandleObject obj); extern JSObject* InitStopIterationClass(JSContext* cx, HandleObject obj); +enum class IteratorKind { Sync, Async }; + } /* namespace js */ #endif /* jsiter_h */ diff --git a/js/src/vm/AsyncIteration.cpp b/js/src/vm/AsyncIteration.cpp index 04effe1b101f..19d0617f33d7 100644 --- a/js/src/vm/AsyncIteration.cpp +++ b/js/src/vm/AsyncIteration.cpp @@ -148,6 +148,69 @@ js::AsyncGeneratorAwaitedRejected(JSContext* cx, Handle a return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Throw, reason); } +const Class AsyncFromSyncIteratorObject::class_ = { + "AsyncFromSyncIteratorObject", + JSCLASS_HAS_RESERVED_SLOTS(AsyncFromSyncIteratorObject::Slots) +}; + +// Async Iteration proposal 6.1.3.1. +JSObject* +js::CreateAsyncFromSyncIterator(JSContext* cx, HandleObject iter) +{ + // Step 1 (implicit). + // Done in bytecode emitted by emitAsyncIterator. + + // Steps 2-4. + return AsyncFromSyncIteratorObject::create(cx, iter); +} + +// Async Iteration proposal 6.1.3.1 steps 2-4. +/* static */ JSObject* +AsyncFromSyncIteratorObject::create(JSContext* cx, HandleObject iter) +{ + // Step 2. + RootedObject proto(cx, GlobalObject::getOrCreateAsyncFromSyncIteratorPrototype(cx, + cx->global())); + if (!proto) + return nullptr; + + RootedObject obj(cx, NewNativeObjectWithGivenProto(cx, &class_, proto)); + if (!obj) + return nullptr; + + Handle asyncIter = obj.as(); + + // Step 3. + asyncIter->setIterator(iter); + + // Step 4. + return asyncIter; +} + +// Async Iteration proposal 6.1.3.2.1 %AsyncFromSyncIteratorPrototype%.next. +static bool +AsyncFromSyncIteratorNext(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return AsyncFromSyncIteratorMethod(cx, args, CompletionKind::Normal); +} + +// Async Iteration proposal 6.1.3.2.2 %AsyncFromSyncIteratorPrototype%.return. +static bool +AsyncFromSyncIteratorReturn(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return AsyncFromSyncIteratorMethod(cx, args, CompletionKind::Return); +} + +// Async Iteration proposal 6.1.3.2.3 %AsyncFromSyncIteratorPrototype%.throw. +static bool +AsyncFromSyncIteratorThrow(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return AsyncFromSyncIteratorMethod(cx, args, CompletionKind::Throw); +} + // Async Iteration proposal 6.4.1.2 AsyncGenerator.prototype.next. static bool AsyncGeneratorNext(JSContext* cx, unsigned argc, Value* vp) @@ -413,17 +476,17 @@ js::AsyncGeneratorResumeNext(JSContext* cx, Handle asyncG return AsyncGeneratorResume(cx, asyncGenObj, completionKind, argument); } -// Async Iteration proposal 6.2.1.2 (partially). +// Async Iteration proposal 6.2.1.3 (partially). // Most steps are done in generator. static MOZ_MUST_USE bool AsyncGeneratorYield(JSContext* cx, Handle asyncGenObj, - HandleValue value, bool done) + HandleValue value) { - // Step 6. + // Step 5. asyncGenObj->setSuspendedYield(); - // Step 10.c. - return AsyncGeneratorResolve(cx, asyncGenObj, value, done); + // Step 8. + return AsyncGeneratorResolve(cx, asyncGenObj, value, false); } // Async Iteration proposal 6.4.3.5 steps 12-14, 16-20. @@ -451,41 +514,29 @@ AsyncGeneratorResume(JSContext* cx, Handle asyncGenObj, return AsyncGeneratorThrown(cx, asyncGenObj); } + if (asyncGenObj->generatorObj()->isAfterAwait()) + return AsyncGeneratorAwait(cx, asyncGenObj, result); + // The following code corresponds to the following 3 cases: // * yield - // * await + // * yield* // * return - // For await and return, property access is done on an internal result + // For yield and return, property access is done on an internal result // object and it's not observable. - // For yield, it's done on an user-provided result object, and it's - // observable, so perform in that order in all cases. + // For yield*, it's done on a possibly user-provided result object, and + // it's observable. + // 2.2.1 yield* steps 6.a.vii, 6.b.ii.7, 6.c.ix. RootedObject resultObj(cx, &result.toObject()); RootedValue value(cx); - RootedValue doneVal(cx); - - // 6.2.1.2 step 10.a. if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value)) return false; - // 6.2.1.2 step 10.b. - if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal)) - return false; - - // 6.2.1.2 step 10.c. if (asyncGenObj->generatorObj()->isAfterYield()) - return AsyncGeneratorYield(cx, asyncGenObj, value, ToBoolean(doneVal)); + return AsyncGeneratorYield(cx, asyncGenObj, value); // 6.4.3.2 step 5.d-g. - if (ToBoolean(doneVal)) { - MOZ_ASSERT(!asyncGenObj->generatorObj()->isAfterAwait()); - return AsyncGeneratorReturned(cx, asyncGenObj, value); - } - - MOZ_ASSERT(asyncGenObj->generatorObj()->isAfterAwait()); - - // 5.1 steps 2-9. - return AsyncGeneratorAwait(cx, asyncGenObj, value); + return AsyncGeneratorReturned(cx, asyncGenObj, value); } static const JSFunctionSpec async_iterator_proto_methods[] = { @@ -493,6 +544,13 @@ static const JSFunctionSpec async_iterator_proto_methods[] = { JS_FS_END }; +static const JSFunctionSpec async_from_sync_iter_methods[] = { + JS_FN("next", AsyncFromSyncIteratorNext, 1, 0), + JS_FN("throw", AsyncFromSyncIteratorThrow, 1, 0), + JS_FN("return", AsyncFromSyncIteratorReturn, 1, 0), + JS_FS_END +}; + static const JSFunctionSpec async_generator_methods[] = { JS_FN("next", AsyncGeneratorNext, 1, 0), JS_FN("throw", AsyncGeneratorThrow, 1, 0), @@ -513,6 +571,19 @@ GlobalObject::initAsyncGenerators(JSContext* cx, Handle global) if (!DefinePropertiesAndFunctions(cx, asyncIterProto, nullptr, async_iterator_proto_methods)) return false; + // Async Iteration proposal 6.1.3.2 %AsyncFromSyncIteratorPrototype%. + RootedObject asyncFromSyncIterProto( + cx, GlobalObject::createBlankPrototypeInheriting(cx, global, &PlainObject::class_, + asyncIterProto)); + if (!asyncFromSyncIterProto) + return false; + if (!DefinePropertiesAndFunctions(cx, asyncFromSyncIterProto, nullptr, + async_from_sync_iter_methods) || + !DefineToStringTag(cx, asyncFromSyncIterProto, cx->names().AsyncFromSyncIterator)) + { + return false; + } + // Async Iteration proposal 6.4.1 %AsyncGeneratorPrototype%. RootedObject asyncGenProto( cx, GlobalObject::createBlankPrototypeInheriting(cx, global, &PlainObject::class_, @@ -557,6 +628,7 @@ GlobalObject::initAsyncGenerators(JSContext* cx, Handle global) } global->setReservedSlot(ASYNC_ITERATOR_PROTO, ObjectValue(*asyncIterProto)); + global->setReservedSlot(ASYNC_FROM_SYNC_ITERATOR_PROTO, ObjectValue(*asyncFromSyncIterProto)); global->setReservedSlot(ASYNC_GENERATOR, ObjectValue(*asyncGenerator)); global->setReservedSlot(ASYNC_GENERATOR_FUNCTION, ObjectValue(*asyncGenFunction)); global->setReservedSlot(ASYNC_GENERATOR_PROTO, ObjectValue(*asyncGenProto)); diff --git a/js/src/vm/AsyncIteration.h b/js/src/vm/AsyncIteration.h index c0135283fa6b..974c209a01cf 100644 --- a/js/src/vm/AsyncIteration.h +++ b/js/src/vm/AsyncIteration.h @@ -196,6 +196,32 @@ class AsyncGeneratorObject : public NativeObject } }; +JSObject* +CreateAsyncFromSyncIterator(JSContext* cx, HandleObject iter); + +class AsyncFromSyncIteratorObject : public NativeObject +{ + private: + enum AsyncFromSyncIteratorObjectSlots { + Slot_Iterator = 0, + Slots + }; + + void setIterator(HandleObject iterator_) { + setFixedSlot(Slot_Iterator, ObjectValue(*iterator_)); + } + + public: + static const Class class_; + + static JSObject* + create(JSContext* cx, HandleObject iter); + + JSObject* iterator() const { + return &getFixedSlot(Slot_Iterator).toObject(); + } +}; + MOZ_MUST_USE bool AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenObj); diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index ffb4d5f25364..758ccb3276a1 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -30,6 +30,7 @@ macro(ArrayValuesAt, ArrayValuesAt, "ArrayValuesAt") \ macro(as, as, "as") \ macro(Async, Async, "Async") \ + macro(AsyncFromSyncIterator, AsyncFromSyncIterator, "Async-from-Sync Iterator") \ macro(AsyncFunction, AsyncFunction, "AsyncFunction") \ macro(AsyncGenerator, AsyncGenerator, "AsyncGenerator") \ macro(AsyncGeneratorFunction, AsyncGeneratorFunction, "AsyncGeneratorFunction") \ diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index c9cbd94ccdb7..faa7497d88e3 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -99,6 +99,7 @@ class GlobalObject : public NativeObject ASYNC_FUNCTION_PROTO, ASYNC_FUNCTION, ASYNC_ITERATOR_PROTO, + ASYNC_FROM_SYNC_ITERATOR_PROTO, ASYNC_GENERATOR, ASYNC_GENERATOR_FUNCTION, ASYNC_GENERATOR_PROTO, @@ -640,6 +641,12 @@ class GlobalObject : public NativeObject initAsyncGenerators)); } + static NativeObject* + getOrCreateAsyncFromSyncIteratorPrototype(JSContext* cx, Handle global) { + return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_FROM_SYNC_ITERATOR_PROTO, + initAsyncGenerators)); + } + static NativeObject* getOrCreateAsyncGenerator(JSContext* cx, Handle global) { return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_GENERATOR, diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index c3899ab5d775..1aa8749a89e5 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1923,7 +1923,6 @@ CASE(EnableInterruptsPseudoOpcode) /* Various 1-byte no-ops. */ CASE(JSOP_NOP) CASE(JSOP_NOP_DESTRUCTURING) -CASE(JSOP_UNUSED210) CASE(JSOP_UNUSED211) CASE(JSOP_UNUSED220) CASE(JSOP_UNUSED221) @@ -3578,6 +3577,17 @@ CASE(JSOP_TOASYNCGEN) } END_CASE(JSOP_TOASYNCGEN) +CASE(JSOP_TOASYNCITER) +{ + ReservedRooted iter(&rootObject1, ®S.sp[-1].toObject()); + JSObject* asyncIter = CreateAsyncFromSyncIterator(cx, iter); + if (!asyncIter) + goto error; + + REGS.sp[-1].setObject(*asyncIter); +} +END_CASE(JSOP_TOASYNCITER) + CASE(JSOP_SETFUNNAME) { MOZ_ASSERT(REGS.stackDepth() >= 2); @@ -5099,6 +5109,10 @@ js::ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind) case CheckIsObjectKind::GetIterator: JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_GET_ITER_RETURNED_PRIMITIVE); break; + case CheckIsObjectKind::GetAsyncIterator: + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_GET_ASYNC_ITER_RETURNED_PRIMITIVE); + break; default: MOZ_CRASH("Unknown kind"); } diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 5e437ba671e0..678feb7c1745 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -556,7 +556,8 @@ enum class CheckIsObjectKind : uint8_t { IteratorNext, IteratorReturn, IteratorThrow, - GetIterator + GetIterator, + GetAsyncIterator }; bool diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 8896c4a9ec67..602cfe3fa97e 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2152,7 +2152,15 @@ * Stack: promise, gen => resolved */ \ macro(JSOP_AWAIT, 209, "await", NULL, 4, 2, 1, JOF_UINT24) \ - macro(JSOP_UNUSED210, 210, "unused210", NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Pops the iterator from the top of the stack, and create async iterator + * from it and push the async iterator back onto the stack. + * Category: Statements + * Type: Generator + * Operands: + * Stack: iter => asynciter + */ \ + macro(JSOP_TOASYNCITER, 210, "toasynciter", NULL, 1, 1, 1, JOF_BYTE) \ macro(JSOP_UNUSED211, 211, "unused211", NULL, 1, 0, 0, JOF_BYTE) \ /* * Initializes generator frame, creates a generator and pushes it on the