diff options
author | Moonchild <moonchild@palemoon.org> | 2019-12-17 21:47:18 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-12-17 21:47:18 +0000 |
commit | 07d0bcbf112a4e274905837c6ea0b0212b51e4e3 (patch) | |
tree | 3b43cb63b33d82d4965d402aca39028836983bb4 | |
parent | e2de507e0261c9b138cd3cf5356c21eca3e7a28d (diff) | |
parent | 6c3e42ac6427fabaf83b5acc7877aa3d15117125 (diff) | |
download | UXP-07d0bcbf112a4e274905837c6ea0b0212b51e4e3.tar UXP-07d0bcbf112a4e274905837c6ea0b0212b51e4e3.tar.gz UXP-07d0bcbf112a4e274905837c6ea0b0212b51e4e3.tar.lz UXP-07d0bcbf112a4e274905837c6ea0b0212b51e4e3.tar.xz UXP-07d0bcbf112a4e274905837c6ea0b0212b51e4e3.zip |
Merge pull request #1327 from g4jc/async_iteration
Implement Async Iteration in SpiderMonkey
This resolves #1287
80 files changed, 3191 insertions, 727 deletions
diff --git a/js/public/Class.h b/js/public/Class.h index 67c0cbca8..f4fa9ccaf 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -779,7 +779,7 @@ struct JSClass { // application. #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5 #define JSCLASS_GLOBAL_SLOT_COUNT \ - (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 40) + (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 45) #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/AsyncIteration.js b/js/src/builtin/AsyncIteration.js new file mode 100644 index 000000000..029c7c0f8 --- /dev/null +++ b/js/src/builtin/AsyncIteration.js @@ -0,0 +1,7 @@ +/* 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/. */ + +function AsyncIteratorIdentity() { + return this; +} diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 1d068f8c6..4900a91cb 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -11,11 +11,13 @@ #include "mozilla/TimeStamp.h" #include "jscntxt.h" +#include "jsexn.h" +#include "jsiter.h" #include "gc/Heap.h" #include "js/Debug.h" #include "vm/AsyncFunction.h" -#include "vm/SelfHosting.h" +#include "vm/AsyncIteration.h" #include "jsobjinlines.h" @@ -31,10 +33,34 @@ MillisecondsSinceStartup() return (now - mozilla::TimeStamp::ProcessCreation(ignored)).ToMilliseconds(); } -#define PROMISE_HANDLER_IDENTITY 0 -#define PROMISE_HANDLER_THROWER 1 -#define PROMISE_HANDLER_AWAIT_FULFILLED 2 -#define PROMISE_HANDLER_AWAIT_REJECTED 3 +enum PromiseHandler { + PromiseHandlerIdentity = 0, + PromiseHandlerThrower, + + // ES 2018 draft 25.5.5.4-5. + PromiseHandlerAsyncFunctionAwaitedFulfilled, + PromiseHandlerAsyncFunctionAwaitedRejected, + + // Async Iteration proposal 4.1. + PromiseHandlerAsyncGeneratorAwaitedFulfilled, + PromiseHandlerAsyncGeneratorAwaitedRejected, + + // Async Iteration proposal 11.4.3.5.1-2. + PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled, + PromiseHandlerAsyncGeneratorResumeNextReturnRejected, + + // Async Iteration proposal 11.4.3.7 steps 8.c-e. + PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled, + PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected, + + // Async Iteration proposal 11.1.3.2.5. + // Async-from-Sync 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. + PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone, + PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone, +}; enum ResolutionMode { ResolveMode, @@ -47,9 +73,8 @@ enum ResolveFunctionSlots { }; enum RejectFunctionSlots { - RejectFunctionSlot_Promise = ResolveFunctionSlot_Promise, + RejectFunctionSlot_Promise = 0, RejectFunctionSlot_ResolveFunction, - RejectFunctionSlot_PromiseAllData = RejectFunctionSlot_ResolveFunction, }; enum PromiseAllResolveElementFunctionSlots { @@ -86,7 +111,7 @@ class PromiseAllDataHolder : public NativeObject static const Class class_; JSObject* promiseObj() { return &getFixedSlot(PromiseAllDataHolderSlot_Promise).toObject(); } JSObject* resolveObj() { - return getFixedSlot(PromiseAllDataHolderSlot_ResolveFunction).toObjectOrNull(); + return &getFixedSlot(PromiseAllDataHolderSlot_ResolveFunction).toObject(); } Value valuesArray() { return getFixedSlot(PromiseAllDataHolderSlot_ValuesArray); } int32_t remainingCount() { @@ -126,7 +151,7 @@ NewPromiseAllDataHolder(JSContext* cx, HandleObject resultPromise, HandleValue v dataHolder->setFixedSlot(PromiseAllDataHolderSlot_Promise, ObjectValue(*resultPromise)); dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(1)); dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ValuesArray, valuesArray); - dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ResolveFunction, ObjectOrNullValue(resolve)); + dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ResolveFunction, ObjectValue(*resolve)); return dataHolder; } @@ -177,13 +202,15 @@ enum ReactionRecordSlots { ReactionRecordSlot_IncumbentGlobalObject, ReactionRecordSlot_Flags, ReactionRecordSlot_HandlerArg, + ReactionRecordSlot_Generator, ReactionRecordSlots, }; #define REACTION_FLAG_RESOLVED 0x1 #define REACTION_FLAG_FULFILLED 0x2 #define REACTION_FLAG_IGNORE_DEFAULT_RESOLUTION 0x4 -#define REACTION_FLAG_AWAIT 0x8 +#define REACTION_FLAG_ASYNC_FUNCTION 0x8 +#define REACTION_FLAG_ASYNC_GENERATOR 0x10 // ES2016, 25.4.1.2. class PromiseReactionRecord : public NativeObject @@ -210,14 +237,30 @@ class PromiseReactionRecord : public NativeObject flags |= REACTION_FLAG_FULFILLED; setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); } - void setIsAwait() { + void setIsAsyncFunction() { + int32_t flags = this->flags(); + flags |= REACTION_FLAG_ASYNC_FUNCTION; + setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); + } + bool isAsyncFunction() { int32_t flags = this->flags(); - flags |= REACTION_FLAG_AWAIT; + return flags & REACTION_FLAG_ASYNC_FUNCTION; + } + void setIsAsyncGenerator(Handle<AsyncGeneratorObject*> asyncGenObj) { + int32_t flags = this->flags(); + flags |= REACTION_FLAG_ASYNC_GENERATOR; setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); + + setFixedSlot(ReactionRecordSlot_Generator, ObjectValue(*asyncGenObj)); } - bool isAwait() { + bool isAsyncGenerator() { int32_t flags = this->flags(); - return flags & REACTION_FLAG_AWAIT; + return flags & REACTION_FLAG_ASYNC_GENERATOR; + } + AsyncGeneratorObject* asyncGenerator() { + MOZ_ASSERT(isAsyncGenerator()); + return &getFixedSlot(ReactionRecordSlot_Generator).toObject() + .as<AsyncGeneratorObject>(); } Value handler() { MOZ_ASSERT(targetState() != JS::PromiseState::Pending); @@ -277,12 +320,6 @@ CreateResolvingFunctions(JSContext* cx, HandleValue promise, if (!reject) return false; - // TODO: remove this once all self-hosted promise code is gone. - // The resolving functions are trusted, so self-hosted code should be able - // to call them using callFunction instead of callContentFunction. - resolve->setFlags(resolve->flags() | JSFunction::SELF_HOSTED); - reject->setFlags(reject->flags() | JSFunction::SELF_HOSTED); - resolve->setExtendedSlot(ResolveFunctionSlot_Promise, promise); resolve->setExtendedSlot(ResolveFunctionSlot_RejectFunction, ObjectValue(*reject)); @@ -319,28 +356,27 @@ RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp) return true; } + // Step 5. + // Here, we only remove the Promise reference from the resolution + // functions. Actually marking it as fulfilled/rejected happens later. + ClearResolutionFunctionSlots(reject); + RootedObject promise(cx, &promiseVal.toObject()); // In some cases the Promise reference on the resolution function won't // have been removed during resolution, so we need to check that here, // too. if (promise->is<PromiseObject>() && - PromiseHasAnyFlag(promise->as<PromiseObject>(), PROMISE_FLAG_RESOLVED)) + promise->as<PromiseObject>().state() != JS::PromiseState::Pending) { - args.rval().setUndefined(); return true; } - // Step 5. - // Here, we only remove the Promise reference from the resolution - // functions. Actually marking it as fulfilled/rejected happens later. - ClearResolutionFunctionSlots(reject); - // Step 6. - bool result = RejectMaybeWrappedPromise(cx, promise, reasonVal); - if (result) - args.rval().setUndefined(); - return result; + if (!RejectMaybeWrappedPromise(cx, promise, reasonVal)) + return false; + args.rval().setUndefined(); + return true; } static MOZ_MUST_USE bool FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, @@ -351,16 +387,28 @@ static MOZ_MUST_USE bool EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue thenable, HandleValue thenVal); -// ES2016, 25.4.1.3.2, steps 7-13. +// ES2016, 25.4.1.3.2, steps 6-13. static MOZ_MUST_USE bool ResolvePromiseInternal(JSContext* cx, HandleObject promise, HandleValue resolutionVal) { - // Step 7. + // Step 7 (reordered). if (!resolutionVal.isObject()) return FulfillMaybeWrappedPromise(cx, promise, resolutionVal); RootedObject resolution(cx, &resolutionVal.toObject()); + // Step 6. + if (resolution == promise) { + // Step 6.a. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF); + RootedValue selfResolutionError(cx); + MOZ_ALWAYS_TRUE(GetAndClearException(cx, &selfResolutionError)); + + // Step 6.b. + return RejectMaybeWrappedPromise(cx, promise, selfResolutionError); + } + // Step 8. RootedValue thenVal(cx); bool status = GetProperty(cx, resolution, resolution, cx->names().then, &thenVal); @@ -398,10 +446,7 @@ ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp) RootedFunction resolve(cx, &args.callee().as<JSFunction>()); RootedValue resolutionVal(cx, args.get(0)); - // Steps 1-2. - RootedValue promiseVal(cx, resolve->getExtendedSlot(ResolveFunctionSlot_Promise)); - - // Steps 3-4. + // Steps 3-4 (reordered). // We use the reference to the reject function as a signal for whether // the resolve or reject function was already called, at which point // the references on each of the functions are cleared. @@ -410,43 +455,28 @@ ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp) return true; } - RootedObject promise(cx, &promiseVal.toObject()); + // Steps 1-2 (reordered). + RootedObject promise(cx, &resolve->getExtendedSlot(ResolveFunctionSlot_Promise).toObject()); + + // Step 5. + // Here, we only remove the Promise reference from the resolution + // functions. Actually marking it as fulfilled/rejected happens later. + ClearResolutionFunctionSlots(resolve); // In some cases the Promise reference on the resolution function won't // have been removed during resolution, so we need to check that here, // too. if (promise->is<PromiseObject>() && - PromiseHasAnyFlag(promise->as<PromiseObject>(), PROMISE_FLAG_RESOLVED)) + promise->as<PromiseObject>().state() != JS::PromiseState::Pending) { - args.rval().setUndefined(); return true; } - // Step 5. - // Here, we only remove the Promise reference from the resolution - // functions. Actually marking it as fulfilled/rejected happens later. - ClearResolutionFunctionSlots(resolve); - - // Step 6. - if (resolutionVal == promiseVal) { - // Step 6.a. - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, - JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF); - RootedValue selfResolutionError(cx); - bool status = GetAndClearException(cx, &selfResolutionError); - MOZ_ASSERT(status); - - // Step 6.b. - status = RejectMaybeWrappedPromise(cx, promise, selfResolutionError); - if (status) - args.rval().setUndefined(); - return status; - } - - bool status = ResolvePromiseInternal(cx, promise, resolutionVal); - if (status) - args.rval().setUndefined(); - return status; + // Steps 6-13. + if (!ResolvePromiseInternal(cx, promise, resolutionVal)) + return false; + args.rval().setUndefined(); + return true; } static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp); @@ -652,7 +682,7 @@ enum GetCapabilitiesExecutorSlots { }; static MOZ_MUST_USE PromiseObject* -CreatePromiseObjectWithDefaultResolution(JSContext* cx) +CreatePromiseObjectWithoutResolutionFunctions(JSContext* cx) { Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx)); if (!promise) @@ -687,7 +717,7 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, // in the list passed to all/race, which (potentially) means exposing them // to content. if (canOmitResolutionFunctions && IsNativeFunction(cVal, PromiseConstructor)) { - promise.set(CreatePromiseObjectWithDefaultResolution(cx)); + promise.set(CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!promise) return false; return true; @@ -795,9 +825,7 @@ RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue re // interpreter frame active right now. If a thenable job with a // throwing `then` function got us here, that'll not be the case, // so we add one by throwing the error from self-hosted code. - FixedInvokeArgs<1> getErrorArgs(cx); - getErrorArgs[0].set(Int32Value(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON)); - if (!CallSelfHostedFunction(cx, "GetInternalError", reason, getErrorArgs, &reason)) + if (!GetInternalError(cx, JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON, &reason)) return false; } } @@ -832,10 +860,10 @@ TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseStat } static MOZ_MUST_USE bool -AwaitPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction, - MutableHandleValue rval) +AsyncFunctionPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction, + MutableHandleValue rval) { - MOZ_ASSERT(reaction->isAwait()); + MOZ_ASSERT(reaction->isAsyncFunction()); RootedValue handlerVal(cx, reaction->handler()); RootedValue argument(cx, reaction->handlerArg()); @@ -843,15 +871,14 @@ AwaitPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction, RootedValue generatorVal(cx, resultPromise->getFixedSlot(PromiseSlot_AwaitGenerator)); int32_t handlerNum = int32_t(handlerVal.toNumber()); - MOZ_ASSERT(handlerNum == PROMISE_HANDLER_AWAIT_FULFILLED - || handlerNum == PROMISE_HANDLER_AWAIT_REJECTED); // Await's handlers don't return a value, nor throw exception. // They fail only on OOM. - if (handlerNum == PROMISE_HANDLER_AWAIT_FULFILLED) { + if (handlerNum == PromiseHandlerAsyncFunctionAwaitedFulfilled) { if (!AsyncFunctionAwaitedFulfilled(cx, resultPromise, generatorVal, argument)) return false; } else { + MOZ_ASSERT(handlerNum == PromiseHandlerAsyncFunctionAwaitedRejected); if (!AsyncFunctionAwaitedRejected(cx, resultPromise, generatorVal, argument)) return false; } @@ -860,6 +887,55 @@ AwaitPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction, return true; } +static MOZ_MUST_USE bool +AsyncGeneratorPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction, + MutableHandleValue rval) +{ + MOZ_ASSERT(reaction->isAsyncGenerator()); + + RootedValue handlerVal(cx, reaction->handler()); + RootedValue argument(cx, reaction->handlerArg()); + Rooted<AsyncGeneratorObject*> asyncGenObj(cx, reaction->asyncGenerator()); + + int32_t handlerNum = int32_t(handlerVal.toNumber()); + + // Await's handlers don't return a value, nor throw exception. + // They fail only on OOM. + if (handlerNum == PromiseHandlerAsyncGeneratorAwaitedFulfilled) { + // 4.1.1. + if (!AsyncGeneratorAwaitedFulfilled(cx, asyncGenObj, argument)) + return false; + } else if (handlerNum == PromiseHandlerAsyncGeneratorAwaitedRejected) { + // 4.1.2. + if (!AsyncGeneratorAwaitedRejected(cx, asyncGenObj, argument)) + return false; + } else if (handlerNum == PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled) { + asyncGenObj->setCompleted(); + // 11.4.3.5.1 step 1. + if (!AsyncGeneratorResolve(cx, asyncGenObj, argument, true)) + return false; + } else if (handlerNum == PromiseHandlerAsyncGeneratorResumeNextReturnRejected) { + asyncGenObj->setCompleted(); + // 11.4.3.5.2 step 1. + if (!AsyncGeneratorReject(cx, asyncGenObj, argument)) + return false; + } else if (handlerNum == PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled) { + asyncGenObj->setExecuting(); + // 11.4.3.7 steps 8.d-e. + if (!AsyncGeneratorYieldReturnAwaitedFulfilled(cx, asyncGenObj, argument)) + return false; + } else { + MOZ_ASSERT(handlerNum == PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected); + asyncGenObj->setExecuting(); + // 11.4.3.7 step 8.c. + if (!AsyncGeneratorYieldReturnAwaitedRejected(cx, asyncGenObj, argument)) + return false; + } + + rval.setUndefined(); + return true; +} + // ES2016, 25.4.2.1. /** * Callback triggering the fulfill/reject reaction for a resolved Promise, @@ -903,8 +979,10 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) // Steps 1-2. Rooted<PromiseReactionRecord*> reaction(cx, &reactionObj->as<PromiseReactionRecord>()); - if (reaction->isAwait()) - return AwaitPromiseReactionJob(cx, reaction, args.rval()); + if (reaction->isAsyncFunction()) + return AsyncFunctionPromiseReactionJob(cx, reaction, args.rval()); + if (reaction->isAsyncGenerator()) + return AsyncGeneratorPromiseReactionJob(cx, reaction, args.rval()); // Step 3. RootedValue handlerVal(cx, reaction->handler()); @@ -919,13 +997,23 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) int32_t handlerNum = int32_t(handlerVal.toNumber()); // Step 4. - if (handlerNum == PROMISE_HANDLER_IDENTITY) { + if (handlerNum == PromiseHandlerIdentity) { handlerResult = argument; - } else { + } else if (handlerNum == PromiseHandlerThrower) { // Step 5. - MOZ_ASSERT(handlerNum == PROMISE_HANDLER_THROWER); resolutionMode = RejectMode; handlerResult = argument; + } else { + MOZ_ASSERT(handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone || + handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone); + + bool done = handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone; + // Async Iteration proposal 11.1.3.2.5 step 1. + RootedObject resultObj(cx, CreateIterResultObject(cx, argument, done)); + if (!resultObj) + return false; + + handlerResult = ObjectValue(*resultObj); } } else { // Step 6. @@ -964,8 +1052,8 @@ PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) * * Usage of the function's extended slots is as follows: * ThenableJobSlot_Handler: The handler to use as the Promise reaction. - * This can be PROMISE_HANDLER_IDENTITY, - * PROMISE_HANDLER_THROWER, or a callable. In the + * This can be PromiseHandlerIdentity, + * PromiseHandlerThrower, or a callable. In the * latter case, it's guaranteed to be an object * from the same compartment as the * PromiseReactionJob. @@ -1100,11 +1188,17 @@ GetResolveFunctionFromReject(JSFunction* reject) { MOZ_ASSERT(reject->maybeNative() == RejectPromiseFunction); Value resolveFunVal = reject->getExtendedSlot(RejectFunctionSlot_ResolveFunction); - if (IsNativeFunction(resolveFunVal, ResolvePromiseFunction)) - return &resolveFunVal.toObject().as<JSFunction>(); + MOZ_ASSERT(IsNativeFunction(resolveFunVal, ResolvePromiseFunction)); + return &resolveFunVal.toObject().as<JSFunction>(); +} - PromiseAllDataHolder* resolveFunObj = &resolveFunVal.toObject().as<PromiseAllDataHolder>(); - return &resolveFunObj->resolveObj()->as<JSFunction>(); +static JSFunction* +GetRejectFunctionFromResolve(JSFunction* resolve) +{ + MOZ_ASSERT(resolve->maybeNative() == ResolvePromiseFunction); + Value rejectFunVal = resolve->getExtendedSlot(ResolveFunctionSlot_RejectFunction); + MOZ_ASSERT(IsNativeFunction(rejectFunVal, RejectPromiseFunction)); + return &rejectFunVal.toObject().as<JSFunction>(); } static JSFunction* @@ -1140,8 +1234,7 @@ ClearResolutionFunctionSlots(JSFunction* resolutionFun) JSFunction* reject; if (resolutionFun->maybeNative() == ResolvePromiseFunction) { resolve = resolutionFun; - reject = &resolutionFun->getExtendedSlot(ResolveFunctionSlot_RejectFunction) - .toObject().as<JSFunction>(); + reject = GetRejectFunctionFromResolve(resolutionFun); } else { resolve = GetResolveFunctionFromReject(resolutionFun); reject = resolutionFun; @@ -1367,6 +1460,14 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto / return promise; } +// ES2016, 25.4.3.1. skipping creation of resolution functions and executor +// function invocation. +/* static */ PromiseObject* +PromiseObject::createSkippingExecutor(JSContext* cx) +{ + return CreatePromiseObjectWithoutResolutionFunctions(cx); +} + static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, HandleObject promiseObj, HandleObject resolve, HandleObject reject, @@ -1585,7 +1686,6 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res if (promise->state() != JS::PromiseState::Pending) return true; - if (mode == ResolveMode) { if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION)) return true; @@ -1595,7 +1695,6 @@ RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue res if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_REJECT_FUNCTION)) return true; return RejectMaybeWrappedPromise(cx, promiseObj, result); - } // ES2016, 25.4.4.1.1. @@ -1669,7 +1768,7 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // Step 6. RootedValue nextValue(cx); RootedId indexId(cx); - RootedValue rejectFunVal(cx, ObjectOrNullValue(reject)); + RootedValue rejectFunVal(cx, ObjectValue(*reject)); while (true) { // Steps a-c, e-g. @@ -1690,11 +1789,8 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, // Steps d.iii-iv. if (remainingCount == 0) { - if (resolve) { - return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode, - promiseObj); - } - return ResolvePromiseInternal(cx, promiseObj, valuesArrayVal); + return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode, + promiseObj); } // We're all set for now! @@ -1817,13 +1913,8 @@ PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) // Step 6 (Adapted to work with PromiseAllDataHolder's layout). RootedObject resolveAllFun(cx, data->resolveObj()); RootedObject promiseObj(cx, data->promiseObj()); - if (!resolveAllFun) { - if (!FulfillMaybeWrappedPromise(cx, promiseObj, valuesVal)) - return false; - } else { - if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj)) - return false; - } + if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj)) + return false; } // Step 11. @@ -1904,8 +1995,8 @@ PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, RootedValue CVal(cx, ObjectValue(*C)); RootedValue nextValue(cx); - RootedValue resolveFunVal(cx, ObjectOrNullValue(resolve)); - RootedValue rejectFunVal(cx, ObjectOrNullValue(reject)); + RootedValue resolveFunVal(cx, ObjectValue(*resolve)); + RootedValue rejectFunVal(cx, ObjectValue(*reject)); while (true) { // Steps a-c, e-g. @@ -2155,12 +2246,12 @@ static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx, // Some async/await functions are implemented here instead of // js/src/builtin/AsyncFunction.cpp, to call Promise internal functions. -// Async Functions proposal 1.1.8 and 1.2.14 step 1. +// ES 2018 draft 14.6.11 and 14.7.14 step 1. MOZ_MUST_USE PromiseObject* js::CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal) { // Step 1. - Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithDefaultResolution(cx)); + Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!promise) return nullptr; @@ -2169,7 +2260,7 @@ js::CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal) return promise; } -// Async Functions proposal 2.2 steps 3.f, 3.g. +// ES 2018 draft 25.5.5.2 steps 3.f, 3.g. MOZ_MUST_USE bool js::AsyncFunctionThrown(JSContext* cx, Handle<PromiseObject*> resultPromise) { @@ -2185,7 +2276,7 @@ js::AsyncFunctionThrown(JSContext* cx, Handle<PromiseObject*> resultPromise) return true; } -// Async Functions proposal 2.2 steps 3.d-e, 3.g. +// ES 2018 draft 25.5.5.2 steps 3.d-e, 3.g. MOZ_MUST_USE bool js::AsyncFunctionReturned(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value) { @@ -2197,28 +2288,30 @@ js::AsyncFunctionReturned(JSContext* cx, Handle<PromiseObject*> resultPromise, H return true; } -// Async Functions proposal 2.3 steps 2-8. -MOZ_MUST_USE bool -js::AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value) +// Helper function that performs the equivalent steps as +// Async Iteration proposal 4.1 Await steps 2-3, 6-9 or similar. +template <typename T> +static MOZ_MUST_USE bool +InternalAwait(JSContext* cx, HandleValue value, HandleObject resultPromise, + HandleValue onFulfilled, HandleValue onRejected, T extraStep) { + MOZ_ASSERT(onFulfilled.isNumber() || onFulfilled.isObject()); + MOZ_ASSERT(onRejected.isNumber() || onRejected.isObject()); + // Step 2. - Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithDefaultResolution(cx)); + Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!promise) return false; - // Steps 3. + // Step 3. if (!ResolvePromiseInternal(cx, promise, value)) return false; - // Steps 4-5. - RootedValue onFulfilled(cx, Int32Value(PROMISE_HANDLER_AWAIT_FULFILLED)); - RootedValue onRejected(cx, Int32Value(PROMISE_HANDLER_AWAIT_REJECTED)); - RootedObject incumbentGlobal(cx); if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal)) return false; - // Steps 6-7. + // Steps 7-8. Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, resultPromise, onFulfilled, onRejected, nullptr, nullptr, @@ -2226,12 +2319,403 @@ js::AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, Hand if (!reaction) return false; - reaction->setIsAwait(); + // Step 6. + extraStep(reaction); - // Step 8. + // Step 9. return PerformPromiseThenWithReaction(cx, promise, reaction); } +// ES 2018 draft 25.5.5.3 steps 2-10. +MOZ_MUST_USE bool +js::AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value) +{ + // Steps 4-5. + RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncFunctionAwaitedFulfilled)); + RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncFunctionAwaitedRejected)); + + // Steps 2-3, 6-10. + auto extra = [](Handle<PromiseReactionRecord*> reaction) { + reaction->setIsAsyncFunction(); + }; + return InternalAwait(cx, value, resultPromise, onFulfilled, onRejected, extra); +} + +// Async Iteration proposal 4.1 Await steps 2-9. +MOZ_MUST_USE bool +js::AsyncGeneratorAwait(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue value) +{ + // Steps 4-5. + RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitedFulfilled)); + RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitedRejected)); + + // Steps 2-3, 6-9. + auto extra = [&](Handle<PromiseReactionRecord*> reaction) { + reaction->setIsAsyncGenerator(asyncGenObj); + }; + return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra); +} + +// Async Iteration proposal 11.1.3.2.1 %AsyncFromSyncIteratorPrototype%.next +// Async Iteration proposal 11.1.3.2.2 %AsyncFromSyncIteratorPrototype%.return +// Async Iteration proposal 11.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<AsyncFromSyncIteratorObject>()) { + // 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<AsyncFromSyncIteratorObject*> asyncIter( + cx, &thisVal.toObject().as<AsyncFromSyncIteratorObject>()); + + // Step 4. + RootedObject iter(cx, asyncIter->iterator()); + + RootedValue resultVal(cx); + RootedValue func(cx); + if (completionKind == CompletionKind::Normal) { + // 11.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) { + // 11.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 { + // 11.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; + } + } + + // 11.1.3.2.1 steps 5-6 (partially). + // 11.1.3.2.2, 11.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); + + // 11.1.3.2.1 steps 5-6 (partially). + // 11.1.3.2.2, 11.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 11.1.3.2.1. + // For 11.1.3.2.2 and 11.1.3.2.3, steps 7-16 corresponds to steps 11-20. + + // Steps 7-8. + RootedValue doneVal(cx); + if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal)) + return AbruptRejectPromise(cx, args, resultPromise, nullptr); + bool done = ToBoolean(doneVal); + + // Steps 9-10. + RootedValue value(cx); + if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value)) + return AbruptRejectPromise(cx, args, resultPromise, nullptr); + + // Steps 13-14. + RootedValue onFulfilled(cx, Int32Value(done + ? PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone + : PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone)); + RootedValue onRejected(cx, Int32Value(PromiseHandlerThrower)); + + // Steps 11-12, 15. + auto extra = [](Handle<PromiseReactionRecord*> reaction) { + }; + if (!InternalAwait(cx, value, resultPromise, onFulfilled, onRejected, extra)) + return false; + + // Step 16. + args.rval().setObject(*resultPromise); + return true; +} + +static MOZ_MUST_USE bool +AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj); + +// Async Iteration proposal 11.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. + RootedObject resultObj(cx, CreateIterResultObject(cx, value, done)); + if (!resultObj) + return false; + + RootedValue resultValue(cx, ObjectValue(*resultObj)); + + // Step 7. + if (!ResolvePromiseInternal(cx, resultPromise, resultValue)) + return false; + + // Step 8. + if (!AsyncGeneratorResumeNext(cx, asyncGenObj)) + return false; + + // Step 9. + return true; +} + +// Async Iteration proposal 11.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 11.4.3.5. +static MOZ_MUST_USE bool +AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj) +{ + // Step 1 (implicit). + + // Steps 2-3. + MOZ_ASSERT(!asyncGenObj->isExecuting()); + + // Step 4. + if (asyncGenObj->isAwaitingYieldReturn() || asyncGenObj->isAwaitingReturn()) + return true; + + // Steps 5-6. + if (asyncGenObj->isQueueEmpty()) + return true; + + // Steps 7-8. + Rooted<AsyncGeneratorRequest*> request( + cx, AsyncGeneratorObject::peekRequest(cx, asyncGenObj)); + if (!request) + return false; + + // Step 9. + CompletionKind completionKind = request->completionKind(); + + // Step 10. + if (completionKind != CompletionKind::Normal) { + // Step 10.a. + if (asyncGenObj->isSuspendedStart()) + asyncGenObj->setCompleted(); + + // Step 10.b. + if (asyncGenObj->isCompleted()) { + RootedValue value(cx, request->completionValue()); + + // Step 10.b.i. + if (completionKind == CompletionKind::Return) { + // Steps 10.b.i.1. + asyncGenObj->setAwaitingReturn(); + + // Steps 10.b.i.4-6 (reordered). + RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled)); + RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorResumeNextReturnRejected)); + + // Steps 10.b.i.2-3, 7-10. + auto extra = [&](Handle<PromiseReactionRecord*> reaction) { + reaction->setIsAsyncGenerator(asyncGenObj); + }; + return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra); + } + + // Step 10.b.ii.1. + MOZ_ASSERT(completionKind == CompletionKind::Throw); + + // Steps 10.b.ii.2-3. + return AsyncGeneratorReject(cx, asyncGenObj, value); + } + } else if (asyncGenObj->isCompleted()) { + // Step 11. + return AsyncGeneratorResolve(cx, asyncGenObj, UndefinedHandleValue, true); + } + + // Step 12. + MOZ_ASSERT(asyncGenObj->isSuspendedStart() || asyncGenObj->isSuspendedYield()); + + // Step 16 (reordered). + asyncGenObj->setExecuting(); + + RootedValue argument(cx, request->completionValue()); + + if (completionKind == CompletionKind::Return) { + // 11.4.3.7 AsyncGeneratorYield step 8.b-e. + // Since we don't have the place that handles return from yield + // inside the generator, handle the case here, with extra state + // State_AwaitingYieldReturn. + asyncGenObj->setAwaitingYieldReturn(); + + RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled)); + RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected)); + + auto extra = [&](Handle<PromiseReactionRecord*> reaction) { + reaction->setIsAsyncGenerator(asyncGenObj); + }; + return InternalAwait(cx, argument, nullptr, onFulfilled, onRejected, extra); + } + + // Steps 13-15, 17-21. + return AsyncGeneratorResume(cx, asyncGenObj, completionKind, argument); +} + +// Async Iteration proposal 11.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) @@ -2291,12 +2775,12 @@ PerformPromiseThen(JSContext* cx, Handle<PromiseObject*> promise, HandleValue on // Step 3. RootedValue onFulfilled(cx, onFulfilled_); if (!IsCallable(onFulfilled)) - onFulfilled = Int32Value(PROMISE_HANDLER_IDENTITY); + onFulfilled = Int32Value(PromiseHandlerIdentity); // Step 4. RootedValue onRejected(cx, onRejected_); if (!IsCallable(onRejected)) - onRejected = Int32Value(PROMISE_HANDLER_THROWER); + onRejected = Int32Value(PromiseHandlerThrower); RootedObject incumbentGlobal(cx); if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal)) @@ -2645,6 +3129,9 @@ PromiseObject::resolve(JSContext* cx, Handle<PromiseObject*> promise, HandleValu if (promise->state() != JS::PromiseState::Pending) return true; + if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION)) + return ResolvePromiseInternal(cx, promise, resolutionValue); + RootedObject resolveFun(cx, GetResolveFunctionFromPromise(promise)); RootedValue funVal(cx, ObjectValue(*resolveFun)); @@ -2668,6 +3155,9 @@ PromiseObject::reject(JSContext* cx, Handle<PromiseObject*> promise, HandleValue if (promise->state() != JS::PromiseState::Pending) return true; + if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_REJECT_FUNCTION)) + return RejectMaybeWrappedPromise(cx, promise, rejectionValue); + RootedValue funVal(cx, promise->getFixedSlot(PromiseSlot_RejectFunction)); MOZ_ASSERT(IsCallable(funVal)); diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h index 6a6453e46..b04bd0e2a 100644 --- a/js/src/builtin/Promise.h +++ b/js/src/builtin/Promise.h @@ -44,6 +44,8 @@ class PromiseObject : public NativeObject static PromiseObject* create(JSContext* cx, HandleObject executor, HandleObject proto = nullptr, bool needsWrapping = false); + static PromiseObject* createSkippingExecutor(JSContext* cx); + static JSObject* unforgeableResolve(JSContext* cx, HandleValue value); static JSObject* unforgeableReject(JSContext* cx, HandleValue value); @@ -149,6 +151,26 @@ AsyncFunctionThrown(JSContext* cx, Handle<PromiseObject*> resultPromise); MOZ_MUST_USE bool AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value); +class AsyncGeneratorObject; + +MOZ_MUST_USE bool +AsyncGeneratorAwait(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, HandleValue value); + +MOZ_MUST_USE bool +AsyncGeneratorResolve(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue value, bool done); + +MOZ_MUST_USE bool +AsyncGeneratorReject(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue exception); + +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/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 8e8bb2417..22c958d4c 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2800,11 +2800,11 @@ ASTSerializer::generatorExpression(ParseNode* pn, MutableHandleValue dst) LOCAL_ASSERT(next->isKind(PNK_SEMI) && next->pn_kid->isKind(PNK_YIELD) && - next->pn_kid->pn_left); + next->pn_kid->pn_kid); RootedValue body(cx); - return expression(next->pn_kid->pn_left, &body) && + return expression(next->pn_kid->pn_kid, &body) && builder.generatorExpression(body, blocks, filter, isLegacy, &pn->pn_pos, dst); } @@ -3146,7 +3146,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) case PNK_YIELD_STAR: { - MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos)); + MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos)); RootedValue arg(cx); return expression(pn->pn_left, &arg) && @@ -3155,10 +3155,10 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) case PNK_YIELD: { - MOZ_ASSERT_IF(pn->pn_left, pn->pn_pos.encloses(pn->pn_left->pn_pos)); + MOZ_ASSERT_IF(pn->pn_kid, pn->pn_pos.encloses(pn->pn_kid->pn_pos)); RootedValue arg(cx); - return optExpression(pn->pn_left, &arg) && + return optExpression(pn->pn_kid, &arg) && builder.yieldExpression(arg, NotDelegating, &pn->pn_pos, dst); } @@ -3422,10 +3422,10 @@ ASTSerializer::function(ParseNode* pn, ASTType type, MutableHandleValue dst) RootedFunction func(cx, pn->pn_funbox->function()); GeneratorStyle generatorStyle = - pn->pn_funbox->isGenerator() - ? (pn->pn_funbox->isLegacyGenerator() - ? GeneratorStyle::Legacy - : GeneratorStyle::ES6) + pn->pn_funbox->isStarGenerator() + ? GeneratorStyle::ES6 + : pn->pn_funbox->isLegacyGenerator() + ? GeneratorStyle::Legacy : GeneratorStyle::None; bool isAsync = pn->pn_funbox->isAsync(); @@ -3480,7 +3480,7 @@ ASTSerializer::functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& ParseNode* pnstart = pnbody->pn_head; // Skip over initial yield in generator. - if (pnstart && pnstart->isKind(PNK_YIELD)) { + if (pnstart && pnstart->isKind(PNK_INITIALYIELD)) { MOZ_ASSERT(pnstart->getOp() == JSOP_INITIALYIELD); pnstart = pnstart->pn_next; } diff --git a/js/src/doc/Debugger/Conventions.md b/js/src/doc/Debugger/Conventions.md index e8bd3ee43..5de7bc171 100644 --- a/js/src/doc/Debugger/Conventions.md +++ b/js/src/doc/Debugger/Conventions.md @@ -110,8 +110,8 @@ resumption value has one of the following forms: the `new` expression returns the frame's `this` value. Similarly, if the function is the constructor for a subclass, then a non-object value may result in a TypeError. - If the frame is a generator or async function, then <i>value</i> must - conform to the iterator protocol: it must be a non-proxy object of the form + If the frame is a generator function, then <i>value</i> must conform to the + iterator protocol: it must be a non-proxy object of the form <code>{ done: <i>boolean</i>, value: <i>v</i> }</code>, where both `done` and `value` are ordinary properties. diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index a1abbfeda..ccfe3cd2e 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -727,5 +727,18 @@ frontend::CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fu BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope, TraceLogger_ParserCompileFunction); + return compiler.compileStandaloneFunction(fun, NotGenerator, AsyncFunction, parameterListEnd); +} + +bool +frontend::CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + Maybe<uint32_t> parameterListEnd) +{ + RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); + + BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope, + TraceLogger_ParserCompileFunction); return compiler.compileStandaloneFunction(fun, StarGenerator, AsyncFunction, parameterListEnd); } diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h index 0bc1ab2ab..029fbe632 100644 --- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -85,6 +85,12 @@ CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun, mozilla::Maybe<uint32_t> parameterListEnd); MOZ_MUST_USE bool +CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + mozilla::Maybe<uint32_t> parameterListEnd); + +MOZ_MUST_USE bool CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options, Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 309d6c290..f4574b248 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -100,7 +100,7 @@ class BytecodeEmitter::NestableControl : public Nestable<BytecodeEmitter::Nestab NestableControl(BytecodeEmitter* bce, StatementKind kind) : Nestable<NestableControl>(&bce->innermostNestableControl), kind_(kind), - emitterScope_(bce->innermostEmitterScope) + emitterScope_(bce->innermostEmitterScopeNoCheck()) { } public: @@ -389,7 +389,10 @@ class BytecodeEmitter::EmitterScope : public Nestable<BytecodeEmitter::EmitterSc nextFrameSlot_ = bi.nextFrameSlot(); if (nextFrameSlot_ > bce->maxFixedSlots) bce->maxFixedSlots = nextFrameSlot_; - MOZ_ASSERT_IF(bce->sc->isFunctionBox() && bce->sc->asFunctionBox()->isGenerator(), + MOZ_ASSERT_IF(bce->sc->isFunctionBox() && + (bce->sc->asFunctionBox()->isStarGenerator() || + bce->sc->asFunctionBox()->isLegacyGenerator() || + bce->sc->asFunctionBox()->isAsync()), bce->maxFixedSlots == 0); } @@ -423,7 +426,7 @@ class BytecodeEmitter::EmitterScope : public Nestable<BytecodeEmitter::EmitterSc // enclosing BCE. if ((*bce)->parent) { *bce = (*bce)->parent; - return (*bce)->innermostEmitterScope; + return (*bce)->innermostEmitterScopeNoCheck(); } return nullptr; @@ -457,7 +460,7 @@ class BytecodeEmitter::EmitterScope : public Nestable<BytecodeEmitter::EmitterSc public: explicit EmitterScope(BytecodeEmitter* bce) - : Nestable<EmitterScope>(&bce->innermostEmitterScope), + : Nestable<EmitterScope>(&bce->innermostEmitterScope_), nameCache_(bce->cx->frontendCollectionPool()), hasEnvironment_(false), environmentChainLength_(0), @@ -863,7 +866,7 @@ BytecodeEmitter::EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind Handle<LexicalScope::Data*> bindings) { MOZ_ASSERT(kind != ScopeKind::NamedLambda && kind != ScopeKind::StrictNamedLambda); - MOZ_ASSERT(this == bce->innermostEmitterScope); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); if (!ensureCache(bce)) return false; @@ -932,7 +935,7 @@ BytecodeEmitter::EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind bool BytecodeEmitter::EmitterScope::enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox) { - MOZ_ASSERT(this == bce->innermostEmitterScope); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); MOZ_ASSERT(funbox->namedLambdaBindings()); if (!ensureCache(bce)) @@ -999,7 +1002,7 @@ BytecodeEmitter::EmitterScope::enterComprehensionFor(BytecodeEmitter* bce, bool BytecodeEmitter::EmitterScope::enterParameterExpressionVar(BytecodeEmitter* bce) { - MOZ_ASSERT(this == bce->innermostEmitterScope); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); if (!ensureCache(bce)) return false; @@ -1032,7 +1035,7 @@ BytecodeEmitter::EmitterScope::enterParameterExpressionVar(BytecodeEmitter* bce) bool BytecodeEmitter::EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* funbox) { - MOZ_ASSERT(this == bce->innermostEmitterScope); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); // If there are parameter expressions, there is an extra var scope. if (!funbox->hasExtraBodyVarScope()) @@ -1123,7 +1126,7 @@ BytecodeEmitter::EmitterScope::enterFunctionExtraBodyVar(BytecodeEmitter* bce, F MOZ_ASSERT(funbox->hasParameterExprs); MOZ_ASSERT(funbox->extraVarScopeBindings() || funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings()); - MOZ_ASSERT(this == bce->innermostEmitterScope); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); // The extra var scope is never popped once it's entered. It replaces the // function scope as the var emitter scope. @@ -1209,7 +1212,7 @@ class DynamicBindingIter : public BindingIter bool BytecodeEmitter::EmitterScope::enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc) { - MOZ_ASSERT(this == bce->innermostEmitterScope); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); bce->setVarEmitterScope(this); @@ -1269,7 +1272,7 @@ BytecodeEmitter::EmitterScope::enterGlobal(BytecodeEmitter* bce, GlobalSharedCon bool BytecodeEmitter::EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc) { - MOZ_ASSERT(this == bce->innermostEmitterScope); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); bce->setVarEmitterScope(this); @@ -1324,7 +1327,7 @@ BytecodeEmitter::EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext bool BytecodeEmitter::EmitterScope::enterModule(BytecodeEmitter* bce, ModuleSharedContext* modulesc) { - MOZ_ASSERT(this == bce->innermostEmitterScope); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); bce->setVarEmitterScope(this); @@ -1381,7 +1384,7 @@ BytecodeEmitter::EmitterScope::enterModule(BytecodeEmitter* bce, ModuleSharedCon bool BytecodeEmitter::EmitterScope::enterWith(BytecodeEmitter* bce) { - MOZ_ASSERT(this == bce->innermostEmitterScope); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); if (!ensureCache(bce)) return false; @@ -1409,7 +1412,7 @@ BytecodeEmitter::EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal) { // If we aren't leaving the scope due to a non-local jump (e.g., break), // we must be the innermost scope. - MOZ_ASSERT_IF(!nonLocal, this == bce->innermostEmitterScope); + MOZ_ASSERT_IF(!nonLocal, this == bce->innermostEmitterScopeNoCheck()); ScopeKind kind = scope(bce)->kind(); switch (kind) { @@ -1985,6 +1988,8 @@ class MOZ_STACK_CLASS IfThenElseEmitter class ForOfLoopControl : public LoopControl { + using EmitterScope = BytecodeEmitter::EmitterScope; + // The stack depth of the iterator. int32_t iterDepth_; @@ -2026,12 +2031,16 @@ class ForOfLoopControl : public LoopControl bool allowSelfHosted_; + IteratorKind iterKind_; + public: - ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted) + ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted, + IteratorKind iterKind) : LoopControl(bce, StatementKind::ForOfLoop), iterDepth_(iterDepth), numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX), - allowSelfHosted_(allowSelfHosted) + allowSelfHosted_(allowSelfHosted), + iterKind_(iterKind) { } @@ -2043,7 +2052,7 @@ class ForOfLoopControl : public LoopControl return false; MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX); - numYieldsAtBeginCodeNeedingIterClose_ = bce->yieldOffsetList.numYields; + numYieldsAtBeginCodeNeedingIterClose_ = bce->yieldAndAwaitOffsetList.numYields; return true; } @@ -2073,8 +2082,8 @@ class ForOfLoopControl : public LoopControl MOZ_ASSERT(slotFromTop == unsigned(bce->stackDepth - iterDepth_)); if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER return false; - if (!emitIteratorClose(bce, CompletionKind::Throw)) // ITER ... EXCEPTION - return false; + if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Throw)) + return false; // ITER ... EXCEPTION if (!ifIteratorIsNotClosed.emitEnd()) // ITER ... EXCEPTION return false; @@ -2085,7 +2094,7 @@ class ForOfLoopControl : public LoopControl // If any yields were emitted, then this for-of loop is inside a star // generator and must handle the case of Generator.return. Like in // yield*, it is handled with a finally block. - uint32_t numYieldsEmitted = bce->yieldOffsetList.numYields; + uint32_t numYieldsEmitted = bce->yieldAndAwaitOffsetList.numYields; if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) { if (!tryCatch_->emitFinally()) return false; @@ -2097,8 +2106,8 @@ class ForOfLoopControl : public LoopControl return false; if (!bce->emitDupAt(slotFromTop + 1)) // ITER ... FTYPE FVALUE ITER return false; - if (!emitIteratorClose(bce, CompletionKind::Normal)) // ITER ... FTYPE FVALUE - return false; + if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Normal)) + return false; // ITER ... FTYPE FVALUE if (!ifGeneratorClosing.emitEnd()) // ITER ... FTYPE FVALUE return false; } @@ -2112,16 +2121,27 @@ class ForOfLoopControl : public LoopControl return true; } - bool emitIteratorClose(BytecodeEmitter* bce, - CompletionKind completionKind = CompletionKind::Normal) { + bool emitIteratorCloseInInnermostScope(BytecodeEmitter* bce, + CompletionKind completionKind = CompletionKind::Normal) { + return emitIteratorCloseInScope(bce, *bce->innermostEmitterScope(), completionKind); + } + + bool emitIteratorCloseInScope(BytecodeEmitter* bce, + EmitterScope& currentScope, + CompletionKind completionKind = CompletionKind::Normal) { ptrdiff_t start = bce->offset(); - if (!bce->emitIteratorClose(completionKind, allowSelfHosted_)) + if (!bce->emitIteratorCloseInScope(currentScope, iterKind_, completionKind, + allowSelfHosted_)) + { return false; + } ptrdiff_t end = bce->offset(); return bce->tryNoteList.append(JSTRY_FOR_OF_ITERCLOSE, 0, start, end); } - bool emitPrepareForNonLocalJump(BytecodeEmitter* bce, bool isTarget) { + bool emitPrepareForNonLocalJumpFromScope(BytecodeEmitter* bce, + EmitterScope& currentScope, + bool isTarget) { // Pop unnecessary values from the stack. Effectively this means // leaving try-catch block. However, the performing IteratorClose can // reach the depth for try-catch, and effectively re-enter the @@ -2138,7 +2158,7 @@ class ForOfLoopControl : public LoopControl if (!bce->emit1(JSOP_SWAP)) // UNDEF ITER return false; - if (!emitIteratorClose(bce)) // UNDEF + if (!emitIteratorCloseInScope(bce, currentScope, CompletionKind::Normal)) // UNDEF return false; if (isTarget) { @@ -2181,13 +2201,16 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, bodyScopeIndex(UINT32_MAX), varEmitterScope(nullptr), innermostNestableControl(nullptr), - innermostEmitterScope(nullptr), + innermostEmitterScope_(nullptr), innermostTDZCheckCache(nullptr), +#ifdef DEBUG + unstableEmitterScope(false), +#endif constList(cx), scopeList(cx), tryNoteList(cx), scopeNoteList(cx), - yieldOffsetList(cx), + yieldAndAwaitOffsetList(cx), typesetCount(0), hasSingletons(false), hasTryFinally(false), @@ -2239,13 +2262,13 @@ BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const NameLocation BytecodeEmitter::lookupName(JSAtom* name) { - return innermostEmitterScope->lookup(this, name); + return innermostEmitterScope()->lookup(this, name); } Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInScope(JSAtom* name, EmitterScope* target) { - return innermostEmitterScope->locationBoundInScope(this, name, target); + return innermostEmitterScope()->locationBoundInScope(this, name, target); } Maybe<NameLocation> @@ -2706,7 +2729,7 @@ class NonLocalExitControl : bce_(bce), savedScopeNoteIndex_(bce->scopeNoteList.length()), savedDepth_(bce->stackDepth), - openScopeNoteIndex_(bce->innermostEmitterScope->noteIndex()), + openScopeNoteIndex_(bce->innermostEmitterScope()->noteIndex()), kind_(kind) { } @@ -2752,9 +2775,11 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta using NestableControl = BytecodeEmitter::NestableControl; using EmitterScope = BytecodeEmitter::EmitterScope; - EmitterScope* es = bce_->innermostEmitterScope; + EmitterScope* es = bce_->innermostEmitterScope(); int npops = 0; + AutoCheckUnstableEmitterScope cues(bce_); + // For 'continue', 'break', and 'return' statements, emit IteratorClose // bytecode inline. 'continue' statements do not call IteratorClose for // the loop they are continuing. @@ -2805,8 +2830,11 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta return false; ForOfLoopControl& loopinfo = control->as<ForOfLoopControl>(); - if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ false)) // ... + if (!loopinfo.emitPrepareForNonLocalJumpFromScope(bce_, *es, + /* isTarget = */ false)) + { // ... return false; + } } else { npops += 3; } @@ -2833,8 +2861,11 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta if (target && emitIteratorCloseAtTarget && target->is<ForOfLoopControl>()) { ForOfLoopControl& loopinfo = target->as<ForOfLoopControl>(); - if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ true)) // ... UNDEF UNDEF UNDEF + if (!loopinfo.emitPrepareForNonLocalJumpFromScope(bce_, *es, + /* isTarget = */ true)) + { // ... UNDEF UNDEF UNDEF return false; + } } EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope; @@ -2869,7 +2900,7 @@ BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteTy Scope* BytecodeEmitter::innermostScope() const { - return innermostEmitterScope->scope(this); + return innermostEmitterScope()->scope(this); } bool @@ -3172,10 +3203,11 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) *answer = true; return true; + case PNK_INITIALYIELD: case PNK_YIELD_STAR: case PNK_YIELD: case PNK_AWAIT: - MOZ_ASSERT(pn->isArity(PN_BINARY)); + MOZ_ASSERT(pn->isArity(PN_UNARY)); *answer = true; return true; @@ -3528,7 +3560,7 @@ BytecodeEmitter::needsImplicitThis() return true; // Otherwise see if the current point is under a 'with'. - for (EmitterScope* es = innermostEmitterScope; es; es = es->enclosingInFrame()) { + for (EmitterScope* es = innermostEmitterScope(); es; es = es->enclosingInFrame()) { if (es->scope(this)->kind() == ScopeKind::With) return true; } @@ -3718,6 +3750,18 @@ BytecodeEmitter::emitFinishIteratorResult(bool done) } 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) { switch (loc.kind()) { @@ -4783,7 +4827,9 @@ BytecodeEmitter::isRunOnceLambda() FunctionBox* funbox = sc->asFunctionBox(); return !funbox->argumentsHasLocalBinding() && - !funbox->isGenerator() && + !funbox->isStarGenerator() && + !funbox->isLegacyGenerator() && + !funbox->isAsync() && !funbox->function()->explicitName(); } @@ -4793,26 +4839,26 @@ BytecodeEmitter::emitYieldOp(JSOp op) if (op == JSOP_FINALYIELDRVAL) return emit1(JSOP_FINALYIELDRVAL); - MOZ_ASSERT(op == JSOP_INITIALYIELD || op == JSOP_YIELD); + MOZ_ASSERT(op == JSOP_INITIALYIELD || op == JSOP_YIELD || op == JSOP_AWAIT); ptrdiff_t off; if (!emitN(op, 3, &off)) return false; - uint32_t yieldIndex = yieldOffsetList.length(); - if (yieldIndex >= JS_BIT(24)) { + uint32_t yieldAndAwaitIndex = yieldAndAwaitOffsetList.length(); + if (yieldAndAwaitIndex >= JS_BIT(24)) { reportError(nullptr, JSMSG_TOO_MANY_YIELDS); return false; } if (op == JSOP_YIELD) - yieldOffsetList.numYields++; + yieldAndAwaitOffsetList.numYields++; else - yieldOffsetList.numAwaits++; + yieldAndAwaitOffsetList.numAwaits++; - SET_UINT24(code(off), yieldIndex); + SET_UINT24(code(off), yieldAndAwaitIndex); - if (!yieldOffsetList.append(offset())) + if (!yieldAndAwaitOffsetList.append(offset())) return false; return emit1(JSOP_DEBUGAFTERYIELD); @@ -5165,7 +5211,7 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri // destructuring declaration needs to initialize the name in // the function scope. The innermost scope is the var scope, // and its enclosing scope is the function scope. - EmitterScope* funScope = innermostEmitterScope->enclosingInFrame(); + EmitterScope* funScope = innermostEmitterScope()->enclosingInFrame(); NameLocation paramLoc = *locationOfNameBoundInScope(name, funScope); if (!emitSetOrInitializeNameAtLocation(name, paramLoc, emitSwapScopeAndRhs, true)) return false; @@ -5228,7 +5274,8 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri } bool -BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted /* = false */) +BytecodeEmitter::emitIteratorNext(ParseNode* pn, IteratorKind iterKind /* = IteratorKind::Sync */, + bool allowSelfHosted /* = false */) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".next() iteration is prohibited in self-hosted code because it " @@ -5242,6 +5289,12 @@ BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted /* = false return false; if (!emitCall(JSOP_CALL, 0, pn)) // ... RESULT return false; + + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) // ... RESULT + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ... RESULT return false; checkTypeSet(JSOP_CALL); @@ -5249,8 +5302,10 @@ BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted /* = false } bool -BytecodeEmitter::emitIteratorClose(CompletionKind completionKind /* = CompletionKind::Normal */, - bool allowSelfHosted /* = false */) +BytecodeEmitter::emitIteratorCloseInScope(EmitterScope& currentScope, + IteratorKind iterKind /* = IteratorKind::Sync */, + CompletionKind completionKind /* = CompletionKind::Normal */, + bool allowSelfHosted /* = false */) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".close() on iterators is prohibited in self-hosted code because it " @@ -5334,6 +5389,18 @@ BytecodeEmitter::emitIteratorClose(CompletionKind completionKind /* = Completion return false; checkTypeSet(JSOP_CALL); + if (iterKind == IteratorKind::Async) { + if (completionKind != CompletionKind::Throw) { + // Await clobbers rval, so save the current rval. + if (!emit1(JSOP_GETRVAL)) // ... ... RESULT RVAL + return false; + if (!emit1(JSOP_SWAP)) // ... ... RVAL RESULT + return false; + } + if (!emitAwaitInScope(currentScope)) // ... ... RVAL? RESULT + return false; + } + if (completionKind == CompletionKind::Throw) { if (!emit1(JSOP_SWAP)) // ... RET ITER RESULT UNDEF return false; @@ -5343,7 +5410,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 @@ -5360,8 +5427,15 @@ BytecodeEmitter::emitIteratorClose(CompletionKind completionKind /* = Completion if (!emit1(JSOP_POP)) // ... RESULT return false; } else { - if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RVAL? RESULT return false; + + if (iterKind == IteratorKind::Async) { + if (!emit1(JSOP_SWAP)) // ... RESULT RVAL + return false; + if (!emit1(JSOP_SETRVAL)) // ... RESULT + return false; + } } if (!ifReturnMethodIsDefined.emitElse()) @@ -5587,7 +5661,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // For an empty pattern [], call IteratorClose unconditionally. Nothing // else needs to be done. if (!pattern->pn_head) - return emitIteratorClose(); // ... OBJ + return emitIteratorCloseInInnermostScope(); // ... OBJ // Push an initial FALSE value for DONE. if (!emit1(JSOP_FALSE)) // ... OBJ ITER FALSE @@ -5788,7 +5862,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav return false; if (!ifDone.emitElse()) // ... OBJ ITER return false; - if (!emitIteratorClose()) // ... OBJ + if (!emitIteratorCloseInInnermostScope()) // ... OBJ return false; if (!ifDone.emitEnd()) return false; @@ -6966,6 +7040,63 @@ BytecodeEmitter::emitIterator() } 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) { LoopControl loopInfo(this, StatementKind::Spread); @@ -7018,7 +7149,7 @@ BytecodeEmitter::emitSpread(bool allowSelfHosted) if (!emitDupAt(2)) // ITER ARR I ITER return false; - if (!emitIteratorNext(nullptr, allowSelfHosted)) // ITER ARR I RESULT + if (!emitIteratorNext(nullptr, IteratorKind::Sync, allowSelfHosted)) // ITER ARR I RESULT return false; if (!emit1(JSOP_DUP)) // ITER ARR I RESULT RESULT return false; @@ -7118,6 +7249,13 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte MOZ_ASSERT(forOfHead->isKind(PNK_FOROF)); MOZ_ASSERT(forOfHead->isArity(PN_TERNARY)); + unsigned iflags = forOfLoop->pn_iflags; + IteratorKind iterKind = (iflags & JSITER_FORAWAITOF) + ? IteratorKind::Async + : IteratorKind::Sync; + MOZ_ASSERT_IF(iterKind == IteratorKind::Async, sc->asFunctionBox()); + MOZ_ASSERT_IF(iterKind == IteratorKind::Async, sc->asFunctionBox()->isAsync()); + ParseNode* forHeadExpr = forOfHead->pn_kid3; // Certain builtins (e.g. Array.from) are implemented in self-hosting @@ -7133,8 +7271,13 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte // Evaluate the expression being iterated. if (!emitTree(forHeadExpr)) // ITERABLE return false; - if (!emitIterator()) // ITER - return false; + if (iterKind == IteratorKind::Async) { + if (!emitAsyncIterator()) // ITER + return false; + } else { + if (!emitIterator()) // ITER + return false; + } int32_t iterDepth = stackDepth; @@ -7145,7 +7288,7 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF return false; - ForOfLoopControl loopInfo(this, iterDepth, allowSelfHostedIter); + ForOfLoopControl loopInfo(this, iterDepth, allowSelfHostedIter, iterKind); // Annotate so IonMonkey can find the loop-closing jump. unsigned noteIndex; @@ -7170,7 +7313,7 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte // bindings inducing an environment, recreate the current environment. DebugOnly<ParseNode*> forOfTarget = forOfHead->pn_kid1; MOZ_ASSERT(forOfTarget->isKind(PNK_LET) || forOfTarget->isKind(PNK_CONST)); - MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); + MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope()); MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); if (headLexicalEmitterScope->hasEnvironment()) { @@ -7241,7 +7384,7 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte if (!emitDupAt(1)) // ITER UNDEF ITER return false; - if (!emitIteratorNext(forOfHead, allowSelfHostedIter)) // ITER UNDEF RESULT + if (!emitIteratorNext(forOfHead, iterKind, allowSelfHostedIter)) // ITER UNDEF RESULT return false; if (!emit1(JSOP_SWAP)) // ITER RESULT UNDEF @@ -7352,7 +7495,7 @@ BytecodeEmitter::emitForIn(ParseNode* forInLoop, EmitterScope* headLexicalEmitte // it must be the innermost one. If that scope has closed-over // bindings inducing an environment, recreate the current environment. MOZ_ASSERT(forInTarget->isKind(PNK_LET) || forInTarget->isKind(PNK_CONST)); - MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); + MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope()); MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); if (headLexicalEmitterScope->hasEnvironment()) { @@ -7481,7 +7624,7 @@ BytecodeEmitter::emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterSc // exists for the head, it must be the innermost one. If that scope // has closed-over bindings inducing an environment, recreate the // current environment. - MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); + MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope()); MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); if (headLexicalEmitterScope->hasEnvironment()) { @@ -7529,7 +7672,7 @@ BytecodeEmitter::emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterSc // ES 13.7.4.8 step 3.e. The per-iteration freshening. if (forLoopRequiresFreshening) { - MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); + MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope()); MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); if (headLexicalEmitterScope->hasEnvironment()) { @@ -8061,7 +8204,8 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) MOZ_ASSERT(fun->isArrow() == (pn->getOp() == JSOP_LAMBDA_ARROW)); if (funbox->isAsync()) { MOZ_ASSERT(!needsProto); - return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow()); + return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow(), + fun->isStarGenerator()); } if (fun->isArrow()) { @@ -8116,8 +8260,11 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) MOZ_ASSERT(pn->getOp() == JSOP_NOP); switchToPrologue(); if (funbox->isAsync()) { - if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow())) + if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow(), + fun->isStarGenerator())) + { return false; + } } else { if (!emitIndex32(JSOP_LAMBDA, index)) return false; @@ -8133,10 +8280,12 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) // initialize the binding name of the function in the current scope. bool isAsync = funbox->isAsync(); - auto emitLambda = [index, isAsync](BytecodeEmitter* bce, const NameLocation&, bool) { + bool isStarGenerator = funbox->isStarGenerator(); + auto emitLambda = [index, isAsync, isStarGenerator](BytecodeEmitter* bce, + const NameLocation&, bool) { if (isAsync) { return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false, - /* isArrow = */ false); + /* isArrow = */ false, isStarGenerator); } return bce->emitIndexOp(JSOP_LAMBDA, index); }; @@ -8171,7 +8320,8 @@ BytecodeEmitter::emitAsyncWrapperLambda(unsigned index, bool isArrow) { } bool -BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow) +BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow, + bool isStarGenerator) { // needsHomeObject can be true for propertyList for extended class. // In that case push both unwrapped and wrapped function, in order to @@ -8203,8 +8353,13 @@ BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isA if (!emit1(JSOP_DUP)) return false; } - if (!emit1(JSOP_TOASYNC)) - return false; + if (isStarGenerator) { + if (!emit1(JSOP_TOASYNCGEN)) + return false; + } else { + if (!emit1(JSOP_TOASYNC)) + return false; + } return true; } @@ -8438,7 +8593,8 @@ BytecodeEmitter::emitReturn(ParseNode* pn) if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false; - if (sc->isFunctionBox() && sc->asFunctionBox()->isStarGenerator()) { + bool needsIteratorResult = sc->isFunctionBox() && sc->asFunctionBox()->needsIteratorResult(); + if (needsIteratorResult) { if (!emitPrepareIteratorResult()) return false; } @@ -8447,13 +8603,20 @@ BytecodeEmitter::emitReturn(ParseNode* pn) if (ParseNode* pn2 = pn->pn_kid) { if (!emitTree(pn2)) return false; + + bool isAsyncGenerator = sc->asFunctionBox()->isAsync() && + sc->asFunctionBox()->isStarGenerator(); + if (isAsyncGenerator) { + if (!emitAwaitInInnermostScope()) + return false; + } } else { /* No explicit return value provided */ if (!emit1(JSOP_UNDEFINED)) return false; } - if (sc->isFunctionBox() && sc->asFunctionBox()->isStarGenerator()) { + if (needsIteratorResult) { if (!emitFinishIteratorResult(true)) return false; } @@ -8478,11 +8641,11 @@ BytecodeEmitter::emitReturn(ParseNode* pn) */ ptrdiff_t top = offset(); - bool isGenerator = sc->isFunctionBox() && sc->asFunctionBox()->isGenerator(); + bool needsFinalYield = sc->isFunctionBox() && sc->asFunctionBox()->needsFinalYield(); bool isDerivedClassConstructor = sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor(); - if (!emit1((isGenerator || isDerivedClassConstructor) ? JSOP_SETRVAL : JSOP_RETURN)) + if (!emit1((needsFinalYield || isDerivedClassConstructor) ? JSOP_SETRVAL : JSOP_RETURN)) return false; // Make sure that we emit this before popping the blocks in prepareForNonLocalJump, @@ -8497,7 +8660,7 @@ BytecodeEmitter::emitReturn(ParseNode* pn) if (!nle.prepareForNonLocalJumpToOutermost()) return false; - if (isGenerator) { + if (needsFinalYield) { // We know that .generator is on the function scope, as we just exited // all nested scopes. NameLocation loc = @@ -8520,52 +8683,105 @@ BytecodeEmitter::emitReturn(ParseNode* pn) } bool +BytecodeEmitter::emitGetDotGeneratorInScope(EmitterScope& currentScope) +{ + NameLocation loc = *locationOfNameBoundInFunctionScope(cx->names().dotGenerator, ¤tScope); + return emitGetNameAtLocation(cx->names().dotGenerator, loc); +} + +bool +BytecodeEmitter::emitInitialYield(ParseNode* pn) +{ + if (!emitTree(pn->pn_kid)) + return false; + + if (!emitYieldOp(JSOP_INITIALYIELD)) + return false; + + if (!emit1(JSOP_POP)) + return false; + + return true; +} + +bool BytecodeEmitter::emitYield(ParseNode* pn) { MOZ_ASSERT(sc->isFunctionBox()); + MOZ_ASSERT(pn->getOp() == JSOP_YIELD); - if (pn->getOp() == JSOP_YIELD) { - if (sc->asFunctionBox()->isStarGenerator()) { - if (!emitPrepareIteratorResult()) - return false; - } - if (pn->pn_left) { - if (!emitTree(pn->pn_left)) - return false; - } else { - if (!emit1(JSOP_UNDEFINED)) - return false; - } - if (sc->asFunctionBox()->isStarGenerator()) { - if (!emitFinishIteratorResult(false)) - return false; - } + bool needsIteratorResult = sc->asFunctionBox()->needsIteratorResult(); + if (needsIteratorResult) { + if (!emitPrepareIteratorResult()) + return false; + } + if (pn->pn_kid) { + if (!emitTree(pn->pn_kid)) + return false; } else { - MOZ_ASSERT(pn->getOp() == JSOP_INITIALYIELD); + if (!emit1(JSOP_UNDEFINED)) + return false; } - if (!emitTree(pn->pn_right)) + // 11.4.3.7 AsyncGeneratorYield step 5. + bool isAsyncGenerator = sc->asFunctionBox()->isAsync(); + if (isAsyncGenerator) { + if (!emitAwaitInInnermostScope()) // RESULT + return false; + } + + if (needsIteratorResult) { + if (!emitFinishIteratorResult(false)) + return false; + } + + if (!emitGetDotGeneratorInInnermostScope()) return false; - if (!emitYieldOp(pn->getOp())) + if (!emitYieldOp(JSOP_YIELD)) return false; - if (pn->getOp() == JSOP_INITIALYIELD && !emit1(JSOP_POP)) + return true; +} + +bool +BytecodeEmitter::emitAwaitInInnermostScope(ParseNode* pn) +{ + MOZ_ASSERT(sc->isFunctionBox()); + MOZ_ASSERT(pn->getOp() == JSOP_AWAIT); + + if (!emitTree(pn->pn_kid)) return false; + return emitAwaitInInnermostScope(); +} +bool +BytecodeEmitter::emitAwaitInScope(EmitterScope& currentScope) +{ + if (!emitGetDotGeneratorInScope(currentScope)) + return false; + if (!emitYieldOp(JSOP_AWAIT)) + return false; return true; } bool -BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) +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 @@ -8586,8 +8802,14 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) MOZ_ASSERT(this->stackDepth == startDepth); + // 11.4.3.7 AsyncGeneratorYield step 5. + if (isAsyncGenerator) { + if (!emitAwaitInInnermostScope()) // NEXT ITER RESULT + return false; + } + // Load the generator object. - if (!emitTree(gen)) // ITER RESULT GENOBJ + if (!emitGetDotGeneratorInInnermostScope()) // NEXT ITER RESULT GENOBJ return false; // Yield RESULT as-is, without re-boxing. @@ -8623,7 +8845,8 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) // // 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 (!emitIteratorCloseInInnermostScope(iterKind)) // NEXT ITER RESULT EXCEPTION return false; if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_ITERATOR_NO_THROW)) // throw return false; @@ -8639,6 +8862,12 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitCall(JSOP_CALL, 1, iter)) // ITER OLDRESULT RESULT return false; checkTypeSet(JSOP_CALL); + + if (isAsyncGenerator) { + if (!emitAwaitInInnermostScope()) // NEXT ITER OLDRESULT RESULT + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) // ITER OLDRESULT RESULT return false; if (!emit1(JSOP_SWAP)) // ITER RESULT OLDRESULT @@ -8705,6 +8934,11 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) return false; checkTypeSet(JSOP_CALL); + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) // ... FTYPE FVALUE RESULT + return false; + } + // Step v. if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ITER OLDRESULT FTYPE FVALUE RESULT return false; @@ -8779,9 +9013,15 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) return false; if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT return false; + checkTypeSet(JSOP_CALL); + + if (isAsyncGenerator) { + if (!emitAwaitInInnermostScope()) // NEXT 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: @@ -10131,7 +10371,7 @@ BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn) { ParseNode* funBody = pn->last(); FunctionBox* funbox = sc->asFunctionBox(); - EmitterScope* funScope = innermostEmitterScope; + EmitterScope* funScope = innermostEmitterScope(); bool hasParameterExprs = funbox->hasParameterExprs; bool hasRest = funbox->hasRest(); @@ -10312,22 +10552,26 @@ BytecodeEmitter::emitFunctionBody(ParseNode* funBody) if (!emitTree(funBody)) return false; - if (funbox->isGenerator()) { + if (funbox->needsFinalYield()) { // If we fall off the end of a generator, do a final yield. - if (funbox->isStarGenerator() && !emitPrepareIteratorResult()) - return false; + bool needsIteratorResult = funbox->needsIteratorResult(); + if (needsIteratorResult) { + if (!emitPrepareIteratorResult()) + return false; + } if (!emit1(JSOP_UNDEFINED)) return false; - if (sc->asFunctionBox()->isStarGenerator() && !emitFinishIteratorResult(true)) - return false; + if (needsIteratorResult) { + if (!emitFinishIteratorResult(true)) + return false; + } if (!emit1(JSOP_SETRVAL)) return false; - NameLocation loc = *locationOfNameBoundInFunctionScope(cx->names().dotGenerator); - if (!emitGetNameAtLocation(cx->names().dotGenerator, loc)) + if (!emitGetDotGeneratorInInnermostScope()) return false; // No need to check for finally blocks, etc as in EmitReturn. @@ -10587,7 +10831,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: break; case PNK_YIELD_STAR: - if (!emitYieldStar(pn->pn_left, pn->pn_right)) + if (!emitYieldStar(pn->pn_kid)) return false; break; @@ -10596,12 +10840,21 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: return false; break; + case PNK_INITIALYIELD: + if (!emitInitialYield(pn)) + return false; + break; + case PNK_YIELD: - case PNK_AWAIT: if (!emitYield(pn)) return false; break; + case PNK_AWAIT: + if (!emitAwaitInInnermostScope(pn)) + return false; + break; + case PNK_STATEMENTLIST: if (!emitStatementList(pn)) return false; @@ -11281,7 +11534,7 @@ CGScopeNoteList::finish(ScopeNoteArray* array, uint32_t prologueLength) } void -CGYieldOffsetList::finish(YieldOffsetArray& array, uint32_t prologueLength) +CGYieldAndAwaitOffsetList::finish(YieldAndAwaitOffsetArray& array, uint32_t prologueLength) { MOZ_ASSERT(length() == array.length()); diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 595ee6405..8ad409c11 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" @@ -98,15 +99,15 @@ struct CGScopeNoteList { void finish(ScopeNoteArray* array, uint32_t prologueLength); }; -struct CGYieldOffsetList { +struct CGYieldAndAwaitOffsetList { Vector<uint32_t> list; uint32_t numYields; uint32_t numAwaits; - explicit CGYieldOffsetList(ExclusiveContext* cx) : list(cx), numYields(0), numAwaits(0) {} + explicit CGYieldAndAwaitOffsetList(ExclusiveContext* cx) : list(cx), numYields(0), numAwaits(0) {} MOZ_MUST_USE bool append(uint32_t offset) { return list.append(offset); } size_t length() const { return list.length(); } - void finish(YieldOffsetArray& array, uint32_t prologueLength); + void finish(YieldAndAwaitOffsetArray& array, uint32_t prologueLength); }; static size_t MaxBytecodeLength = INT32_MAX; @@ -227,9 +228,23 @@ struct MOZ_STACK_CLASS BytecodeEmitter EmitterScope* varEmitterScope; NestableControl* innermostNestableControl; - EmitterScope* innermostEmitterScope; + EmitterScope* innermostEmitterScope_; TDZCheckCache* innermostTDZCheckCache; +#ifdef DEBUG + bool unstableEmitterScope; + + friend class AutoCheckUnstableEmitterScope; +#endif + + EmitterScope* innermostEmitterScope() const { + MOZ_ASSERT(!unstableEmitterScope); + return innermostEmitterScopeNoCheck(); + } + EmitterScope* innermostEmitterScopeNoCheck() const { + return innermostEmitterScope_; + } + CGConstList constList; /* constants to be included with the script */ CGObjectList objectList; /* list of emitted objects */ CGScopeList scopeList; /* list of emitted scopes */ @@ -237,10 +252,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter CGScopeNoteList scopeNoteList; /* list of emitted block scope notes */ /* - * For each yield op, map the yield index (stored as bytecode operand) to - * the offset of the next op. + * For each yield or await op, map the yield and await index (stored as + * bytecode operand) to the offset of the next op. */ - CGYieldOffsetList yieldOffsetList; + CGYieldAndAwaitOffsetList yieldAndAwaitOffsetList; uint16_t typesetCount; /* Number of JOF_TYPESET opcodes generated */ @@ -318,7 +333,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter EmitterScope* source); mozilla::Maybe<NameLocation> locationOfNameBoundInFunctionScope(JSAtom* name) { - return locationOfNameBoundInFunctionScope(name, innermostEmitterScope); + return locationOfNameBoundInFunctionScope(name, innermostEmitterScope()); } void setVarEmitterScope(EmitterScope* emitterScope) { @@ -606,17 +621,29 @@ 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 emitGetDotGeneratorInInnermostScope() { + return emitGetDotGeneratorInScope(*innermostEmitterScope()); + } + MOZ_MUST_USE bool emitGetDotGeneratorInScope(EmitterScope& currentScope); + + MOZ_MUST_USE bool emitInitialYield(ParseNode* pn); MOZ_MUST_USE bool emitYield(ParseNode* pn); MOZ_MUST_USE bool emitYieldOp(JSOp op); - MOZ_MUST_USE bool emitYieldStar(ParseNode* iter, ParseNode* gen); - + MOZ_MUST_USE bool emitYieldStar(ParseNode* iter); + MOZ_MUST_USE bool emitAwaitInInnermostScope() { + return emitAwaitInScope(*innermostEmitterScope()); + } + MOZ_MUST_USE bool emitAwaitInInnermostScope(ParseNode* pn); + MOZ_MUST_USE bool emitAwaitInScope(EmitterScope& currentScope); MOZ_MUST_USE bool emitPropLHS(ParseNode* pn); MOZ_MUST_USE bool emitPropOp(ParseNode* pn, JSOp op); MOZ_MUST_USE bool emitPropIncDec(ParseNode* pn); MOZ_MUST_USE bool emitAsyncWrapperLambda(unsigned index, bool isArrow); - MOZ_MUST_USE bool emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow); + MOZ_MUST_USE bool emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow, + bool isStarGenerator); MOZ_MUST_USE bool emitComputedPropertyName(ParseNode* computedPropName); @@ -703,11 +730,22 @@ 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, - bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, IteratorKind kind = IteratorKind::Sync, + bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorCloseInScope(EmitterScope& currentScope, + IteratorKind iterKind = IteratorKind::Sync, + CompletionKind completionKind = CompletionKind::Normal, + bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorCloseInInnermostScope(IteratorKind iterKind = IteratorKind::Sync, + CompletionKind completionKind = CompletionKind::Normal, + bool allowSelfHosted = false) { + return emitIteratorCloseInScope(*innermostEmitterScope(), iterKind, completionKind, + allowSelfHosted); + } template <typename InnerEmitter> MOZ_MUST_USE bool wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, @@ -805,6 +843,31 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall = false); }; +class MOZ_RAII AutoCheckUnstableEmitterScope { +#ifdef DEBUG + bool prev_; + BytecodeEmitter* bce_; +#endif + + public: + AutoCheckUnstableEmitterScope() = delete; + explicit AutoCheckUnstableEmitterScope(BytecodeEmitter* bce) +#ifdef DEBUG + : bce_(bce) +#endif + { +#ifdef DEBUG + prev_ = bce_->unstableEmitterScope; + bce_->unstableEmitterScope = true; +#endif + } + ~AutoCheckUnstableEmitterScope() { +#ifdef DEBUG + bce_->unstableEmitterScope = prev_; +#endif + } +}; + } /* namespace frontend */ } /* namespace js */ diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 689fa02b4..16294c4a8 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -117,9 +117,10 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) // These two aren't statements in the spec, but we sometimes insert them // in statement lists anyway. + case PNK_INITIALYIELD: case PNK_YIELD_STAR: case PNK_YIELD: - MOZ_ASSERT(node->isArity(PN_BINARY)); + MOZ_ASSERT(node->isArity(PN_UNARY)); *result = false; return true; @@ -1775,21 +1776,23 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo case PNK_GENEXP: return FoldList(cx, pn, parser, inGenexpLambda); + case PNK_INITIALYIELD: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + MOZ_ASSERT(pn->pn_kid->isKind(PNK_ASSIGN) && + pn->pn_kid->pn_left->isKind(PNK_NAME) && + pn->pn_kid->pn_right->isKind(PNK_GENERATOR)); + return true; + case PNK_YIELD_STAR: - MOZ_ASSERT(pn->isArity(PN_BINARY)); - MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME)); - return Fold(cx, &pn->pn_left, parser, inGenexpLambda); + MOZ_ASSERT(pn->isArity(PN_UNARY)); + return Fold(cx, &pn->pn_kid, parser, inGenexpLambda); case PNK_YIELD: case PNK_AWAIT: - MOZ_ASSERT(pn->isArity(PN_BINARY)); - MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME) || - (pn->pn_right->isKind(PNK_ASSIGN) && - pn->pn_right->pn_left->isKind(PNK_NAME) && - pn->pn_right->pn_right->isKind(PNK_GENERATOR))); - if (!pn->pn_left) + MOZ_ASSERT(pn->isArity(PN_UNARY)); + if (!pn->pn_kid) return true; - return Fold(cx, &pn->pn_left, parser, inGenexpLambda); + return Fold(cx, &pn->pn_kid, parser, inGenexpLambda); case PNK_RETURN: return FoldReturn(cx, pn, parser, inGenexpLambda); diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 2d7f57e1e..44694298b 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -440,20 +440,24 @@ class FullParseHandler return true; } - ParseNode* newYieldExpression(uint32_t begin, ParseNode* value, ParseNode* gen, - JSOp op = JSOP_YIELD) { + ParseNode* newInitialYieldExpression(uint32_t begin, ParseNode* gen) { + TokenPos pos(begin, begin + 1); + return new_<UnaryNode>(PNK_INITIALYIELD, JSOP_INITIALYIELD, pos, gen); + } + + ParseNode* newYieldExpression(uint32_t begin, ParseNode* value) { TokenPos pos(begin, value ? value->pn_pos.end : begin + 1); - return new_<BinaryNode>(PNK_YIELD, op, pos, value, gen); + return new_<UnaryNode>(PNK_YIELD, JSOP_YIELD, pos, value); } - ParseNode* newYieldStarExpression(uint32_t begin, ParseNode* value, ParseNode* gen) { + ParseNode* newYieldStarExpression(uint32_t begin, ParseNode* value) { TokenPos pos(begin, value->pn_pos.end); - return new_<BinaryNode>(PNK_YIELD_STAR, JSOP_NOP, pos, value, gen); + return new_<UnaryNode>(PNK_YIELD_STAR, JSOP_NOP, pos, value); } - ParseNode* newAwaitExpression(uint32_t begin, ParseNode* value, ParseNode* gen) { + ParseNode* newAwaitExpression(uint32_t begin, ParseNode* value) { TokenPos pos(begin, value ? value->pn_pos.end : begin + 1); - return new_<BinaryNode>(PNK_AWAIT, JSOP_YIELD, pos, value, gen); + return new_<UnaryNode>(PNK_AWAIT, JSOP_AWAIT, pos, value); } // Statements @@ -506,8 +510,7 @@ class FullParseHandler if (!genInit) return false; - ParseNode* initialYield = newYieldExpression(yieldPos.begin, nullptr, genInit, - JSOP_INITIALYIELD); + ParseNode* initialYield = newInitialYieldExpression(yieldPos.begin, genInit); if (!initialYield) return false; diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index dc54d0a88..376be7624 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -501,24 +501,25 @@ class NameResolver return false; break; + case PNK_INITIALYIELD: + MOZ_ASSERT(cur->pn_kid->isKind(PNK_ASSIGN) && + cur->pn_kid->pn_left->isKind(PNK_NAME) && + cur->pn_kid->pn_right->isKind(PNK_GENERATOR)); + break; + case PNK_YIELD_STAR: - MOZ_ASSERT(cur->isArity(PN_BINARY)); - MOZ_ASSERT(cur->pn_right->isKind(PNK_NAME)); - if (!resolve(cur->pn_left, prefix)) + MOZ_ASSERT(cur->isArity(PN_UNARY)); + if (!resolve(cur->pn_kid, prefix)) return false; break; case PNK_YIELD: case PNK_AWAIT: - MOZ_ASSERT(cur->isArity(PN_BINARY)); - if (cur->pn_left) { - if (!resolve(cur->pn_left, prefix)) + MOZ_ASSERT(cur->isArity(PN_UNARY)); + if (cur->pn_kid) { + if (!resolve(cur->pn_kid, prefix)) return false; } - MOZ_ASSERT(cur->pn_right->isKind(PNK_NAME) || - (cur->pn_right->isKind(PNK_ASSIGN) && - cur->pn_right->pn_left->isKind(PNK_NAME) && - cur->pn_right->pn_right->isKind(PNK_GENERATOR))); break; case PNK_RETURN: diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 5fe64e3d3..42ae9451a 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -286,22 +286,24 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) return PushResult::Recyclable; } - // The left half is the expression being yielded. The right half is - // internal goop: a name reference to the invisible '.generator' local - // variable, or an assignment of a PNK_GENERATOR node to the '.generator' - // local, for a synthesized, prepended initial yield. Yum! + // The child is an assignment of a PNK_GENERATOR node to the + // '.generator' local, for a synthesized, prepended initial yield. + case PNK_INITIALYIELD: { + MOZ_ASSERT(pn->isArity(PN_UNARY)); + MOZ_ASSERT(pn->pn_kid->isKind(PNK_ASSIGN) && + pn->pn_kid->pn_left->isKind(PNK_NAME) && + pn->pn_kid->pn_right->isKind(PNK_GENERATOR)); + stack->push(pn->pn_kid); + return PushResult::Recyclable; + } + + // The child is the expression being yielded. case PNK_YIELD_STAR: case PNK_YIELD: case PNK_AWAIT: { - MOZ_ASSERT(pn->isArity(PN_BINARY)); - MOZ_ASSERT(pn->pn_right); - MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME) || - (pn->pn_right->isKind(PNK_ASSIGN) && - pn->pn_right->pn_left->isKind(PNK_NAME) && - pn->pn_right->pn_right->isKind(PNK_GENERATOR))); - if (pn->pn_left) - stack->push(pn->pn_left); - stack->push(pn->pn_right); + MOZ_ASSERT(pn->isArity(PN_UNARY)); + if (pn->pn_kid) + stack->push(pn->pn_kid); return PushResult::Recyclable; } diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 1f20f3988..9a435e270 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -83,6 +83,7 @@ class ObjectBox; F(THROW) \ F(DEBUGGER) \ F(GENERATOR) \ + F(INITIALYIELD) \ F(YIELD) \ F(YIELD_STAR) \ F(GENEXP) \ @@ -418,8 +419,9 @@ IsTypeofKind(ParseNodeKind kind) * PNK_LEXICALSCOPE scope pn_u.scope.bindings: scope bindings * pn_u.scope.body: scope body * PNK_GENERATOR nullary - * PNK_YIELD, binary pn_left: expr or null; pn_right: generator object - * PNK_YIELD_STAR + * PNK_INITIALYIELD unary pn_kid: generator object + * PNK_YIELD, unary pn_kid: expr or null + * PNK_YIELD_STAR, * PNK_ARRAYCOMP list pn_count: 1 * pn_head: list of 1 element, which is block * enclosing for loop(s) and optionally diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 0c279591f..cf9f1e27c 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -2474,10 +2474,8 @@ Parser<SyntaxParseHandler>::finishFunction(bool isStandaloneFunction /* = false } static YieldHandling -GetYieldHandling(GeneratorKind generatorKind, FunctionAsyncKind asyncKind) +GetYieldHandling(GeneratorKind generatorKind) { - if (asyncKind == AsyncFunction) - return YieldIsName; if (generatorKind == NotGenerator) return YieldIsName; return YieldIsKeyword; @@ -2541,7 +2539,7 @@ Parser<FullParseHandler>::standaloneFunction(HandleFunction fun, return null(); funpc.setIsStandaloneFunctionBody(); - YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); + YieldHandling yieldHandling = GetYieldHandling(generatorKind); AutoAwaitIsKeyword<FullParseHandler> awaitIsKeyword(this, asyncKind == AsyncFunction); if (!functionFormalParametersAndBody(InAllowed, yieldHandling, fn, Statement, parameterListEnd, /* isStandaloneFunction = */ true)) @@ -2699,7 +2697,7 @@ Parser<ParseHandler>::functionBody(InHandling inHandling, YieldHandling yieldHan switch (pc->generatorKind()) { case NotGenerator: - MOZ_ASSERT(pc->lastYieldOffset == startYieldOffset); + MOZ_ASSERT_IF(!pc->isAsync(), pc->lastYieldOffset == startYieldOffset); break; case LegacyGenerator: @@ -2715,12 +2713,12 @@ Parser<ParseHandler>::functionBody(InHandling inHandling, YieldHandling yieldHan break; case StarGenerator: - MOZ_ASSERT_IF(!pc->isAsync(), kind != Arrow); - MOZ_ASSERT_IF(!pc->isAsync(), type == StatementListBody); + MOZ_ASSERT(kind != Arrow); + MOZ_ASSERT(type == StatementListBody); break; } - if (pc->isGenerator()) { + if (pc->needsDotGeneratorName()) { MOZ_ASSERT_IF(!pc->isAsync(), type == StatementListBody); if (!declareDotGeneratorName()) return null(); @@ -2761,9 +2759,9 @@ Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind, #endif switch (kind) { case Expression: - flags = (generatorKind == NotGenerator + flags = (generatorKind == NotGenerator && asyncKind == SyncFunction ? JSFunction::INTERPRETED_LAMBDA - : JSFunction::INTERPRETED_LAMBDA_GENERATOR); + : JSFunction::INTERPRETED_LAMBDA_GENERATOR_OR_ASYNC); break; case Arrow: flags = JSFunction::INTERPRETED_LAMBDA_ARROW; @@ -2771,9 +2769,9 @@ Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind, break; case Method: MOZ_ASSERT(generatorKind == NotGenerator || generatorKind == StarGenerator); - flags = (generatorKind == NotGenerator + flags = (generatorKind == NotGenerator && asyncKind == SyncFunction ? JSFunction::INTERPRETED_METHOD - : JSFunction::INTERPRETED_METHOD_GENERATOR); + : JSFunction::INTERPRETED_METHOD_GENERATOR_OR_ASYNC); allocKind = gc::AllocKind::FUNCTION_EXTENDED; break; case ClassConstructor: @@ -2799,9 +2797,9 @@ Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind, allocKind = gc::AllocKind::FUNCTION_EXTENDED; } #endif - flags = (generatorKind == NotGenerator + flags = (generatorKind == NotGenerator && asyncKind == SyncFunction ? JSFunction::INTERPRETED_NORMAL - : JSFunction::INTERPRETED_GENERATOR); + : JSFunction::INTERPRETED_GENERATOR_OR_ASYNC); } // We store the async wrapper in a slot for later access. @@ -3324,7 +3322,6 @@ Parser<ParseHandler>::functionDefinition(uint32_t toStringStart, Node pn, InHand bool tryAnnexB /* = false */) { MOZ_ASSERT_IF(kind == Statement, funName); - MOZ_ASSERT_IF(asyncKind == AsyncFunction, generatorKind == StarGenerator); // When fully parsing a LazyScript, we do not fully reparse its inner // functions, which are also lazy. Instead, their free variables and @@ -3336,7 +3333,7 @@ Parser<ParseHandler>::functionDefinition(uint32_t toStringStart, Node pn, InHand } RootedObject proto(context); - if (generatorKind == StarGenerator) { + if (generatorKind == StarGenerator || asyncKind == AsyncFunction) { // If we are off the main thread, the generator meta-objects have // already been created by js::StartOffThreadParseScript, so cx will not // be necessary. @@ -3408,7 +3405,7 @@ Parser<FullParseHandler>::trySyntaxParseInnerFunction(ParseNode* pn, HandleFunct // parse to avoid the overhead of a lazy syntax-only parse. Although // the prediction may be incorrect, IIFEs are common enough that it // pays off for lots of code. - if (pn->isLikelyIIFE() && generatorKind == NotGenerator) + if (pn->isLikelyIIFE() && generatorKind == NotGenerator && asyncKind == SyncFunction) break; Parser<SyntaxParseHandler>* parser = handler.syntaxParser; @@ -3584,7 +3581,7 @@ Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, bool strict if (!tokenStream.peekTokenPos(&pn->pn_pos, modifier)) return null(); - YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); + YieldHandling yieldHandling = GetYieldHandling(generatorKind); FunctionSyntaxKind syntaxKind = Statement; if (fun->isClassConstructor()) syntaxKind = ClassConstructor; @@ -3665,7 +3662,7 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, return false; uint32_t openedPos = 0; if (tt != TOK_LC) { - if ((funbox->isStarGenerator() && !funbox->isAsync()) || kind == Method || + if (funbox->isStarGenerator() || kind == Method || kind == GetterNoExpressionClosure || kind == SetterNoExpressionClosure || IsConstructorKind(kind)) { error(JSMSG_CURLY_BEFORE_BODY); @@ -3695,7 +3692,7 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, // whether the arrow function is enclosed in a generator function or not. // Whereas the |yield| in the function body is always parsed as a name. // The same goes when parsing |await| in arrow functions. - YieldHandling bodyYieldHandling = GetYieldHandling(pc->generatorKind(), pc->asyncKind()); + YieldHandling bodyYieldHandling = GetYieldHandling(pc->generatorKind()); Node body; { AutoAwaitIsKeyword<ParseHandler> awaitIsKeyword(this, funbox->isAsync()); @@ -3786,12 +3783,8 @@ Parser<ParseHandler>::functionStmt(uint32_t toStringStart, YieldHandling yieldHa if (!tokenStream.getToken(&tt)) return null(); - GeneratorKind generatorKind = asyncKind == AsyncFunction ? StarGenerator : NotGenerator; + GeneratorKind generatorKind = NotGenerator; if (tt == TOK_MUL) { - if (asyncKind != SyncFunction) { - error(JSMSG_ASYNC_GENERATOR); - return null(); - } generatorKind = StarGenerator; if (!tokenStream.getToken(&tt)) return null(); @@ -3817,7 +3810,7 @@ Parser<ParseHandler>::functionStmt(uint32_t toStringStart, YieldHandling yieldHa MOZ_ASSERT(declaredInStmt->kind() != StatementKind::Label); MOZ_ASSERT(StatementKindIsBraced(declaredInStmt->kind())); - if (!pc->sc()->strict() && generatorKind == NotGenerator) { + if (!pc->sc()->strict() && generatorKind == NotGenerator && asyncKind == SyncFunction) { // In sloppy mode, try Annex B.3.3 semantics. If making an // additional 'var' binding of the same name does not throw an // early error, do so. This 'var' binding would be assigned @@ -3841,7 +3834,7 @@ Parser<ParseHandler>::functionStmt(uint32_t toStringStart, YieldHandling yieldHa if (!pn) return null(); - YieldHandling newYieldHandling = GetYieldHandling(generatorKind, asyncKind); + YieldHandling newYieldHandling = GetYieldHandling(generatorKind); return functionDefinition(toStringStart, pn, InAllowed, newYieldHandling, name, Statement, generatorKind, asyncKind, tryAnnexB); } @@ -3854,22 +3847,18 @@ Parser<ParseHandler>::functionExpr(uint32_t toStringStart, InvokedPrediction inv MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION)); AutoAwaitIsKeyword<ParseHandler> awaitIsKeyword(this, asyncKind == AsyncFunction); - GeneratorKind generatorKind = asyncKind == AsyncFunction ? StarGenerator : NotGenerator; + GeneratorKind generatorKind = NotGenerator; TokenKind tt; if (!tokenStream.getToken(&tt)) return null(); if (tt == TOK_MUL) { - if (asyncKind != SyncFunction) { - error(JSMSG_ASYNC_GENERATOR); - return null(); - } generatorKind = StarGenerator; if (!tokenStream.getToken(&tt)) return null(); } - YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); + YieldHandling yieldHandling = GetYieldHandling(generatorKind); RootedPropertyName name(context); if (TokenKindIsPossibleIdentifier(tt)) { @@ -5971,6 +5960,7 @@ Parser<ParseHandler>::matchInOrOf(bool* isForInp, bool* isForOfp) template <class ParseHandler> bool Parser<ParseHandler>::forHeadStart(YieldHandling yieldHandling, + IteratorKind iterKind, ParseNodeKind* forHeadKind, Node* forInitialPart, Maybe<ParseContext::Scope>& forLoopLexicalScope, @@ -6061,6 +6051,11 @@ Parser<ParseHandler>::forHeadStart(YieldHandling yieldHandling, if (!matchInOrOf(&isForIn, &isForOf)) return false; + if (iterKind == IteratorKind::Async && !isForOf) { + error(JSMSG_FOR_AWAIT_NOT_OF); + return false; + } + // If we don't encounter 'in'/'of', we have a for(;;) loop. We've handled // the init expression; the caller handles the rest. Allow the Operand // modifier when regetting: Operand must be used to examine the ';' in @@ -6134,6 +6129,7 @@ Parser<ParseHandler>::forStatement(YieldHandling yieldHandling) ParseContext::Statement stmt(pc, StatementKind::ForLoop); bool isForEach = false; + IteratorKind iterKind = IteratorKind::Sync; unsigned iflags = 0; if (allowsForEachIn()) { @@ -6148,6 +6144,17 @@ Parser<ParseHandler>::forStatement(YieldHandling yieldHandling) } } + if (pc->isAsync()) { + bool matched; + if (!tokenStream.matchToken(&matched, TOK_AWAIT)) + return null(); + + if (matched) { + iflags |= JSITER_FORAWAITOF; + iterKind = IteratorKind::Async; + } + } + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); // PNK_FORHEAD, PNK_FORIN, or PNK_FOROF depending on the loop type. @@ -6185,7 +6192,7 @@ Parser<ParseHandler>::forStatement(YieldHandling yieldHandling) // // In either case the subsequent token can be consistently accessed using // TokenStream::None semantics. - if (!forHeadStart(yieldHandling, &headKind, &startNode, forLoopLexicalScope, + if (!forHeadStart(yieldHandling, iterKind, &headKind, &startNode, forLoopLexicalScope, &iteratedExpr)) { return null(); @@ -6551,29 +6558,6 @@ Parser<ParseHandler>::returnStatement(YieldHandling yieldHandling) template <typename ParseHandler> typename ParseHandler::Node -Parser<ParseHandler>::newYieldExpression(uint32_t begin, typename ParseHandler::Node expr, - bool isYieldStar) -{ - Node generator = newDotGeneratorName(); - if (!generator) - return null(); - if (isYieldStar) - return handler.newYieldStarExpression(begin, expr, generator); - return handler.newYieldExpression(begin, expr, generator); -} - -template <typename ParseHandler> -typename ParseHandler::Node -Parser<ParseHandler>::newAwaitExpression(uint32_t begin, typename ParseHandler::Node expr) -{ - Node generator = newDotGeneratorName(); - if (!generator) - return null(); - return handler.newAwaitExpression(begin, expr, generator); -} - -template <typename ParseHandler> -typename ParseHandler::Node Parser<ParseHandler>::yieldExpression(InHandling inHandling) { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_YIELD)); @@ -6620,7 +6604,9 @@ Parser<ParseHandler>::yieldExpression(InHandling inHandling) if (!exprNode) return null(); } - return newYieldExpression(begin, exprNode, kind == PNK_YIELD_STAR); + if (kind == PNK_YIELD_STAR) + return handler.newYieldStarExpression(begin, exprNode); + return handler.newYieldExpression(begin, exprNode); } case NotGenerator: @@ -6697,7 +6683,7 @@ Parser<ParseHandler>::yieldExpression(InHandling inHandling) return null(); } - return newYieldExpression(begin, exprNode); + return handler.newYieldExpression(begin, exprNode); } } @@ -8187,7 +8173,6 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl uint32_t toStringStart = pos().begin; tokenStream.ungetToken(); - GeneratorKind generatorKind = NotGenerator; FunctionAsyncKind asyncKind = SyncFunction; if (next == TOK_ASYNC) { @@ -8200,7 +8185,6 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl if (nextSameLine == TOK_ARROW) { tokenStream.ungetToken(); } else { - generatorKind = StarGenerator; asyncKind = AsyncFunction; } } @@ -8210,7 +8194,7 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl return null(); Node arrowFunc = functionDefinition(toStringStart, pn, inHandling, yieldHandling, nullptr, - Arrow, generatorKind, asyncKind); + Arrow, NotGenerator, asyncKind); if (!arrowFunc) return null(); @@ -8454,7 +8438,7 @@ Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling t if (!kid) return null(); pc->lastAwaitOffset = begin; - return newAwaitExpression(begin, kid); + return handler.newAwaitExpression(begin, kid); } } @@ -8712,7 +8696,7 @@ Parser<ParseHandler>::comprehensionTail(GeneratorKind comprehensionKind) return handler.newArrayPush(begin, bodyExpr); MOZ_ASSERT(comprehensionKind == StarGenerator); - Node yieldExpr = newYieldExpression(begin, bodyExpr); + Node yieldExpr = handler.newYieldExpression(begin, bodyExpr); if (!yieldExpr) return null(); yieldExpr = handler.parenthesize(yieldExpr); @@ -9494,11 +9478,6 @@ Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, bool isGenerator = false; bool isAsync = false; - if (ltok == TOK_MUL) { - isGenerator = true; - if (!tokenStream.getToken(<ok)) - return null(); - } if (ltok == TOK_ASYNC) { // AsyncMethod[Yield, Await]: @@ -9526,9 +9505,10 @@ Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, } } - if (isAsync && isGenerator) { - error(JSMSG_ASYNC_GENERATOR); - return null(); + if (ltok == TOK_MUL) { + isGenerator = true; + if (!tokenStream.getToken(<ok)) + return null(); } propAtom.set(nullptr); @@ -9950,8 +9930,7 @@ Parser<ParseHandler>::methodDefinition(uint32_t toStringStart, PropertyType prop MOZ_CRASH("Parser: methodDefinition: unexpected property type"); } - GeneratorKind generatorKind = (propType == PropertyType::GeneratorMethod || - propType == PropertyType::AsyncMethod) + GeneratorKind generatorKind = propType == PropertyType::GeneratorMethod ? StarGenerator : NotGenerator; @@ -9959,7 +9938,7 @@ Parser<ParseHandler>::methodDefinition(uint32_t toStringStart, PropertyType prop ? AsyncFunction : SyncFunction; - YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); + YieldHandling yieldHandling = GetYieldHandling(generatorKind); Node pn = handler.newFunctionExpression(); if (!pn) diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 88d2dad18..243a77083 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -12,6 +12,7 @@ #include "mozilla/Array.h" #include "mozilla/Maybe.h" +#include "jsiter.h" #include "jspubtd.h" #include "frontend/BytecodeCompiler.h" @@ -496,10 +497,6 @@ class ParseContext : public Nestable<ParseContext> return sc_->isFunctionBox() ? sc_->asFunctionBox()->generatorKind() : NotGenerator; } - bool isGenerator() const { - return generatorKind() != NotGenerator; - } - bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; } @@ -512,6 +509,10 @@ class ParseContext : public Nestable<ParseContext> return sc_->isFunctionBox() && sc_->asFunctionBox()->isAsync(); } + bool needsDotGeneratorName() const { + return isStarGenerator() || isLegacyGenerator() || isAsync(); + } + FunctionAsyncKind asyncKind() const { return isAsync() ? AsyncFunction : SyncFunction; } @@ -818,7 +819,9 @@ class ParserBase : public StrictModeGetter // whether it's prohibited due to strictness, JS version, or occurrence // inside a star generator. bool yieldExpressionsSupported() { - return (versionNumber() >= JSVERSION_1_7 || pc->isGenerator()) && !pc->isAsync(); + return (versionNumber() >= JSVERSION_1_7 && !pc->isAsync()) || + pc->isStarGenerator() || + pc->isLegacyGenerator(); } virtual bool strictMode() { return pc->sc()->strict(); } @@ -1108,8 +1111,6 @@ class Parser final : public ParserBase, private JS::AutoGCRooter inline Node newName(PropertyName* name); inline Node newName(PropertyName* name, TokenPos pos); - inline Node newYieldExpression(uint32_t begin, Node expr, bool isYieldStar = false); - inline Node newAwaitExpression(uint32_t begin, Node expr); inline bool abortIfSyntaxParser(); @@ -1196,6 +1197,7 @@ class Parser final : public ParserBase, private JS::AutoGCRooter Node forStatement(YieldHandling yieldHandling); bool forHeadStart(YieldHandling yieldHandling, + IteratorKind iterKind, ParseNodeKind* forHeadKind, Node* forInitialPart, mozilla::Maybe<ParseContext::Scope>& forLetImpliedScope, diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index 013444690..8a5f6c0bd 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -520,7 +520,9 @@ class FunctionBox : public ObjectBox, public SharedContext return hasExtensibleScope() || needsHomeObject() || isDerivedClassConstructor() || - isGenerator(); + isStarGenerator() || + isLegacyGenerator() || + isAsync(); } bool hasExtraBodyVarScope() const { @@ -531,7 +533,7 @@ class FunctionBox : public ObjectBox, public SharedContext bool needsExtraBodyVarEnvironmentRegardlessOfBindings() const { MOZ_ASSERT(hasParameterExprs); - return hasExtensibleScope() || isGenerator(); + return hasExtensibleScope() || needsDotGeneratorName(); } bool isLikelyConstructorWrapper() const { @@ -539,10 +541,21 @@ class FunctionBox : public ObjectBox, public SharedContext } GeneratorKind generatorKind() const { return GeneratorKindFromBits(generatorKindBits_); } - bool isGenerator() const { return generatorKind() != NotGenerator; } bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; } bool isStarGenerator() const { return generatorKind() == StarGenerator; } FunctionAsyncKind asyncKind() const { return AsyncKindFromBits(asyncKindBits_); } + + bool needsFinalYield() const { + return isStarGenerator() || isLegacyGenerator() || isAsync(); + } + bool needsDotGeneratorName() const { + return isStarGenerator() || isLegacyGenerator() || isAsync(); + } + + bool needsIteratorResult() const { + return isStarGenerator(); + } + bool isAsync() const { return asyncKind() == AsyncFunction; } bool isArrow() const { return function()->isArrow(); } @@ -560,7 +573,7 @@ class FunctionBox : public ObjectBox, public SharedContext // A generator kind can be set at initialization, or when "yield" is // first seen. In both cases the transition can only happen from // NotGenerator. - MOZ_ASSERT(!isGenerator()); + MOZ_ASSERT(!isStarGenerator() && !isLegacyGenerator()); generatorKindBits_ = GeneratorKindAsBits(kind); } @@ -655,7 +668,11 @@ SharedContext::asModuleContext() inline bool SharedContext::allBindingsClosedOver() { - return bindingsAccessedDynamically() || (isFunctionBox() && asFunctionBox()->isGenerator()); + return bindingsAccessedDynamically() || + (isFunctionBox() && + (asFunctionBox()->isStarGenerator() || + asFunctionBox()->isLegacyGenerator() || + asFunctionBox()->isAsync())); } } // namespace frontend diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index a604b599f..d919f1354 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -301,9 +301,9 @@ class SyntaxParseHandler MOZ_MUST_USE bool addSpreadProperty(Node literal, uint32_t begin, Node inner) { return true; } MOZ_MUST_USE bool addObjectMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; } MOZ_MUST_USE bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op, bool isStatic) { return true; } - Node newYieldExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; } - Node newYieldStarExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; } - Node newAwaitExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; } + Node newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; } + Node newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; } + Node newAwaitExpression(uint32_t begin, Node value) { return NodeGeneric; } // Statements diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js index d4e7e8576..0b2ee766b 100644 --- a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js @@ -9,28 +9,6 @@ async function f() { } `); -// To continue testing after uncaught exception, remember the exception and -// return normal completeion. -var currentFrame; -var uncaughtException; -dbg.uncaughtExceptionHook = function(e) { - uncaughtException = e; - return { - return: currentFrame.eval("({ done: true, value: 'uncaught' })").return - }; -}; -function testUncaughtException() { - uncaughtException = undefined; - var val = g.eval(` -var val; -f().then(v => { val = v }); -drainJobQueue(); -val; -`); - assertEq(val, "uncaught"); - assertEq(uncaughtException instanceof TypeError, true); -} - // Just continue dbg.onExceptionUnwind = function(frame) { return undefined; @@ -42,83 +20,10 @@ drainJobQueue(); assertEq(exc instanceof ReferenceError, true); `); -// Should return object. -dbg.onExceptionUnwind = function(frame) { - currentFrame = frame; - return { - return: "foo" - }; -}; -testUncaughtException(); - -// The object should have `done` property and `value` property. -dbg.onExceptionUnwind = function(frame) { - currentFrame = frame; - return { - return: frame.eval("({})").return - }; -}; -testUncaughtException(); - -// The object should have `done` property. -dbg.onExceptionUnwind = function(frame) { - currentFrame = frame; - return { - return: frame.eval("({ value: 10 })").return - }; -}; -testUncaughtException(); - -// The object should have `value` property. -dbg.onExceptionUnwind = function(frame) { - currentFrame = frame; - return { - return: frame.eval("({ done: true })").return - }; -}; -testUncaughtException(); - -// `done` property should be a boolean value. -dbg.onExceptionUnwind = function(frame) { - currentFrame = frame; - return { - return: frame.eval("({ done: 10, value: 10 })").return - }; -}; -testUncaughtException(); - -// `done` property shouldn't be an accessor. -dbg.onExceptionUnwind = function(frame) { - currentFrame = frame; - return { - return: frame.eval("({ get done() { return true; }, value: 10 })").return - }; -}; -testUncaughtException(); - -// `value` property shouldn't be an accessor. -dbg.onExceptionUnwind = function(frame) { - currentFrame = frame; - return { - return: frame.eval("({ done: true, get value() { return 10; } })").return - }; -}; -testUncaughtException(); - -// The object shouldn't be a Proxy. -dbg.onExceptionUnwind = function(frame) { - currentFrame = frame; - return { - return: frame.eval("new Proxy({ done: true, value: 10 }, {})").return - }; -}; -testUncaughtException(); - -// Correct resumption value. +// Return with resumption value. dbg.onExceptionUnwind = function(frame) { - currentFrame = frame; return { - return: frame.eval("({ done: true, value: 10 })").return + return: 10 }; }; var val = g.eval(` diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index b2f9d3b23..d254b9826 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -25,6 +25,7 @@ #include "jit/VMFunctions.h" #include "js/UniquePtr.h" #include "vm/AsyncFunction.h" +#include "vm/AsyncIteration.h" #include "vm/EnvironmentObject.h" #include "vm/Interpreter.h" #include "vm/TraceLogging.h" @@ -43,7 +44,7 @@ using mozilla::AssertedCast; BaselineCompiler::BaselineCompiler(JSContext* cx, TempAllocator& alloc, JSScript* script) : BaselineCompilerSpecific(cx, alloc, script), - yieldOffsets_(cx), + yieldAndAwaitOffsets_(cx), modifiesArguments_(false) { } @@ -210,7 +211,7 @@ BaselineCompiler::compile() pcMappingIndexEntries.length(), pcEntries.length(), bytecodeTypeMapEntries, - yieldOffsets_.length(), + yieldAndAwaitOffsets_.length(), traceLoggerToggleOffsets_.length()), JS::DeletePolicy<BaselineScript>(cx->runtime())); if (!baselineScript) { @@ -275,7 +276,7 @@ BaselineCompiler::compile() // searches for the sought entry when queries are in linear order. bytecodeMap[script->nTypeSets()] = 0; - baselineScript->copyYieldEntries(script, yieldOffsets_); + baselineScript->copyYieldAndAwaitEntries(script, yieldAndAwaitOffsets_); if (compileDebugInstrumentation_) baselineScript->setHasDebugInstrumentation(); @@ -3908,6 +3909,50 @@ BaselineCompiler::emit_JSOP_TOASYNC() return true; } +typedef JSObject* (*ToAsyncGenFn)(JSContext*, HandleFunction); +static const VMFunction ToAsyncGenInfo = + FunctionInfo<ToAsyncGenFn>(js::WrapAsyncGenerator, "ToAsyncGen"); + +bool +BaselineCompiler::emit_JSOP_TOASYNCGEN() +{ + frame.syncStack(0); + masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), R0.scratchReg()); + + prepareVMCall(); + pushArg(R0.scratchReg()); + + if (!callVM(ToAsyncGenInfo)) + return false; + + masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); + frame.pop(); + frame.push(R0); + return true; +} + +typedef JSObject* (*ToAsyncIterFn)(JSContext*, HandleObject); +static const VMFunction ToAsyncIterInfo = + FunctionInfo<ToAsyncIterFn>(js::CreateAsyncFromSyncIterator, "ToAsyncIter"); + +bool +BaselineCompiler::emit_JSOP_TOASYNCITER() +{ + frame.syncStack(0); + masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), R0.scratchReg()); + + prepareVMCall(); + pushArg(R0.scratchReg()); + + if (!callVM(ToAsyncIterInfo)) + return false; + + masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); + frame.pop(); + frame.push(R0); + return true; +} + typedef bool (*ThrowObjectCoercibleFn)(JSContext*, HandleValue); static const VMFunction ThrowObjectCoercibleInfo = FunctionInfo<ThrowObjectCoercibleFn>(ThrowObjectCoercible, "ThrowObjectCoercible"); @@ -4174,27 +4219,28 @@ BaselineCompiler::emit_JSOP_GENERATOR() } bool -BaselineCompiler::addYieldOffset() +BaselineCompiler::addYieldAndAwaitOffset() { - MOZ_ASSERT(*pc == JSOP_INITIALYIELD || *pc == JSOP_YIELD); + MOZ_ASSERT(*pc == JSOP_INITIALYIELD || *pc == JSOP_YIELD || *pc == JSOP_AWAIT); - uint32_t yieldIndex = GET_UINT24(pc); + uint32_t yieldAndAwaitIndex = GET_UINT24(pc); - while (yieldIndex >= yieldOffsets_.length()) { - if (!yieldOffsets_.append(0)) + while (yieldAndAwaitIndex >= yieldAndAwaitOffsets_.length()) { + if (!yieldAndAwaitOffsets_.append(0)) return false; } - static_assert(JSOP_INITIALYIELD_LENGTH == JSOP_YIELD_LENGTH, - "code below assumes INITIALYIELD and YIELD have same length"); - yieldOffsets_[yieldIndex] = script->pcToOffset(pc + JSOP_YIELD_LENGTH); + static_assert(JSOP_INITIALYIELD_LENGTH == JSOP_YIELD_LENGTH && + JSOP_INITIALYIELD_LENGTH == JSOP_AWAIT_LENGTH, + "code below assumes INITIALYIELD and YIELD and AWAIT have same length"); + yieldAndAwaitOffsets_[yieldAndAwaitIndex] = script->pcToOffset(pc + JSOP_YIELD_LENGTH); return true; } bool BaselineCompiler::emit_JSOP_INITIALYIELD() { - if (!addYieldOffset()) + if (!addYieldAndAwaitOffset()) return false; frame.syncStack(0); @@ -4204,7 +4250,8 @@ BaselineCompiler::emit_JSOP_INITIALYIELD() masm.unboxObject(frame.addressOfStackValue(frame.peek(-1)), genObj); MOZ_ASSERT(GET_UINT24(pc) == 0); - masm.storeValue(Int32Value(0), Address(genObj, GeneratorObject::offsetOfYieldIndexSlot())); + masm.storeValue(Int32Value(0), + Address(genObj, GeneratorObject::offsetOfYieldAndAwaitIndexSlot())); Register envObj = R0.scratchReg(); Address envChainSlot(genObj, GeneratorObject::offsetOfEnvironmentChainSlot()); @@ -4233,7 +4280,7 @@ static const VMFunction NormalSuspendInfo = bool BaselineCompiler::emit_JSOP_YIELD() { - if (!addYieldOffset()) + if (!addYieldAndAwaitOffset()) return false; // Store generator in R0. @@ -4250,7 +4297,7 @@ BaselineCompiler::emit_JSOP_YIELD() // generator is in the closing state, see GeneratorObject::suspend. masm.storeValue(Int32Value(GET_UINT24(pc)), - Address(genObj, GeneratorObject::offsetOfYieldIndexSlot())); + Address(genObj, GeneratorObject::offsetOfYieldAndAwaitIndexSlot())); Register envObj = R0.scratchReg(); Address envChainSlot(genObj, GeneratorObject::offsetOfEnvironmentChainSlot()); @@ -4282,6 +4329,12 @@ BaselineCompiler::emit_JSOP_YIELD() return emitReturn(); } +bool +BaselineCompiler::emit_JSOP_AWAIT() +{ + return emit_JSOP_YIELD(); +} + typedef bool (*DebugAfterYieldFn)(JSContext*, BaselineFrame*); static const VMFunction DebugAfterYieldInfo = FunctionInfo<DebugAfterYieldFn>(jit::DebugAfterYield, "DebugAfterYield"); @@ -4501,16 +4554,17 @@ BaselineCompiler::emit_JSOP_RESUME() masm.pushValue(retVal); if (resumeKind == GeneratorObject::NEXT) { - // Determine the resume address based on the yieldIndex and the - // yieldIndex -> native table in the BaselineScript. + // Determine the resume address based on the yieldAndAwaitIndex and the + // yieldAndAwaitIndex -> native table in the BaselineScript. masm.load32(Address(scratch1, BaselineScript::offsetOfYieldEntriesOffset()), scratch2); masm.addPtr(scratch2, scratch1); - masm.unboxInt32(Address(genObj, GeneratorObject::offsetOfYieldIndexSlot()), scratch2); + masm.unboxInt32(Address(genObj, GeneratorObject::offsetOfYieldAndAwaitIndexSlot()), + scratch2); masm.loadPtr(BaseIndex(scratch1, scratch2, ScaleFromElemWidth(sizeof(uintptr_t))), scratch1); // Mark as running and jump to the generator's JIT code. - masm.storeValue(Int32Value(GeneratorObject::YIELD_INDEX_RUNNING), - Address(genObj, GeneratorObject::offsetOfYieldIndexSlot())); + masm.storeValue(Int32Value(GeneratorObject::YIELD_AND_AWAIT_INDEX_RUNNING), + Address(genObj, GeneratorObject::offsetOfYieldAndAwaitIndexSlot())); masm.jump(scratch1); } else { MOZ_ASSERT(resumeKind == GeneratorObject::THROW || resumeKind == GeneratorObject::CLOSE); diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 95e0c77ad..a200f7ab9 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -196,6 +196,8 @@ namespace jit { _(JSOP_RUNONCE) \ _(JSOP_REST) \ _(JSOP_TOASYNC) \ + _(JSOP_TOASYNCGEN) \ + _(JSOP_TOASYNCITER) \ _(JSOP_TOID) \ _(JSOP_TOSTRING) \ _(JSOP_TABLESWITCH) \ @@ -207,6 +209,7 @@ namespace jit { _(JSOP_GENERATOR) \ _(JSOP_INITIALYIELD) \ _(JSOP_YIELD) \ + _(JSOP_AWAIT) \ _(JSOP_DEBUGAFTERYIELD) \ _(JSOP_FINALYIELDRVAL) \ _(JSOP_RESUME) \ @@ -255,9 +258,9 @@ class BaselineCompiler : public BaselineCompilerSpecific // equivalent positions when debug mode is off. CodeOffset postDebugPrologueOffset_; - // For each INITIALYIELD or YIELD op, this Vector maps the yield index - // to the bytecode offset of the next op. - Vector<uint32_t> yieldOffsets_; + // For each INITIALYIELD or YIELD or AWAIT op, this Vector maps the yield + // index to the bytecode offset of the next op. + Vector<uint32_t> yieldAndAwaitOffsets_; // Whether any on stack arguments are modified. bool modifiesArguments_; @@ -349,7 +352,7 @@ class BaselineCompiler : public BaselineCompilerSpecific MOZ_MUST_USE bool addPCMappingEntry(bool addIndexEntry); - MOZ_MUST_USE bool addYieldOffset(); + MOZ_MUST_USE bool addYieldAndAwaitOffset(); void getEnvironmentCoordinateObject(Register reg); Address getEnvironmentCoordinateAddressFromObject(Register objReg, Register reg); diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 9530f65fa..e65f10aac 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -6600,12 +6600,13 @@ ICCall_IsSuspendedStarGenerator::Compiler::generateStubCode(MacroAssembler& masm masm.branchTestObjClass(Assembler::NotEqual, genObj, scratch, &StarGeneratorObject::class_, &returnFalse); - // If the yield index slot holds an int32 value < YIELD_INDEX_CLOSING, + // If the yield index slot holds an int32 value < YIELD_AND_AWAIT_INDEX_CLOSING, // the generator is suspended. - masm.loadValue(Address(genObj, GeneratorObject::offsetOfYieldIndexSlot()), argVal); + masm.loadValue(Address(genObj, GeneratorObject::offsetOfYieldAndAwaitIndexSlot()), argVal); masm.branchTestInt32(Assembler::NotEqual, argVal, &returnFalse); masm.unboxInt32(argVal, scratch); - masm.branch32(Assembler::AboveOrEqual, scratch, Imm32(StarGeneratorObject::YIELD_INDEX_CLOSING), + masm.branch32(Assembler::AboveOrEqual, scratch, + Imm32(StarGeneratorObject::YIELD_AND_AWAIT_INDEX_CLOSING), &returnFalse); masm.moveValue(BooleanValue(true), R0); diff --git a/js/src/jit/BaselineJIT.cpp b/js/src/jit/BaselineJIT.cpp index 5c21926b5..94e018252 100644 --- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -760,12 +760,12 @@ BaselineScript::icEntryFromReturnAddress(uint8_t* returnAddr) } void -BaselineScript::copyYieldEntries(JSScript* script, Vector<uint32_t>& yieldOffsets) +BaselineScript::copyYieldAndAwaitEntries(JSScript* script, Vector<uint32_t>& yieldAndAwaitOffsets) { uint8_t** entries = yieldEntryList(); - for (size_t i = 0; i < yieldOffsets.length(); i++) { - uint32_t offset = yieldOffsets[i]; + for (size_t i = 0; i < yieldAndAwaitOffsets.length(); i++) { + uint32_t offset = yieldAndAwaitOffsets[i]; entries[i] = nativeCodeForPC(script, script->offsetToPC(offset)); } } diff --git a/js/src/jit/BaselineJIT.h b/js/src/jit/BaselineJIT.h index 5e7775a61..b6ccd2ff9 100644 --- a/js/src/jit/BaselineJIT.h +++ b/js/src/jit/BaselineJIT.h @@ -397,7 +397,7 @@ struct BaselineScript void copyICEntries(JSScript* script, const BaselineICEntry* entries, MacroAssembler& masm); void adoptFallbackStubs(FallbackICStubSpace* stubSpace); - void copyYieldEntries(JSScript* script, Vector<uint32_t>& yieldOffsets); + void copyYieldAndAwaitEntries(JSScript* script, Vector<uint32_t>& yieldAndAwaitOffsets); PCMappingIndexEntry& pcMappingIndexEntry(size_t index); CompactBufferReader pcMappingReader(size_t indexEntry); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 205812942..2b1c671d1 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -41,6 +41,7 @@ #include "jit/RangeAnalysis.h" #include "jit/SharedICHelpers.h" #include "vm/AsyncFunction.h" +#include "vm/AsyncIteration.h" #include "vm/MatchPairs.h" #include "vm/RegExpObject.h" #include "vm/RegExpStatics.h" @@ -10213,6 +10214,28 @@ CodeGenerator::visitToAsync(LToAsync* lir) callVM(ToAsyncInfo, lir); } +typedef JSObject* (*ToAsyncGenFn)(JSContext*, HandleFunction); +static const VMFunction ToAsyncGenInfo = + FunctionInfo<ToAsyncGenFn>(js::WrapAsyncGenerator, "ToAsyncGen"); + +void +CodeGenerator::visitToAsyncGen(LToAsyncGen* lir) +{ + pushArg(ToRegister(lir->unwrapped())); + callVM(ToAsyncGenInfo, lir); +} + +typedef JSObject* (*ToAsyncIterFn)(JSContext*, HandleObject); +static const VMFunction ToAsyncIterInfo = + FunctionInfo<ToAsyncIterFn>(js::CreateAsyncFromSyncIterator, "ToAsyncIter"); + +void +CodeGenerator::visitToAsyncIter(LToAsyncIter* lir) +{ + pushArg(ToRegister(lir->unwrapped())); + callVM(ToAsyncIterInfo, lir); +} + typedef bool (*ToIdFn)(JSContext*, HandleScript, jsbytecode*, HandleValue, MutableHandleValue); static const VMFunction ToIdInfo = FunctionInfo<ToIdFn>(ToIdOperation, "ToIdOperation"); diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 12f1238ef..65acfe274 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -286,6 +286,8 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitTypeOfV(LTypeOfV* lir); void visitOutOfLineTypeOfV(OutOfLineTypeOfV* ool); void visitToAsync(LToAsync* lir); + void visitToAsyncGen(LToAsyncGen* lir); + void visitToAsyncIter(LToAsyncIter* lir); void visitToIdV(LToIdV* lir); template<typename T> void emitLoadElementT(LLoadElementT* lir, const T& source); void visitLoadElementT(LLoadElementT* lir); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index b8a2d2fba..9337f6150 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -2320,7 +2320,9 @@ IonCompile(JSContext* cx, JSScript* script, static bool CheckFrame(JSContext* cx, BaselineFrame* frame) { - MOZ_ASSERT(!frame->script()->isGenerator()); + MOZ_ASSERT(!frame->script()->isStarGenerator()); + MOZ_ASSERT(!frame->script()->isLegacyGenerator()); + MOZ_ASSERT(!frame->script()->isAsync()); MOZ_ASSERT(!frame->isDebuggerEvalFrame()); MOZ_ASSERT(!frame->isEvalFrame()); @@ -2351,11 +2353,16 @@ CheckScript(JSContext* cx, JSScript* script, bool osr) return false; } - if (script->isGenerator()) { + if (script->isStarGenerator() || script->isLegacyGenerator()) { TrackAndSpewIonAbort(cx, script, "generator script"); return false; } + if (script->isAsync()) { + TrackAndSpewIonAbort(cx, script, "async script"); + return false; + } + if (script->hasNonSyntacticScope() && !script->functionNonDelazifying()) { // Support functions with a non-syntactic global scope but not other // scripts. For global scripts, IonBuilder currently uses the global diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index ace6cd81e..a4724bca4 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -4402,8 +4402,14 @@ jit::AnalyzeArgumentsUsage(JSContext* cx, JSScript* scriptArg) // direct eval is present. // // FIXME: Don't build arguments for ES6 generator expressions. - if (scriptArg->isDebuggee() || script->isGenerator() || script->bindingsAccessedDynamically()) + if (scriptArg->isDebuggee() || + script->isStarGenerator() || + script->isLegacyGenerator() || + script->isAsync() || + script->bindingsAccessedDynamically()) + { return true; + } if (!jit::IsIonEnabled(cx)) return true; diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 0c69729a4..1e12f5dbe 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -2130,6 +2130,12 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_TOASYNC: return jsop_toasync(); + case JSOP_TOASYNCGEN: + return jsop_toasyncgen(); + + case JSOP_TOASYNCITER: + return jsop_toasynciter(); + case JSOP_TOID: return jsop_toid(); @@ -3351,7 +3357,7 @@ IonBuilder::whileOrForInLoop(jssrcnote* sn) unsigned stackPhiCount; if (SN_TYPE(sn) == SRC_FOR_OF) - stackPhiCount = 2; + stackPhiCount = 3; else if (SN_TYPE(sn) == SRC_FOR_IN) stackPhiCount = 1; else @@ -13196,6 +13202,34 @@ IonBuilder::jsop_toasync() } bool +IonBuilder::jsop_toasyncgen() +{ + MDefinition* unwrapped = current->pop(); + MOZ_ASSERT(unwrapped->type() == MIRType::Object); + + MToAsyncGen* ins = MToAsyncGen::New(alloc(), unwrapped); + + current->add(ins); + current->push(ins); + + return resumeAfter(ins); +} + +bool +IonBuilder::jsop_toasynciter() +{ + MDefinition* unwrapped = current->pop(); + MOZ_ASSERT(unwrapped->type() == MIRType::Object); + + MToAsyncIter* ins = MToAsyncIter::New(alloc(), unwrapped); + + current->add(ins); + current->push(ins); + + return resumeAfter(ins); +} + +bool IonBuilder::jsop_toid() { // No-op if the index is an integer. diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 1f763a4f2..6a3b61232 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -762,6 +762,8 @@ class IonBuilder MOZ_MUST_USE bool jsop_globalthis(); MOZ_MUST_USE bool jsop_typeof(); MOZ_MUST_USE bool jsop_toasync(); + MOZ_MUST_USE bool jsop_toasyncgen(); + MOZ_MUST_USE bool jsop_toasynciter(); MOZ_MUST_USE bool jsop_toid(); MOZ_MUST_USE bool jsop_iter(uint8_t flags); MOZ_MUST_USE bool jsop_itermore(); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 22f1d5f70..108450983 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -1176,6 +1176,22 @@ LIRGenerator::visitToAsync(MToAsync* ins) } void +LIRGenerator::visitToAsyncGen(MToAsyncGen* ins) +{ + LToAsyncGen* lir = new(alloc()) LToAsyncGen(useRegisterAtStart(ins->input())); + defineReturn(lir, ins); + assignSafepoint(lir, ins); +} + +void +LIRGenerator::visitToAsyncIter(MToAsyncIter* ins) +{ + LToAsyncIter* lir = new(alloc()) LToAsyncIter(useRegisterAtStart(ins->input())); + defineReturn(lir, ins); + assignSafepoint(lir, ins); +} + +void LIRGenerator::visitToId(MToId* ins) { LToIdV* lir = new(alloc()) LToIdV(useBox(ins->input()), tempDouble()); diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 9342ef471..81e6abbbb 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -121,6 +121,8 @@ class LIRGenerator : public LIRGeneratorSpecific void visitCompare(MCompare* comp); void visitTypeOf(MTypeOf* ins); void visitToAsync(MToAsync* ins); + void visitToAsyncGen(MToAsyncGen* ins); + void visitToAsyncIter(MToAsyncIter* ins); void visitToId(MToId* ins); void visitBitNot(MBitNot* ins); void visitBitAnd(MBitAnd* ins); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 6c376d528..e7186ed30 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -5784,6 +5784,36 @@ class MToAsync TRIVIAL_NEW_WRAPPERS }; +class MToAsyncGen + : public MUnaryInstruction, + public SingleObjectPolicy::Data +{ + explicit MToAsyncGen(MDefinition* unwrapped) + : MUnaryInstruction(unwrapped) + { + setResultType(MIRType::Object); + } + + public: + INSTRUCTION_HEADER(ToAsyncGen) + TRIVIAL_NEW_WRAPPERS +}; + +class MToAsyncIter + : public MUnaryInstruction, + public SingleObjectPolicy::Data +{ + explicit MToAsyncIter(MDefinition* unwrapped) + : MUnaryInstruction(unwrapped) + { + setResultType(MIRType::Object); + } + + public: + INSTRUCTION_HEADER(ToAsyncIter) + TRIVIAL_NEW_WRAPPERS +}; + class MToId : public MUnaryInstruction, public BoxInputsPolicy::Data diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 9e460a2ba..589dde077 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -79,6 +79,8 @@ namespace jit { _(BitNot) \ _(TypeOf) \ _(ToAsync) \ + _(ToAsyncGen) \ + _(ToAsyncIter) \ _(ToId) \ _(BitAnd) \ _(BitOr) \ diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 1ff7adfd1..652c23bf1 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -735,7 +735,7 @@ bool NormalSuspend(JSContext* cx, HandleObject obj, BaselineFrame* frame, jsbytecode* pc, uint32_t stackDepth) { - MOZ_ASSERT(*pc == JSOP_YIELD); + MOZ_ASSERT(*pc == JSOP_YIELD || *pc == JSOP_AWAIT); // Return value is still on the stack. MOZ_ASSERT(stackDepth >= 1); @@ -816,7 +816,7 @@ GeneratorThrowOrClose(JSContext* cx, BaselineFrame* frame, Handle<GeneratorObjec // work. This function always returns false, so we're guaranteed to enter // the exception handler where we will clear the pc. JSScript* script = frame->script(); - uint32_t offset = script->yieldOffsets()[genObj->yieldIndex()]; + uint32_t offset = script->yieldAndAwaitOffsets()[genObj->yieldAndAwaitIndex()]; frame->setOverridePc(script->offsetToPC(offset)); MOZ_ALWAYS_TRUE(DebugAfterYield(cx, frame)); diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index ecf02816e..ff4915d1a 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -1606,6 +1606,32 @@ class LToAsync : public LCallInstructionHelper<1, 1, 0> } }; +class LToAsyncGen : public LCallInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(ToAsyncGen) + explicit LToAsyncGen(const LAllocation& input) { + setOperand(0, input); + } + + const LAllocation* unwrapped() { + return getOperand(0); + } +}; + +class LToAsyncIter : public LCallInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(ToAsyncIter) + explicit LToAsyncIter(const LAllocation& input) { + setOperand(0, input); + } + + const LAllocation* unwrapped() { + return getOperand(0); + } +}; + class LToIdV : public LInstructionHelper<BOX_PIECES, BOX_PIECES, 1> { public: diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index e260d7e94..56b98940a 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -353,6 +353,8 @@ _(Rest) \ _(TypeOfV) \ _(ToAsync) \ + _(ToAsyncGen) \ + _(ToAsyncIter) \ _(ToIdV) \ _(Floor) \ _(FloorF) \ diff --git a/js/src/js.msg b/js/src/js.msg index e2d923bc4..9dc5f4e9f 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -185,7 +185,6 @@ MSG_DEF(JSMSG_ARRAY_COMP_LEFTSIDE, 0, JSEXN_SYNTAXERR, "invalid array compre MSG_DEF(JSMSG_ARRAY_INIT_TOO_BIG, 0, JSEXN_INTERNALERR, "array initializer too large") MSG_DEF(JSMSG_AS_AFTER_IMPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'as' after import *") MSG_DEF(JSMSG_AS_AFTER_RESERVED_WORD, 1, JSEXN_SYNTAXERR, "missing keyword 'as' after reserved word '{0}'") -MSG_DEF(JSMSG_ASYNC_GENERATOR, 0, JSEXN_SYNTAXERR, "generator function or method can't be async") MSG_DEF(JSMSG_AWAIT_IN_DEFAULT, 0, JSEXN_SYNTAXERR, "await can't be used in default expression") MSG_DEF(JSMSG_AWAIT_OUTSIDE_ASYNC, 0, JSEXN_SYNTAXERR, "await is only valid in async functions") MSG_DEF(JSMSG_BAD_ARROW_ARGS, 0, JSEXN_SYNTAXERR, "invalid arrow-function arguments (parentheses around the arrow-function may help)") @@ -438,7 +437,6 @@ MSG_DEF(JSMSG_SC_SAB_DISABLED, 0, JSEXN_TYPEERR, "SharedArrayBuffer not // Debugger MSG_DEF(JSMSG_ASSIGN_FUNCTION_OR_NULL, 1, JSEXN_TYPEERR, "value assigned to {0} must be a function or null") -MSG_DEF(JSMSG_DEBUG_BAD_AWAIT, 0, JSEXN_TYPEERR, "await expression received invalid value") MSG_DEF(JSMSG_DEBUG_BAD_LINE, 0, JSEXN_TYPEERR, "invalid line number") MSG_DEF(JSMSG_DEBUG_BAD_OFFSET, 0, JSEXN_TYPEERR, "invalid script offset") MSG_DEF(JSMSG_DEBUG_BAD_REFERENT, 2, JSEXN_TYPEERR, "{0} does not refer to {1}") @@ -594,3 +592,9 @@ MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "P // Iterator MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of iterator is not callable") MSG_DEF(JSMSG_ITERATOR_NO_THROW, 0, JSEXN_TYPEERR, "iterator does not have a 'throw' method") + +// Async Iteration +MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_TYPEERR, "'for await' loop should be used with 'of'") +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/jsapi.cpp b/js/src/jsapi.cpp index f9a0c6a6b..6483641f3 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -70,6 +70,7 @@ #include "js/StructuredClone.h" #include "js/Utility.h" #include "vm/AsyncFunction.h" +#include "vm/AsyncIteration.h" #include "vm/DateObject.h" #include "vm/Debugger.h" #include "vm/EnvironmentObject.h" @@ -3591,6 +3592,11 @@ CloneFunctionObject(JSContext* cx, HandleObject funobj, HandleObject env, Handle return nullptr; } + if (IsWrappedAsyncGenerator(fun)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CLONE_OBJECT); + return nullptr; + } + if (CanReuseScriptForClone(cx->compartment(), fun, env)) { // If the script is to be reused, either the script can already handle // non-syntactic scopes, or there is only the standard global lexical diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 4dad68e6e..7795c5e4c 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -5088,6 +5088,7 @@ GetSymbolDescription(HandleSymbol symbol); macro(toPrimitive) \ macro(toStringTag) \ macro(unscopables) \ + macro(asyncIterator) \ macro(matchAll) enum class SymbolCode : uint32_t { diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index 3fc9200c1..72f03ea55 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -32,6 +32,7 @@ #include "vm/ErrorObject.h" #include "vm/GlobalObject.h" #include "vm/SavedStacks.h" +#include "vm/SelfHosting.h" #include "vm/StringBuffer.h" #include "jsobjinlines.h" @@ -1091,3 +1092,19 @@ js::ValueToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& byte return "<<error converting value to string>>"; return bytes.encodeLatin1(cx, str); } + +bool +js::GetInternalError(JSContext* cx, unsigned errorNumber, MutableHandleValue error) +{ + FixedInvokeArgs<1> args(cx); + args[0].set(Int32Value(errorNumber)); + return CallSelfHostedFunction(cx, "GetInternalError", NullHandleValue, args, error); +} + +bool +js::GetTypeError(JSContext* cx, unsigned errorNumber, MutableHandleValue error) +{ + FixedInvokeArgs<1> args(cx); + args[0].set(Int32Value(errorNumber)); + return CallSelfHostedFunction(cx, "GetTypeError", NullHandleValue, args, error); +} diff --git a/js/src/jsexn.h b/js/src/jsexn.h index 00120d89c..ee69e3c50 100644 --- a/js/src/jsexn.h +++ b/js/src/jsexn.h @@ -131,6 +131,11 @@ class AutoAssertNoPendingException extern const char* ValueToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& bytes); +bool +GetInternalError(JSContext* cx, unsigned errorNumber, MutableHandleValue error); +bool +GetTypeError(JSContext* cx, unsigned errorNumber, MutableHandleValue error); + } // namespace js #endif /* jsexn_h */ diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 491215456..7b59689ff 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -948,6 +948,7 @@ IsObjectInContextCompartment(JSObject* obj, const JSContext* cx); #define JSITER_HIDDEN 0x10 /* also enumerate non-enumerable properties */ #define JSITER_SYMBOLS 0x20 /* also include symbol property keys */ #define JSITER_SYMBOLSONLY 0x40 /* exclude string property keys */ +#define JSITER_FORAWAITOF 0x80 /* for-await-of */ JS_FRIEND_API(bool) RunningWithTrustedPrincipals(JSContext* cx); diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 9edf238ef..ce1ca33fe 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -41,6 +41,7 @@ #include "js/CallNonGenericMethod.h" #include "js/Proxy.h" #include "vm/AsyncFunction.h" +#include "vm/AsyncIteration.h" #include "vm/Debugger.h" #include "vm/GlobalObject.h" #include "vm/Interpreter.h" @@ -131,7 +132,11 @@ IsFunctionInStrictMode(JSFunction* fun) static bool IsNewerTypeFunction(JSFunction* fun) { - return fun->isArrow() || fun->isGenerator() || fun->isAsync() || fun->isMethod(); + return fun->isArrow() || + fun->isStarGenerator() || + fun->isLegacyGenerator() || + fun->isAsync() || + fun->isMethod(); } // Beware: this function can be invoked on *any* function! That includes @@ -308,6 +313,8 @@ CallerGetterImpl(JSContext* cx, const CallArgs& args) JSFunction* callerFun = &callerObj->as<JSFunction>(); if (IsWrappedAsyncFunction(callerFun)) callerFun = GetUnwrappedAsyncFunction(callerFun); + else if (IsWrappedAsyncGenerator(callerFun)) + callerFun = GetUnwrappedAsyncGenerator(callerFun); MOZ_ASSERT(!callerFun->isBuiltin(), "non-builtin iterator returned a builtin?"); if (callerFun->strict()) { @@ -360,13 +367,15 @@ static const JSPropertySpec function_properties[] = { static bool ResolveInterpretedFunctionPrototype(JSContext* cx, HandleFunction fun, HandleId id) { - MOZ_ASSERT(fun->isInterpreted() || fun->isAsmJSNative()); + bool isAsyncGenerator = IsWrappedAsyncGenerator(fun); + + MOZ_ASSERT_IF(!isAsyncGenerator, fun->isInterpreted() || fun->isAsmJSNative()); MOZ_ASSERT(id == NameToId(cx->names().prototype)); // Assert that fun is not a compiler-created function object, which // must never leak to script or embedding code and then be mutated. // Also assert that fun is not bound, per the ES5 15.3.4.5 ref above. - MOZ_ASSERT(!IsInternalFunctionObject(*fun)); + MOZ_ASSERT_IF(!isAsyncGenerator, !IsInternalFunctionObject(*fun)); MOZ_ASSERT(!fun->isBoundFunction()); // Make the prototype object an instance of Object with the same parent as @@ -376,7 +385,9 @@ ResolveInterpretedFunctionPrototype(JSContext* cx, HandleFunction fun, HandleId bool isStarGenerator = fun->isStarGenerator(); Rooted<GlobalObject*> global(cx, &fun->global()); RootedObject objProto(cx); - if (isStarGenerator) + if (isAsyncGenerator) + objProto = GlobalObject::getOrCreateAsyncGeneratorPrototype(cx, global); + else if (isStarGenerator) objProto = GlobalObject::getOrCreateStarGeneratorObjectPrototype(cx, global); else objProto = GlobalObject::getOrCreateObjectPrototype(cx, global); @@ -392,7 +403,7 @@ ResolveInterpretedFunctionPrototype(JSContext* cx, HandleFunction fun, HandleId // non-enumerable, and writable. However, per the 15 July 2013 ES6 draft, // section 15.19.3, the .prototype of a generator function does not link // back with a .constructor. - if (!isStarGenerator) { + if (!isStarGenerator && !isAsyncGenerator) { RootedValue objVal(cx, ObjectValue(*fun)); if (!DefineProperty(cx, proto, cx->names().constructor, objVal, nullptr, nullptr, 0)) return false; @@ -442,8 +453,14 @@ fun_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) * - Arrow functions * - Function.prototype */ - if (fun->isBuiltin() || (!fun->isConstructor() && !fun->isGenerator())) - return true; + if (!IsWrappedAsyncGenerator(fun)) { + if (fun->isBuiltin()) + return true; + if (!fun->isConstructor()) { + if (!fun->isStarGenerator() && !fun->isLegacyGenerator() && !fun->isAsync()) + return true; + } + } if (!ResolveInterpretedFunctionPrototype(cx, fun, id)) return false; @@ -515,7 +532,7 @@ js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope, { enum FirstWordFlag { HasAtom = 0x1, - IsStarGenerator = 0x2, + HasStarGeneratorProto = 0x2, IsLazy = 0x4, HasSingletonType = 0x8 }; @@ -544,8 +561,8 @@ js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope, if (fun->explicitName() || fun->hasCompileTimeName() || fun->hasGuessedAtom()) firstword |= HasAtom; - if (fun->isStarGenerator()) - firstword |= IsStarGenerator; + if (fun->isStarGenerator() || fun->isAsync()) + firstword |= HasStarGeneratorProto; if (fun->isInterpretedLazy()) { // Encode a lazy script. @@ -581,7 +598,7 @@ js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope, if (mode == XDR_DECODE) { RootedObject proto(cx); - if (firstword & IsStarGenerator) { + if (firstword & HasStarGeneratorProto) { proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global()); if (!proto) return false; @@ -938,6 +955,11 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) return FunctionToString(cx, unwrapped, prettyPrint); } + if (IsWrappedAsyncGenerator(fun)) { + RootedFunction unwrapped(cx, GetUnwrappedAsyncGenerator(fun)); + return FunctionToString(cx, unwrapped, prettyPrint); + } + StringBuffer out(cx); RootedScript script(cx); @@ -1556,7 +1578,7 @@ fun_isGenerator(JSContext* cx, unsigned argc, Value* vp) return true; } - args.rval().setBoolean(fun->isGenerator()); + args.rval().setBoolean(fun->isStarGenerator() || fun->isLegacyGenerator()); return true; } @@ -1588,8 +1610,6 @@ FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generator bool isStarGenerator = generatorKind == StarGenerator; bool isAsync = asyncKind == AsyncFunction; MOZ_ASSERT(generatorKind != LegacyGenerator); - MOZ_ASSERT_IF(isAsync, isStarGenerator); - MOZ_ASSERT_IF(!isStarGenerator, !isAsync); RootedScript maybeScript(cx); const char* filename; @@ -1600,10 +1620,14 @@ FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generator &mutedErrors); const char* introductionType = "Function"; - if (isAsync) - introductionType = "AsyncFunction"; - else if (generatorKind != NotGenerator) + if (isAsync) { + if (isStarGenerator) + introductionType = "AsyncGenerator"; + else + introductionType = "AsyncFunction"; + } else if (generatorKind != NotGenerator) { introductionType = "GeneratorFunction"; + } const char* introducerFilename = filename; if (maybeScript && maybeScript->scriptSource()->introducerFilename()) @@ -1701,7 +1725,7 @@ FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generator // Step 4.d, use %Generator% as the fallback prototype. // Also use %Generator% for the unwrapped function of async functions. - if (!proto && isStarGenerator) { + if (!proto && (isStarGenerator || isAsync)) { proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, global); if (!proto) return false; @@ -1731,12 +1755,20 @@ FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generator : SourceBufferHolder::NoOwnership; bool ok; SourceBufferHolder srcBuf(chars.begin().get(), chars.length(), ownership); - if (isAsync) - ok = frontend::CompileStandaloneAsyncFunction(cx, &fun, options, srcBuf, parameterListEnd); - else if (isStarGenerator) - ok = frontend::CompileStandaloneGenerator(cx, &fun, options, srcBuf, parameterListEnd); - else - ok = frontend::CompileStandaloneFunction(cx, &fun, options, srcBuf, parameterListEnd); + if (isAsync) { + if (isStarGenerator) { + ok = frontend::CompileStandaloneAsyncGenerator(cx, &fun, options, srcBuf, + parameterListEnd); + } else { + ok = frontend::CompileStandaloneAsyncFunction(cx, &fun, options, srcBuf, + parameterListEnd); + } + } else { + if (isStarGenerator) + ok = frontend::CompileStandaloneGenerator(cx, &fun, options, srcBuf, parameterListEnd); + else + ok = frontend::CompileStandaloneFunction(cx, &fun, options, srcBuf, parameterListEnd); + } // Step 33. args.rval().setObject(*fun); @@ -1762,14 +1794,14 @@ js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - // Save the callee before its reset in FunctionConstructor(). + // Save the callee before it's reset in FunctionConstructor(). RootedObject newTarget(cx); if (args.isConstructing()) newTarget = &args.newTarget().toObject(); else newTarget = &args.callee(); - if (!FunctionConstructor(cx, args, StarGenerator, AsyncFunction)) + if (!FunctionConstructor(cx, args, NotGenerator, AsyncFunction)) return false; // ES2017, draft rev 0f10dba4ad18de92d47d421f378233a2eae8f077 @@ -1795,6 +1827,40 @@ js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp) } bool +js::AsyncGeneratorConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Save the callee before its reset in FunctionConstructor(). + RootedObject newTarget(cx); + if (args.isConstructing()) + newTarget = &args.newTarget().toObject(); + else + newTarget = &args.callee(); + + if (!FunctionConstructor(cx, args, StarGenerator, AsyncFunction)) + return false; + + RootedObject proto(cx); + if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) + return false; + + if (!proto) { + proto = GlobalObject::getOrCreateAsyncGenerator(cx, cx->global()); + if (!proto) + return false; + } + + RootedFunction unwrapped(cx, &args.rval().toObject().as<JSFunction>()); + RootedObject wrapped(cx, WrapAsyncGeneratorWithProto(cx, unwrapped, proto)); + if (!wrapped) + return false; + + args.rval().setObject(*wrapped); + return true; +} + +bool JSFunction::isBuiltinFunctionConstructor() { return maybeNative() == Function || maybeNative() == Generator; @@ -1965,7 +2031,7 @@ NewFunctionClone(JSContext* cx, HandleFunction fun, NewObjectKind newKind, gc::AllocKind allocKind, HandleObject proto) { RootedObject cloneProto(cx, proto); - if (!proto && fun->isStarGenerator()) { + if (!proto && (fun->isStarGenerator() || fun->isAsync())) { cloneProto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global()); if (!cloneProto) return nullptr; diff --git a/js/src/jsfun.h b/js/src/jsfun.h index 234169507..ea9e7af9b 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -89,15 +89,15 @@ class JSFunction : public js::NativeObject ASMJS_CTOR = ASMJS_KIND | NATIVE_CTOR, ASMJS_LAMBDA_CTOR = ASMJS_KIND | NATIVE_CTOR | LAMBDA, INTERPRETED_METHOD = INTERPRETED | METHOD_KIND, - INTERPRETED_METHOD_GENERATOR = INTERPRETED | METHOD_KIND, + INTERPRETED_METHOD_GENERATOR_OR_ASYNC = INTERPRETED | METHOD_KIND, INTERPRETED_CLASS_CONSTRUCTOR = INTERPRETED | CLASSCONSTRUCTOR_KIND | CONSTRUCTOR, INTERPRETED_GETTER = INTERPRETED | GETTER_KIND, INTERPRETED_SETTER = INTERPRETED | SETTER_KIND, INTERPRETED_LAMBDA = INTERPRETED | LAMBDA | CONSTRUCTOR, INTERPRETED_LAMBDA_ARROW = INTERPRETED | LAMBDA | ARROW_KIND, - INTERPRETED_LAMBDA_GENERATOR = INTERPRETED | LAMBDA, + INTERPRETED_LAMBDA_GENERATOR_OR_ASYNC = INTERPRETED | LAMBDA, INTERPRETED_NORMAL = INTERPRETED | CONSTRUCTOR, - INTERPRETED_GENERATOR = INTERPRETED, + INTERPRETED_GENERATOR_OR_ASYNC = INTERPRETED, NO_XDR_FLAGS = RESOLVED_LENGTH | RESOLVED_NAME, STABLE_ACROSS_CLONES = CONSTRUCTOR | HAS_GUESSED_ATOM | LAMBDA | @@ -146,7 +146,9 @@ class JSFunction : public js::NativeObject MOZ_ASSERT_IF(nonLazyScript()->funHasExtensibleScope() || nonLazyScript()->needsHomeObject() || nonLazyScript()->isDerivedClassConstructor() || - isGenerator(), + isStarGenerator() || + isLegacyGenerator() || + isAsync(), nonLazyScript()->bodyScope()->hasEnvironment()); return nonLazyScript()->bodyScope()->hasEnvironment(); @@ -501,8 +503,6 @@ class JSFunction : public js::NativeObject return js::NotGenerator; } - bool isGenerator() const { return generatorKind() != js::NotGenerator; } - bool isLegacyGenerator() const { return generatorKind() == js::LegacyGenerator; } bool isStarGenerator() const { return generatorKind() == js::StarGenerator; } @@ -513,9 +513,9 @@ class JSFunction : public js::NativeObject bool isAsync() const { if (isInterpretedLazy()) - return lazyScript()->asyncKind() == js::AsyncFunction; + return lazyScript()->isAsync(); if (hasScript()) - return nonLazyScript()->asyncKind() == js::AsyncFunction; + return nonLazyScript()->isAsync(); return false; } @@ -660,6 +660,9 @@ Generator(JSContext* cx, unsigned argc, Value* vp); extern bool AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp); +extern bool +AsyncGeneratorConstructor(JSContext* cx, unsigned argc, Value* vp); + // Allocate a new function backed by a JSNative. Note that by default this // creates a singleton object. extern JSFunction* diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index c4da86bdb..c58f32382 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -916,28 +916,30 @@ js::GetIteratorObject(JSContext* cx, HandleObject obj, uint32_t flags) return iterator; } +// ES 2017 draft 7.4.7. JSObject* -js::CreateItrResultObject(JSContext* cx, HandleValue value, bool done) +js::CreateIterResultObject(JSContext* cx, HandleValue value, bool done) { - // FIXME: We can cache the iterator result object shape somewhere. - AssertHeapIsIdle(cx); + // Step 1 (implicit). - RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, cx->global())); - if (!proto) + // Step 2. + RootedObject resultObj(cx, NewBuiltinClassInstance<PlainObject>(cx)); + if (!resultObj) return nullptr; - RootedPlainObject obj(cx, NewObjectWithGivenProto<PlainObject>(cx, proto)); - if (!obj) - return nullptr; - - if (!DefineProperty(cx, obj, cx->names().value, value)) + // Step 3. + if (!DefineProperty(cx, resultObj, cx->names().value, value)) return nullptr; - RootedValue doneBool(cx, BooleanValue(done)); - if (!DefineProperty(cx, obj, cx->names().done, doneBool)) + // Step 4. + if (!DefineProperty(cx, resultObj, cx->names().done, + done ? TrueHandleValue : FalseHandleValue)) + { return nullptr; + } - return obj; + // Step 5. + return resultObj; } bool diff --git a/js/src/jsiter.h b/js/src/jsiter.h index 52eb045c5..0b324a014 100644 --- a/js/src/jsiter.h +++ b/js/src/jsiter.h @@ -216,10 +216,10 @@ ThrowStopIteration(JSContext* cx); /* * Create an object of the form { value: VALUE, done: DONE }. - * ES6 draft from 2013-09-05, section 25.4.3.4. + * ES 2017 draft 7.4.7. */ extern JSObject* -CreateItrResultObject(JSContext* cx, HandleValue value, bool done); +CreateIterResultObject(JSContext* cx, HandleValue value, bool done); extern JSObject* InitLegacyIteratorClass(JSContext* cx, HandleObject obj); @@ -227,6 +227,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/jsopcode.h b/js/src/jsopcode.h index e56eebb2d..e417e3a64 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -655,7 +655,8 @@ IsValidBytecodeOffset(JSContext* cx, JSScript* script, size_t offset); inline bool FlowsIntoNext(JSOp op) { - /* JSOP_YIELD is considered to flow into the next instruction, like JSOP_CALL. */ + // JSOP_YIELD/JSOP_AWAIT is considered to flow into the next instruction, + // like JSOP_CALL. switch (op) { case JSOP_RETRVAL: case JSOP_RETURN: diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index fc7438e3b..bdd411d04 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -402,8 +402,8 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip ntrynotes = script->trynotes()->length; if (script->hasScopeNotes()) nscopenotes = script->scopeNotes()->length; - if (script->hasYieldOffsets()) - nyieldoffsets = script->yieldOffsets().length(); + if (script->hasYieldAndAwaitOffsets()) + nyieldoffsets = script->yieldAndAwaitOffsets().length(); nTypeSets = script->nTypeSets(); funLength = script->funLength(); @@ -902,7 +902,7 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip } for (i = 0; i < nyieldoffsets; ++i) { - uint32_t* offset = &script->yieldOffsets()[i]; + uint32_t* offset = &script->yieldAndAwaitOffsets()[i]; if (!xdr->codeUint32(offset)) return false; } @@ -2456,7 +2456,7 @@ ScriptDataSize(uint32_t nscopes, uint32_t nconsts, uint32_t nobjects, if (nscopenotes != 0) size += sizeof(ScopeNoteArray) + nscopenotes * sizeof(ScopeNote); if (nyieldoffsets != 0) - size += sizeof(YieldOffsetArray) + nyieldoffsets * sizeof(uint32_t); + size += sizeof(YieldAndAwaitOffsetArray) + nyieldoffsets * sizeof(uint32_t); return size; } @@ -2558,10 +2558,10 @@ JSScript::partiallyInit(ExclusiveContext* cx, HandleScript script, uint32_t nsco cursor += sizeof(ScopeNoteArray); } - YieldOffsetArray* yieldOffsets = nullptr; + YieldAndAwaitOffsetArray* yieldAndAwaitOffsets = nullptr; if (nyieldoffsets != 0) { - yieldOffsets = reinterpret_cast<YieldOffsetArray*>(cursor); - cursor += sizeof(YieldOffsetArray); + yieldAndAwaitOffsets = reinterpret_cast<YieldAndAwaitOffsetArray*>(cursor); + cursor += sizeof(YieldAndAwaitOffsetArray); } if (nconsts != 0) { @@ -2602,8 +2602,8 @@ JSScript::partiallyInit(ExclusiveContext* cx, HandleScript script, uint32_t nsco } if (nyieldoffsets != 0) { - yieldOffsets->init(reinterpret_cast<uint32_t*>(cursor), nyieldoffsets); - size_t vectorSize = nyieldoffsets * sizeof(script->yieldOffsets()[0]); + yieldAndAwaitOffsets->init(reinterpret_cast<uint32_t*>(cursor), nyieldoffsets); + size_t vectorSize = nyieldoffsets * sizeof(script->yieldAndAwaitOffsets()[0]); #ifdef DEBUG memset(cursor, 0, vectorSize); #endif @@ -2623,10 +2623,10 @@ JSScript::initFunctionPrototype(ExclusiveContext* cx, Handle<JSScript*> script, uint32_t numObjects = 0; uint32_t numTryNotes = 0; uint32_t numScopeNotes = 0; - uint32_t numYieldOffsets = 0; + uint32_t numYieldAndAwaitOffsets = 0; uint32_t numTypeSets = 0; if (!partiallyInit(cx, script, numScopes, numConsts, numObjects, numTryNotes, - numScopeNotes, numYieldOffsets, numTypeSets)) + numScopeNotes, numYieldAndAwaitOffsets, numTypeSets)) { return false; } @@ -2739,7 +2739,7 @@ JSScript::fullyInitFromEmitter(ExclusiveContext* cx, HandleScript script, Byteco if (!partiallyInit(cx, script, bce->scopeList.length(), bce->constList.length(), bce->objectList.length, bce->tryNoteList.length(), bce->scopeNoteList.length(), - bce->yieldOffsetList.length(), bce->typesetCount)) + bce->yieldAndAwaitOffsetList.length(), bce->typesetCount)) { return false; } @@ -2794,8 +2794,8 @@ JSScript::fullyInitFromEmitter(ExclusiveContext* cx, HandleScript script, Byteco // Copy yield offsets last, as the generator kind is set in // initFromFunctionBox. - if (bce->yieldOffsetList.length() != 0) - bce->yieldOffsetList.finish(script->yieldOffsets(), prologueLength); + if (bce->yieldAndAwaitOffsetList.length() != 0) + bce->yieldAndAwaitOffsetList.finish(script->yieldAndAwaitOffsets(), prologueLength); #ifdef DEBUG script->assertValidJumpTargets(); @@ -3188,7 +3188,7 @@ CloneInnerInterpretedFunction(JSContext* cx, HandleScope enclosingScope, HandleF { /* NB: Keep this in sync with XDRInterpretedFunction. */ RootedObject cloneProto(cx); - if (srcFun->isStarGenerator()) { + if (srcFun->isStarGenerator() || srcFun->isAsync()) { cloneProto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global()); if (!cloneProto) return nullptr; @@ -3242,7 +3242,7 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst, uint32_t nscopes = src->scopes()->length; uint32_t ntrynotes = src->hasTrynotes() ? src->trynotes()->length : 0; uint32_t nscopenotes = src->hasScopeNotes() ? src->scopeNotes()->length : 0; - uint32_t nyieldoffsets = src->hasYieldOffsets() ? src->yieldOffsets().length() : 0; + uint32_t nyieldoffsets = src->hasYieldAndAwaitOffsets() ? src->yieldAndAwaitOffsets().length() : 0; /* Script data */ @@ -3380,8 +3380,10 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst, dst->trynotes()->vector = Rebase<JSTryNote>(dst, src, src->trynotes()->vector); if (nscopenotes != 0) dst->scopeNotes()->vector = Rebase<ScopeNote>(dst, src, src->scopeNotes()->vector); - if (nyieldoffsets != 0) - dst->yieldOffsets().vector_ = Rebase<uint32_t>(dst, src, src->yieldOffsets().vector_); + if (nyieldoffsets != 0) { + dst->yieldAndAwaitOffsets().vector_ = + Rebase<uint32_t>(dst, src, src->yieldAndAwaitOffsets().vector_); + } /* * Function delazification assumes that their script does not have a @@ -3917,7 +3919,9 @@ JSScript::argumentsOptimizationFailed(JSContext* cx, HandleScript script) if (script->needsArgsObj()) return true; - MOZ_ASSERT(!script->isGenerator()); + MOZ_ASSERT(!script->isStarGenerator()); + MOZ_ASSERT(!script->isLegacyGenerator()); + MOZ_ASSERT(!script->isAsync()); script->needsArgsObj_ = true; diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 85eb2938d..c19fbfc71 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -156,7 +156,7 @@ struct ScopeNoteArray { uint32_t length; // Count of indexed try notes. }; -class YieldOffsetArray { +class YieldAndAwaitOffsetArray { friend bool detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst, MutableHandle<GCVector<Scope*>> scopes); @@ -1327,13 +1327,12 @@ class JSScript : public js::gc::TenuredCell js::GeneratorKind generatorKind() const { return js::GeneratorKindFromBits(generatorKindBits_); } - bool isGenerator() const { return generatorKind() != js::NotGenerator; } bool isLegacyGenerator() const { return generatorKind() == js::LegacyGenerator; } bool isStarGenerator() const { return generatorKind() == js::StarGenerator; } void setGeneratorKind(js::GeneratorKind kind) { // A script only gets its generator kind set as part of initialization, // so it can only transition from not being a generator. - MOZ_ASSERT(!isGenerator()); + MOZ_ASSERT(!isStarGenerator() && !isLegacyGenerator()); generatorKindBits_ = GeneratorKindAsBits(kind); } @@ -1341,6 +1340,10 @@ class JSScript : public js::gc::TenuredCell return isAsync_ ? js::AsyncFunction : js::SyncFunction; } + bool isAsync() const { + return isAsync_; + } + void setAsyncKind(js::FunctionAsyncKind kind) { isAsync_ = kind == js::AsyncFunction; } @@ -1493,7 +1496,8 @@ class JSScript : public js::gc::TenuredCell bool isRelazifiable() const { return (selfHosted() || lazyScript) && !hasInnerFunctions_ && !types_ && - !isGenerator() && !hasBaselineScript() && !hasAnyIonScript() && + !isStarGenerator() && !isLegacyGenerator() && !isAsync() && + !hasBaselineScript() && !hasAnyIonScript() && !isDefaultClassConstructor() && !doNotRelazify_; } @@ -1695,7 +1699,9 @@ class JSScript : public js::gc::TenuredCell bool hasObjects() const { return hasArray(OBJECTS); } bool hasTrynotes() const { return hasArray(TRYNOTES); } bool hasScopeNotes() const { return hasArray(SCOPENOTES); } - bool hasYieldOffsets() const { return isGenerator(); } + bool hasYieldAndAwaitOffsets() const { + return isStarGenerator() || isLegacyGenerator() || isAsync(); + } #define OFF(fooOff, hasFoo, t) (fooOff() + (hasFoo() ? sizeof(t) : 0)) @@ -1704,7 +1710,9 @@ class JSScript : public js::gc::TenuredCell size_t objectsOffset() const { return OFF(constsOffset, hasConsts, js::ConstArray); } size_t trynotesOffset() const { return OFF(objectsOffset, hasObjects, js::ObjectArray); } size_t scopeNotesOffset() const { return OFF(trynotesOffset, hasTrynotes, js::TryNoteArray); } - size_t yieldOffsetsOffset() const { return OFF(scopeNotesOffset, hasScopeNotes, js::ScopeNoteArray); } + size_t yieldAndAwaitOffsetsOffset() const { + return OFF(scopeNotesOffset, hasScopeNotes, js::ScopeNoteArray); + } #undef OFF @@ -1734,9 +1742,10 @@ class JSScript : public js::gc::TenuredCell return reinterpret_cast<js::ScopeNoteArray*>(data + scopeNotesOffset()); } - js::YieldOffsetArray& yieldOffsets() { - MOZ_ASSERT(hasYieldOffsets()); - return *reinterpret_cast<js::YieldOffsetArray*>(data + yieldOffsetsOffset()); + js::YieldAndAwaitOffsetArray& yieldAndAwaitOffsets() { + MOZ_ASSERT(hasYieldAndAwaitOffsets()); + return *reinterpret_cast<js::YieldAndAwaitOffsetArray*>(data + + yieldAndAwaitOffsetsOffset()); } bool hasLoops(); @@ -2112,8 +2121,6 @@ class LazyScript : public gc::TenuredCell GeneratorKind generatorKind() const { return GeneratorKindFromBits(p_.generatorKindBits); } - bool isGenerator() const { return generatorKind() != NotGenerator; } - bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; } bool isStarGenerator() const { return generatorKind() == StarGenerator; } @@ -2121,7 +2128,7 @@ class LazyScript : public gc::TenuredCell void setGeneratorKind(GeneratorKind kind) { // A script only gets its generator kind set as part of initialization, // so it can only transition from NotGenerator. - MOZ_ASSERT(!isGenerator()); + MOZ_ASSERT(!isStarGenerator() && !isLegacyGenerator()); // Legacy generators cannot currently be lazy. MOZ_ASSERT(kind != LegacyGenerator); p_.generatorKindBits = GeneratorKindAsBits(kind); @@ -2131,6 +2138,10 @@ class LazyScript : public gc::TenuredCell return p_.isAsync ? AsyncFunction : SyncFunction; } + bool isAsync() const { + return p_.isAsync; + } + void setAsyncKind(FunctionAsyncKind kind) { p_.isAsync = kind == AsyncFunction; } diff --git a/js/src/moz.build b/js/src/moz.build index d1e80b4ce..d6c2a426b 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -305,6 +305,7 @@ UNIFIED_SOURCES += [ 'vm/ArgumentsObject.cpp', 'vm/ArrayBufferObject.cpp', 'vm/AsyncFunction.cpp', + 'vm/AsyncIteration.cpp', 'vm/Caches.cpp', 'vm/CallNonGenericMethod.cpp', 'vm/CharacterEncoding.cpp', @@ -747,6 +748,7 @@ selfhosted.inputs = [ 'builtin/SelfHostingDefines.h', 'builtin/Utilities.js', 'builtin/Array.js', + 'builtin/AsyncIteration.js', 'builtin/Classes.js', 'builtin/Date.js', 'builtin/Error.js', @@ -790,3 +792,6 @@ if CONFIG['GNU_CXX']: # Suppress warnings in third-party code. if CONFIG['CLANG_CXX'] or CONFIG['GNU_CXX']: SOURCES['jsdtoa.cpp'].flags += ['-Wno-implicit-fallthrough'] + +if CONFIG['OS_ARCH'] == 'WINNT': + DEFINES['NOMINMAX'] = True diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 51cd11fe8..33f7d6bdc 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -85,6 +85,7 @@ #include "threading/Thread.h" #include "vm/ArgumentsObject.h" #include "vm/AsyncFunction.h" +#include "vm/AsyncIteration.h" #include "vm/Compression.h" #include "vm/Debugger.h" #include "vm/HelperThreads.h" @@ -2304,6 +2305,8 @@ ValueToScript(JSContext* cx, HandleValue v, JSFunction** funp = nullptr) // Get unwrapped async function. if (IsWrappedAsyncFunction(fun)) fun = GetUnwrappedAsyncFunction(fun); + if (IsWrappedAsyncGenerator(fun)) + fun = GetUnwrappedAsyncGenerator(fun); if (!fun->isInterpreted()) { JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_SCRIPTS_ONLY); diff --git a/js/src/tests/ecma_2017/AsyncFunctions/properties.js b/js/src/tests/ecma_2017/AsyncFunctions/properties.js new file mode 100644 index 000000000..ca383901b --- /dev/null +++ b/js/src/tests/ecma_2017/AsyncFunctions/properties.js @@ -0,0 +1,76 @@ +/* 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/. */ + +function assertOwnDescriptor(object, propertyKey, expected) { + var desc = Object.getOwnPropertyDescriptor(object, propertyKey); + if (desc === undefined) { + assertEq(expected, undefined, "Property shouldn't be present"); + return; + } + + assertEq(desc.enumerable, expected.enumerable, `${String(propertyKey)}.[[Enumerable]]`); + assertEq(desc.configurable, expected.configurable, `${String(propertyKey)}.[[Configurable]]`); + + if (Object.prototype.hasOwnProperty.call(desc, "value")) { + assertEq(desc.value, expected.value, `${String(propertyKey)}.[[Value]]`); + assertEq(desc.writable, expected.writable, `${String(propertyKey)}.[[Writable]]`); + } else { + assertEq(desc.get, expected.get, `${String(propertyKey)}.[[Get]]`); + assertEq(desc.set, expected.set, `${String(propertyKey)}.[[Set]]`); + } +} + +async function asyncFunc(){} +var AsyncFunctionPrototype = Object.getPrototypeOf(asyncFunc); +var AsyncFunction = AsyncFunctionPrototype.constructor; + + +// ES2017, 25.5.2 Properties of the AsyncFunction Constructor + +assertEqArray(Object.getOwnPropertyNames(AsyncFunction).sort(), ["length", "name", "prototype"]); +assertEqArray(Object.getOwnPropertySymbols(AsyncFunction), []); + +assertOwnDescriptor(AsyncFunction, "length", { + value: 1, writable: false, enumerable: false, configurable: true +}); + +assertOwnDescriptor(AsyncFunction, "name", { + value: "AsyncFunction", writable: false, enumerable: false, configurable: true +}); + +assertOwnDescriptor(AsyncFunction, "prototype", { + value: AsyncFunctionPrototype, writable: false, enumerable: false, configurable: false +}); + + +// ES2017, 25.5.3 Properties of the AsyncFunction Prototype Object + +assertEqArray(Object.getOwnPropertyNames(AsyncFunctionPrototype).sort(), ["constructor"]); +assertEqArray(Object.getOwnPropertySymbols(AsyncFunctionPrototype), [Symbol.toStringTag]); + +assertOwnDescriptor(AsyncFunctionPrototype, "constructor", { + value: AsyncFunction, writable: false, enumerable: false, configurable: true +}); + +assertOwnDescriptor(AsyncFunctionPrototype, Symbol.toStringTag, { + value: "AsyncFunction", writable: false, enumerable: false, configurable: true +}); + + +// ES2017, 25.5.4 AsyncFunction Instances + +assertEqArray(Object.getOwnPropertyNames(asyncFunc).sort(), ["length", "name"]); +assertEqArray(Object.getOwnPropertySymbols(asyncFunc), []); + +assertOwnDescriptor(asyncFunc, "length", { + value: 0, writable: false, enumerable: false, configurable: true +}); + +assertOwnDescriptor(asyncFunc, "name", { + value: "asyncFunc", writable: false, enumerable: false, configurable: true +}); + + +if (typeof reportCompare == "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/Generators/properties.js b/js/src/tests/ecma_6/Generators/properties.js new file mode 100644 index 000000000..7782e64c0 --- /dev/null +++ b/js/src/tests/ecma_6/Generators/properties.js @@ -0,0 +1,111 @@ +/* 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/. */ + +function assertOwnDescriptor(object, propertyKey, expected) { + var desc = Object.getOwnPropertyDescriptor(object, propertyKey); + if (desc === undefined) { + assertEq(expected, undefined, "Property shouldn't be present"); + return; + } + + assertEq(desc.enumerable, expected.enumerable, `${String(propertyKey)}.[[Enumerable]]`); + assertEq(desc.configurable, expected.configurable, `${String(propertyKey)}.[[Configurable]]`); + + if (Object.prototype.hasOwnProperty.call(desc, "value")) { + assertEq(desc.value, expected.value, `${String(propertyKey)}.[[Value]]`); + assertEq(desc.writable, expected.writable, `${String(propertyKey)}.[[Writable]]`); + } else { + assertEq(desc.get, expected.get, `${String(propertyKey)}.[[Get]]`); + assertEq(desc.set, expected.set, `${String(propertyKey)}.[[Set]]`); + } +} + +function* generator(){} +var GeneratorFunctionPrototype = Object.getPrototypeOf(generator); +var GeneratorFunction = GeneratorFunctionPrototype.constructor; +var GeneratorPrototype = GeneratorFunctionPrototype.prototype; + + +// ES2017, 25.2.2 Properties of the GeneratorFunction Constructor + +assertEqArray(Object.getOwnPropertyNames(GeneratorFunction).sort(), ["length", "name", "prototype"]); +assertEqArray(Object.getOwnPropertySymbols(GeneratorFunction), []); + +assertOwnDescriptor(GeneratorFunction, "length", { + value: 1, writable: false, enumerable: false, configurable: true +}); + +assertOwnDescriptor(GeneratorFunction, "name", { + value: "GeneratorFunction", writable: false, enumerable: false, configurable: true +}); + +assertOwnDescriptor(GeneratorFunction, "prototype", { + value: GeneratorFunctionPrototype, writable: false, enumerable: false, configurable: false +}); + + +// ES2017, 25.2.3 Properties of the GeneratorFunction Prototype Object + +assertEqArray(Object.getOwnPropertyNames(GeneratorFunctionPrototype).sort(), ["constructor", "prototype"]); +assertEqArray(Object.getOwnPropertySymbols(GeneratorFunctionPrototype), [Symbol.toStringTag]); + +assertOwnDescriptor(GeneratorFunctionPrototype, "constructor", { + value: GeneratorFunction, writable: false, enumerable: false, configurable: true +}); + +assertOwnDescriptor(GeneratorFunctionPrototype, "prototype", { + value: GeneratorPrototype, writable: false, enumerable: false, configurable: true +}); + +assertOwnDescriptor(GeneratorFunctionPrototype, Symbol.toStringTag, { + value: "GeneratorFunction", writable: false, enumerable: false, configurable: true +}); + + +// ES2017, 25.2.4 GeneratorFunction Instances + +assertEqArray(Object.getOwnPropertyNames(generator).sort(), ["length", "name", "prototype"]); +assertEqArray(Object.getOwnPropertySymbols(generator), []); + +assertOwnDescriptor(generator, "length", { + value: 0, writable: false, enumerable: false, configurable: true +}); + +assertOwnDescriptor(generator, "name", { + value: "generator", writable: false, enumerable: false, configurable: true +}); + +assertOwnDescriptor(generator, "prototype", { + value: generator.prototype, writable: true, enumerable: false, configurable: false +}); + + +// ES2017, 25.3.1 Properties of Generator Prototype + +assertEqArray(Object.getOwnPropertyNames(GeneratorPrototype).sort(), ["constructor", "next", "return", "throw"]); +assertEqArray(Object.getOwnPropertySymbols(GeneratorPrototype), [Symbol.toStringTag]); + +assertOwnDescriptor(GeneratorPrototype, "constructor", { + value: GeneratorFunctionPrototype, writable: false, enumerable: false, configurable: true +}); + +assertOwnDescriptor(GeneratorPrototype, "next", { + value: GeneratorPrototype.next, writable: true, enumerable: false, configurable: true +}); + +assertOwnDescriptor(GeneratorPrototype, "return", { + value: GeneratorPrototype.return, writable: true, enumerable: false, configurable: true +}); + +assertOwnDescriptor(GeneratorPrototype, "throw", { + value: GeneratorPrototype.throw, writable: true, enumerable: false, configurable: true +}); + +assertOwnDescriptor(GeneratorPrototype, Symbol.toStringTag, { + value: "Generator", writable: false, enumerable: false, configurable: true +}); + + +if (typeof reportCompare == "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/Promise/self-resolve.js b/js/src/tests/ecma_6/Promise/self-resolve.js index e16a2ceb3..4e7e36c6c 100644 --- a/js/src/tests/ecma_6/Promise/self-resolve.js +++ b/js/src/tests/ecma_6/Promise/self-resolve.js @@ -5,6 +5,7 @@ if (!this.Promise) { quit(0); } +// Resolve Promise with itself by directly calling the "Promise Resolve Function". let resolve; let promise = new Promise(function(x) { resolve = x; }); resolve(promise) @@ -20,4 +21,23 @@ drainJobQueue() assertEq(results.length, 1); assertEq(results[0], "rejected"); +// Resolve Promise with itself when the "Promise Resolve Function" is called +// from (the fast path in) PromiseReactionJob. +results = []; + +promise = new Promise(x => { resolve = x; }); +let promise2 = promise.then(() => promise2); + +promise2.then(() => assertEq(true, false, "not reached"), res => { + assertEq(res instanceof TypeError, true); + results.push("rejected"); +}); + +resolve(); + +drainJobQueue(); + +assertEq(results.length, 1); +assertEq(results[0], "rejected"); + this.reportCompare && reportCompare(0, 0, "ok"); diff --git a/js/src/tests/ecma_6/Symbol/well-known.js b/js/src/tests/ecma_6/Symbol/well-known.js index 8c5de1279..095d333a0 100644 --- a/js/src/tests/ecma_6/Symbol/well-known.js +++ b/js/src/tests/ecma_6/Symbol/well-known.js @@ -11,7 +11,8 @@ var names = [ "hasInstance", "split", "toPrimitive", - "unscopables" + "unscopables", + "asyncIterator" ]; for (var name of names) { diff --git a/js/src/tests/ecma_7/AsyncFunctions/syntax.js b/js/src/tests/ecma_7/AsyncFunctions/syntax.js index 28e5febc1..0b4b50af4 100644 --- a/js/src/tests/ecma_7/AsyncFunctions/syntax.js +++ b/js/src/tests/ecma_7/AsyncFunctions/syntax.js @@ -9,9 +9,6 @@ if (typeof Reflect !== "undefined" && Reflect.parse) { assertEq(Reflect.parse("async function a() {}").body[0].async, true); assertEq(Reflect.parse("() => {}").body[0].async, undefined); - // Async generators are not allowed (with regards to spec) - assertThrows(() => Reflect.parse("async function* a() {}"), SyntaxError); - // No line terminator after async assertEq(Reflect.parse("async\nfunction a(){}").body[0].expression.name, "async"); diff --git a/js/src/tests/js1_8_5/reflect-parse/PatternBuilders.js b/js/src/tests/js1_8_5/reflect-parse/PatternBuilders.js index be65bd76e..51b7c6926 100644 --- a/js/src/tests/js1_8_5/reflect-parse/PatternBuilders.js +++ b/js/src/tests/js1_8_5/reflect-parse/PatternBuilders.js @@ -53,9 +53,8 @@ function asyncFunDecl(id, params, body) { params: params, defaults: [], body: body, - generator: true, - async: true, - style: "es6" }); + generator: false, + async: true }); } function varDecl(decls) { return Pattern({ type: "VariableDeclaration", declarations: decls, kind: "var" }); @@ -181,9 +180,8 @@ function asyncFunExpr(id, args, body) { id: id, params: args, body: body, - generator: true, - async: true, - style: "es6" }); + generator: false, + async: true }); } function arrowExpr(args, body) { return Pattern({ type: "ArrowFunctionExpression", @@ -194,10 +192,9 @@ function asyncArrowExpr(isExpression, args, body) { return Pattern({ type: "ArrowFunctionExpression", params: args, body: body, - generator: true, + generator: false, async: true, - expression: isExpression, - style: "es6" }); + expression: isExpression }); } function metaProperty(meta, property) { diff --git a/js/src/vm/AsyncFunction.cpp b/js/src/vm/AsyncFunction.cpp index e14b77424..7132ed984 100644 --- a/js/src/vm/AsyncFunction.cpp +++ b/js/src/vm/AsyncFunction.cpp @@ -40,8 +40,11 @@ GlobalObject::initAsyncFunction(JSContext* cx, Handle<GlobalObject*> global) proto)); if (!asyncFunction) return false; - if (!LinkConstructorAndPrototype(cx, asyncFunction, asyncFunctionProto)) + if (!LinkConstructorAndPrototype(cx, asyncFunction, asyncFunctionProto, + JSPROP_PERMANENT | JSPROP_READONLY, JSPROP_READONLY)) + { return false; + } global->setReservedSlot(ASYNC_FUNCTION, ObjectValue(*asyncFunction)); global->setReservedSlot(ASYNC_FUNCTION_PROTO, ObjectValue(*asyncFunctionProto)); @@ -109,7 +112,7 @@ WrappedAsyncFunction(JSContext* cx, unsigned argc, Value* vp) JSObject* js::WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto) { - MOZ_ASSERT(unwrapped->isStarGenerator()); + MOZ_ASSERT(unwrapped->isAsync()); MOZ_ASSERT(proto, "We need an explicit prototype to avoid the default" "%FunctionPrototype% fallback in NewFunctionWithProto()."); @@ -171,22 +174,14 @@ AsyncFunctionResume(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleV : cx->names().StarGeneratorThrow; FixedInvokeArgs<1> args(cx); args[0].set(valueOrReason); - RootedValue result(cx); - if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &result)) - return AsyncFunctionThrown(cx, resultPromise); - - RootedObject resultObj(cx, &result.toObject()); - RootedValue doneVal(cx); RootedValue value(cx); - if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal)) - return false; - if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value)) - return false; + if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &value)) + return AsyncFunctionThrown(cx, resultPromise); - if (doneVal.toBoolean()) - return AsyncFunctionReturned(cx, resultPromise, value); + if (generatorVal.toObject().as<GeneratorObject>().isAfterAwait()) + return AsyncFunctionAwait(cx, resultPromise, value); - return AsyncFunctionAwait(cx, resultPromise, value); + return AsyncFunctionReturned(cx, resultPromise, value); } // Async Functions proposal 2.2 steps 3-8. @@ -242,9 +237,3 @@ js::IsWrappedAsyncFunction(JSFunction* fun) { return fun->maybeNative() == WrappedAsyncFunction; } - -MOZ_MUST_USE bool -js::CheckAsyncResumptionValue(JSContext* cx, HandleValue v) -{ - return CheckStarGeneratorResumptionValue(cx, v); -} diff --git a/js/src/vm/AsyncFunction.h b/js/src/vm/AsyncFunction.h index d7f2c1311..de7c87d13 100644 --- a/js/src/vm/AsyncFunction.h +++ b/js/src/vm/AsyncFunction.h @@ -35,9 +35,6 @@ MOZ_MUST_USE bool AsyncFunctionAwaitedRejected(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue generatorVal, HandleValue reason); -MOZ_MUST_USE bool -CheckAsyncResumptionValue(JSContext* cx, HandleValue v); - } // namespace js #endif /* vm_AsyncFunction_h */ diff --git a/js/src/vm/AsyncIteration.cpp b/js/src/vm/AsyncIteration.cpp new file mode 100644 index 000000000..300179374 --- /dev/null +++ b/js/src/vm/AsyncIteration.cpp @@ -0,0 +1,644 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/. */ + +#include "vm/AsyncIteration.h" + +#include "jsarray.h" +#include "jscompartment.h" + +#include "builtin/Promise.h" +#include "vm/GeneratorObject.h" +#include "vm/GlobalObject.h" +#include "vm/Interpreter.h" +#include "vm/SelfHosting.h" + +#include "jscntxtinlines.h" +#include "jsobjinlines.h" + +#include "vm/NativeObject-inl.h" + +using namespace js; +using namespace js::gc; + +#define UNWRAPPED_ASYNC_WRAPPED_SLOT 1 +#define WRAPPED_ASYNC_UNWRAPPED_SLOT 0 + +// Async Iteration proposal 8.3.10 Runtime Semantics: EvaluateBody. +static bool +WrappedAsyncGenerator(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedFunction wrapped(cx, &args.callee().as<JSFunction>()); + RootedValue unwrappedVal(cx, wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT)); + RootedFunction unwrapped(cx, &unwrappedVal.toObject().as<JSFunction>()); + RootedValue thisValue(cx, args.thisv()); + + // Step 1. + RootedValue generatorVal(cx); + InvokeArgs args2(cx); + if (!args2.init(cx, argc)) + return false; + for (size_t i = 0, len = argc; i < len; i++) + args2[i].set(args[i]); + if (!Call(cx, unwrappedVal, thisValue, args2, &generatorVal)) + return false; + + // Step 2. + Rooted<AsyncGeneratorObject*> asyncGenObj( + cx, AsyncGeneratorObject::create(cx, wrapped, generatorVal)); + if (!asyncGenObj) + return false; + + // Step 3 (skipped). + // Done in AsyncGeneratorObject::create and generator. + + // Step 4. + args.rval().setObject(*asyncGenObj); + return true; +} + +JSObject* +js::WrapAsyncGeneratorWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto) +{ + MOZ_ASSERT(unwrapped->isAsync()); + MOZ_ASSERT(proto, "We need an explicit prototype to avoid the default" + "%FunctionPrototype% fallback in NewFunctionWithProto()."); + + // Create a new function with AsyncGeneratorPrototype, reusing the name and + // the length of `unwrapped`. + + RootedAtom funName(cx, unwrapped->explicitName()); + uint16_t length; + if (!JSFunction::getLength(cx, unwrapped, &length)) + return nullptr; + + RootedFunction wrapped(cx, NewFunctionWithProto(cx, WrappedAsyncGenerator, length, + JSFunction::NATIVE_FUN, nullptr, + funName, proto, + AllocKind::FUNCTION_EXTENDED, + TenuredObject)); + if (!wrapped) + return nullptr; + + if (unwrapped->hasCompileTimeName()) + wrapped->setCompileTimeName(unwrapped->compileTimeName()); + + // Link them to each other to make GetWrappedAsyncGenerator and + // GetUnwrappedAsyncGenerator work. + unwrapped->setExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped)); + wrapped->setExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT, ObjectValue(*unwrapped)); + + return wrapped; +} + +JSObject* +js::WrapAsyncGenerator(JSContext* cx, HandleFunction unwrapped) +{ + RootedObject proto(cx, GlobalObject::getOrCreateAsyncGenerator(cx, cx->global())); + if (!proto) + return nullptr; + + return WrapAsyncGeneratorWithProto(cx, unwrapped, proto); +} + +bool +js::IsWrappedAsyncGenerator(JSFunction* fun) +{ + return fun->maybeNative() == WrappedAsyncGenerator; +} + +JSFunction* +js::GetWrappedAsyncGenerator(JSFunction* unwrapped) +{ + MOZ_ASSERT(unwrapped->isAsync()); + return &unwrapped->getExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT).toObject().as<JSFunction>(); +} + +JSFunction* +js::GetUnwrappedAsyncGenerator(JSFunction* wrapped) +{ + MOZ_ASSERT(IsWrappedAsyncGenerator(wrapped)); + JSFunction* unwrapped = &wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT) + .toObject().as<JSFunction>(); + MOZ_ASSERT(unwrapped->isAsync()); + return unwrapped; +} + +// Async Iteration proposal 4.1.1 Await Fulfilled Functions. +MOZ_MUST_USE bool +js::AsyncGeneratorAwaitedFulfilled(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue value) +{ + return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Normal, value); +} + +// Async Iteration proposal 4.1.2 Await Rejected Functions. +MOZ_MUST_USE bool +js::AsyncGeneratorAwaitedRejected(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue reason) +{ + return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Throw, reason); +} + +// Async Iteration proposal 11.4.3.7 step 8.d-e. +MOZ_MUST_USE bool +js::AsyncGeneratorYieldReturnAwaitedFulfilled(JSContext* cx, + Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue value) +{ + return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Return, value); +} + +// Async Iteration proposal 11.4.3.7 step 8.d-e. +MOZ_MUST_USE bool +js::AsyncGeneratorYieldReturnAwaitedRejected(JSContext* cx, + Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue reason) +{ + return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Throw, reason); +} + +const Class AsyncFromSyncIteratorObject::class_ = { + "AsyncFromSyncIteratorObject", + JSCLASS_HAS_RESERVED_SLOTS(AsyncFromSyncIteratorObject::Slots) +}; + +// Async Iteration proposal 11.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 11.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<AsyncFromSyncIteratorObject*> asyncIter = obj.as<AsyncFromSyncIteratorObject>(); + + // Step 3. + asyncIter->setIterator(iter); + + // Step 4. + return asyncIter; +} + +// Async Iteration proposal 11.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 11.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 11.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 11.4.1.2 AsyncGenerator.prototype.next. +static bool +AsyncGeneratorNext(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-3. + return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Normal, args.get(0), + args.rval()); +} + +// Async Iteration proposal 11.4.1.3 AsyncGenerator.prototype.return. +static bool +AsyncGeneratorReturn(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-3. + return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Return, args.get(0), + args.rval()); +} + +// Async Iteration proposal 11.4.1.4 AsyncGenerator.prototype.throw. +static bool +AsyncGeneratorThrow(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-3. + return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Throw, args.get(0), + args.rval()); +} + +const Class AsyncGeneratorObject::class_ = { + "AsyncGenerator", + JSCLASS_HAS_RESERVED_SLOTS(AsyncGeneratorObject::Slots) +}; + +// ES 2017 draft 9.1.13. +template <typename ProtoGetter> +static JSObject* +OrdinaryCreateFromConstructor(JSContext* cx, HandleFunction fun, + ProtoGetter protoGetter, const Class* clasp) +{ + // Step 1 (skipped). + + // Step 2. + RootedValue protoVal(cx); + if (!GetProperty(cx, fun, fun, cx->names().prototype, &protoVal)) + return nullptr; + + RootedObject proto(cx, protoVal.isObject() ? &protoVal.toObject() : nullptr); + if (!proto) { + proto = protoGetter(cx, cx->global()); + if (!proto) + return nullptr; + } + + // Step 3. + return NewNativeObjectWithGivenProto(cx, clasp, proto); +} + +/* static */ AsyncGeneratorObject* +AsyncGeneratorObject::create(JSContext* cx, HandleFunction asyncGen, HandleValue generatorVal) +{ + MOZ_ASSERT(generatorVal.isObject()); + MOZ_ASSERT(generatorVal.toObject().is<GeneratorObject>()); + + RootedObject obj( + cx, OrdinaryCreateFromConstructor(cx, asyncGen, + GlobalObject::getOrCreateAsyncGeneratorPrototype, + &class_)); + if (!obj) + return nullptr; + + Handle<AsyncGeneratorObject*> asyncGenObj = obj.as<AsyncGeneratorObject>(); + + // Async Iteration proposal 6.4.3.2 AsyncGeneratorStart. + // Step 6. + asyncGenObj->setGenerator(generatorVal); + + // Step 7. + asyncGenObj->setSuspendedStart(); + + // Step 8. + asyncGenObj->clearSingleQueueRequest(); + + return asyncGenObj; +} + +static MOZ_MUST_USE bool +InternalEnqueue(JSContext* cx, HandleArrayObject queue, HandleValue val) +{ + uint32_t length; + if (!GetLengthProperty(cx, queue, &length)) + return false; + + if (length >= MAX_ARRAY_INDEX) { + ReportOutOfMemory(cx); + return false; + } + + if (!DefineElement(cx, queue, length, val)) + return false; + return SetLengthProperty(cx, queue, length + 1); +} + +static MOZ_MUST_USE bool +InternalDequeue(JSContext* cx, HandleArrayObject queue, MutableHandleValue val) +{ + uint32_t length; + if (!GetLengthProperty(cx, queue, &length)) + return false; + + MOZ_ASSERT(length != 0, "Queue should not be empty here"); + + if (!GetElement(cx, queue, queue, 0, val)) + return false; + + uint32_t newlength = length - 1; + RootedValue tmp(cx); + for (uint32_t i = 0; i < newlength; i++) { + if (!GetElement(cx, queue, queue, i + 1, &tmp)) + return false; + if (!DefineElement(cx, queue, i, tmp)) + return false; + } + ObjectOpResult result; + if (!DeleteElement(cx, queue, newlength, result)) + return false; + if (!result) { + RootedId id(cx, INT_TO_JSID(newlength)); + return result.reportError(cx, queue, id); + } + return SetLengthProperty(cx, queue, newlength); +} + +/* static */ MOZ_MUST_USE bool +AsyncGeneratorObject::enqueueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + Handle<AsyncGeneratorRequest*> request) +{ + if (asyncGenObj->isSingleQueue()) { + if (asyncGenObj->isSingleQueueEmpty()) { + asyncGenObj->setSingleQueueRequest(request); + return true; + } + + RootedArrayObject queue(cx, NewDenseEmptyArray(cx)); + if (!queue) + return false; + + if (!NewbornArrayPush(cx, queue, ObjectValue(*asyncGenObj->singleQueueRequest()))) + return false; + if (!NewbornArrayPush(cx, queue, ObjectValue(*request))) + return false; + + asyncGenObj->setQueue(queue); + return true; + } + + RootedArrayObject queue(cx, asyncGenObj->queue()); + RootedValue requestVal(cx, ObjectValue(*request)); + return InternalEnqueue(cx, queue, requestVal); +} + +/* static */ AsyncGeneratorRequest* +AsyncGeneratorObject::dequeueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj) +{ + if (asyncGenObj->isSingleQueue()) { + AsyncGeneratorRequest* request = asyncGenObj->singleQueueRequest(); + asyncGenObj->clearSingleQueueRequest(); + return request; + } + + RootedArrayObject queue(cx, asyncGenObj->queue()); + RootedValue requestVal(cx); + if (!InternalDequeue(cx, queue, &requestVal)) + return nullptr; + + return &requestVal.toObject().as<AsyncGeneratorRequest>(); +} + +/* static */ AsyncGeneratorRequest* +AsyncGeneratorObject::peekRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj) +{ + if (asyncGenObj->isSingleQueue()) + return asyncGenObj->singleQueueRequest(); + + RootedArrayObject queue(cx, asyncGenObj->queue()); + + RootedValue requestVal(cx); + if (!GetElement(cx, queue, queue, 0, &requestVal)) + return nullptr; + + return &requestVal.toObject().as<AsyncGeneratorRequest>(); +} + +const Class AsyncGeneratorRequest::class_ = { + "AsyncGeneratorRequest", + JSCLASS_HAS_RESERVED_SLOTS(AsyncGeneratorRequest::Slots) +}; + +// Async Iteration proposal 11.4.3.1. +/* static */ AsyncGeneratorRequest* +AsyncGeneratorRequest::create(JSContext* cx, CompletionKind completionKind_, + HandleValue completionValue_, HandleObject promise_) +{ + RootedObject obj(cx, NewNativeObjectWithGivenProto(cx, &class_, nullptr)); + if (!obj) + return nullptr; + + Handle<AsyncGeneratorRequest*> request = obj.as<AsyncGeneratorRequest>(); + request->setCompletionKind(completionKind_); + request->setCompletionValue(completionValue_); + request->setPromise(promise_); + return request; +} + +// Async Iteration proposal 11.4.3.2 AsyncGeneratorStart steps 5.d-g. +static MOZ_MUST_USE bool +AsyncGeneratorReturned(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue value) +{ + // Step 5.d. + asyncGenObj->setCompleted(); + + // Step 5.e (done in bytecode). + // Step 5.f.i (implicit). + + // Step 5.g. + return AsyncGeneratorResolve(cx, asyncGenObj, value, true); +} + +// Async Iteration proposal 11.4.3.2 AsyncGeneratorStart steps 5.d, f. +static MOZ_MUST_USE bool +AsyncGeneratorThrown(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj) +{ + // Step 5.d. + asyncGenObj->setCompleted(); + + // Not much we can do about uncatchable exceptions, so just bail. + if (!cx->isExceptionPending()) + return false; + + // Step 5.f.i. + RootedValue value(cx); + if (!GetAndClearException(cx, &value)) + return false; + + // Step 5.f.ii. + return AsyncGeneratorReject(cx, asyncGenObj, value); +} + +// Async Iteration proposal 11.4.3.7 (partially). +// Most steps are done in generator. +static MOZ_MUST_USE bool +AsyncGeneratorYield(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, HandleValue value) +{ + // Step 5 is done in bytecode. + + // Step 6. + asyncGenObj->setSuspendedYield(); + + // Step 9. + return AsyncGeneratorResolve(cx, asyncGenObj, value, false); +} + +// Async Iteration proposal 4.1 Await steps 2-9. +// Async Iteration proposal 8.2.1 yield* steps 6.a.vii, 6.b.ii.7, 6.c.ix. +// Async Iteration proposal 11.4.3.2 AsyncGeneratorStart step 5.f-g. +// Async Iteration proposal 11.4.3.5 AsyncGeneratorResumeNext +// steps 12-14, 16-20. +// Execution context switching is handled in generator. +MOZ_MUST_USE bool +js::AsyncGeneratorResume(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + CompletionKind completionKind, HandleValue argument) +{ + RootedValue generatorVal(cx, asyncGenObj->generatorVal()); + + // 11.4.3.5 steps 12-14, 16-20. + HandlePropertyName funName = completionKind == CompletionKind::Normal + ? cx->names().StarGeneratorNext + : completionKind == CompletionKind::Throw + ? cx->names().StarGeneratorThrow + : cx->names().StarGeneratorReturn; + FixedInvokeArgs<1> args(cx); + args[0].set(argument); + RootedValue result(cx); + if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &result)) { + // 11.4.3.2 step 5.d, f. + return AsyncGeneratorThrown(cx, asyncGenObj); + } + + // 4.1 steps 2-9. + if (asyncGenObj->generatorObj()->isAfterAwait()) + return AsyncGeneratorAwait(cx, asyncGenObj, result); + + // The following code corresponds to the following 3 cases: + // * yield + // * yield* + // * return + // For yield and return, property access is done on an internal result + // object and it's not observable. + // For yield*, it's done on a possibly user-provided result object, and + // it's observable. + // + // Note that IteratorComplete steps in 8.2.1 are done in bytecode. + + // 8.2.1 yield* steps 6.a.vii, 6.b.ii.7, 6.c.ix. + RootedObject resultObj(cx, &result.toObject()); + RootedValue value(cx); + + if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value)) + return false; + + if (asyncGenObj->generatorObj()->isAfterYield()) + return AsyncGeneratorYield(cx, asyncGenObj, value); + + // 11.4.3.2 step 5.d-g. + return AsyncGeneratorReturned(cx, asyncGenObj, value); +} + +static const JSFunctionSpec async_iterator_proto_methods[] = { + JS_SELF_HOSTED_SYM_FN(asyncIterator, "AsyncIteratorIdentity", 0, 0), + 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), + JS_FN("return", AsyncGeneratorReturn, 1, 0), + JS_FS_END +}; + +/* static */ MOZ_MUST_USE bool +GlobalObject::initAsyncGenerators(JSContext* cx, Handle<GlobalObject*> global) +{ + if (global->getReservedSlot(ASYNC_ITERATOR_PROTO).isObject()) + return true; + + // Async Iteration proposal 11.1.2 %AsyncIteratorPrototype%. + RootedObject asyncIterProto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global)); + if (!asyncIterProto) + return false; + if (!DefinePropertiesAndFunctions(cx, asyncIterProto, nullptr, async_iterator_proto_methods)) + return false; + + // Async Iteration proposal 11.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 11.4.1 %AsyncGeneratorPrototype%. + RootedObject asyncGenProto( + cx, GlobalObject::createBlankPrototypeInheriting(cx, global, &PlainObject::class_, + asyncIterProto)); + if (!asyncGenProto) + return false; + if (!DefinePropertiesAndFunctions(cx, asyncGenProto, nullptr, async_generator_methods) || + !DefineToStringTag(cx, asyncGenProto, cx->names().AsyncGenerator)) + { + return false; + } + + // Async Iteration proposal 11.3.3 %AsyncGenerator%. + RootedObject asyncGenerator(cx, NewSingletonObjectWithFunctionPrototype(cx, global)); + if (!asyncGenerator) + return false; + if (!JSObject::setDelegate(cx, asyncGenerator)) + return false; + if (!LinkConstructorAndPrototype(cx, asyncGenerator, asyncGenProto, JSPROP_READONLY, + JSPROP_READONLY) || + !DefineToStringTag(cx, asyncGenerator, cx->names().AsyncGeneratorFunction)) + { + return false; + } + + RootedValue function(cx, global->getConstructor(JSProto_Function)); + if (!function.toObjectOrNull()) + return false; + RootedObject proto(cx, &function.toObject()); + RootedAtom name(cx, cx->names().AsyncGeneratorFunction); + + // Async Iteration proposal 11.3.2 %AsyncGeneratorFunction%. + RootedObject asyncGenFunction( + cx, NewFunctionWithProto(cx, AsyncGeneratorConstructor, 1, JSFunction::NATIVE_CTOR, + nullptr, name, proto, gc::AllocKind::FUNCTION, SingletonObject)); + if (!asyncGenFunction) + return false; + if (!LinkConstructorAndPrototype(cx, asyncGenFunction, asyncGenerator, + JSPROP_PERMANENT | JSPROP_READONLY, JSPROP_READONLY)) + { + return false; + } + + 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)); + return true; +} diff --git a/js/src/vm/AsyncIteration.h b/js/src/vm/AsyncIteration.h new file mode 100644 index 000000000..58c43131b --- /dev/null +++ b/js/src/vm/AsyncIteration.h @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jscntxt.h" +#include "jsobj.h" + +#include "builtin/Promise.h" +#include "vm/GeneratorObject.h" + +namespace js { + +// Async generator consists of 2 functions, |wrapped| and |unwrapped|. +// |unwrapped| is a generator function compiled from async generator script, +// |await| behaves just like |yield| there. |unwrapped| isn't exposed to user +// script. +// |wrapped| is a native function that is the value of async generator. + +JSObject* +WrapAsyncGeneratorWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto); + +JSObject* +WrapAsyncGenerator(JSContext* cx, HandleFunction unwrapped); + +bool +IsWrappedAsyncGenerator(JSFunction* fun); + +JSFunction* +GetWrappedAsyncGenerator(JSFunction* unwrapped); + +JSFunction* +GetUnwrappedAsyncGenerator(JSFunction* wrapped); + +MOZ_MUST_USE bool +AsyncGeneratorAwaitedFulfilled(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue value); +MOZ_MUST_USE bool +AsyncGeneratorAwaitedRejected(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue reason); +MOZ_MUST_USE bool +AsyncGeneratorYieldReturnAwaitedFulfilled(JSContext* cx, + Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue value); +MOZ_MUST_USE bool +AsyncGeneratorYieldReturnAwaitedRejected(JSContext* cx, + Handle<AsyncGeneratorObject*> asyncGenObj, + HandleValue reason); + +class AsyncGeneratorRequest : public NativeObject +{ + private: + enum AsyncGeneratorRequestSlots { + Slot_CompletionKind = 0, + Slot_CompletionValue, + Slot_Promise, + Slots, + }; + + void setCompletionKind(CompletionKind completionKind_) { + setFixedSlot(Slot_CompletionKind, + Int32Value(static_cast<int32_t>(completionKind_))); + } + void setCompletionValue(HandleValue completionValue_) { + setFixedSlot(Slot_CompletionValue, completionValue_); + } + void setPromise(HandleObject promise_) { + setFixedSlot(Slot_Promise, ObjectValue(*promise_)); + } + + public: + static const Class class_; + + static AsyncGeneratorRequest* + create(JSContext* cx, CompletionKind completionKind, HandleValue completionValue, + HandleObject promise); + + CompletionKind completionKind() const { + return static_cast<CompletionKind>(getFixedSlot(Slot_CompletionKind).toInt32()); + } + JS::Value completionValue() const { + return getFixedSlot(Slot_CompletionValue); + } + JSObject* promise() const { + return &getFixedSlot(Slot_Promise).toObject(); + } +}; + +class AsyncGeneratorObject : public NativeObject +{ + private: + enum AsyncGeneratorObjectSlots { + Slot_State = 0, + Slot_Generator, + Slot_QueueOrRequest, + Slots + }; + + enum State { + State_SuspendedStart, + State_SuspendedYield, + State_Executing, + // State_AwaitingYieldReturn corresponds to the case that + // AsyncGenerator#return is called while State_Executing, + // just like the case that AsyncGenerator#return is called + // while State_Completed. + State_AwaitingYieldReturn, + State_AwaitingReturn, + State_Completed + }; + + State state() const { + return static_cast<State>(getFixedSlot(Slot_State).toInt32()); + } + void setState(State state_) { + setFixedSlot(Slot_State, Int32Value(state_)); + } + + void setGenerator(const Value& value) { + setFixedSlot(Slot_Generator, value); + } + + // 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, an + // array is created and requests are pushed into it, and the array is + // stored to the slot. + + bool isSingleQueue() const { + return getFixedSlot(Slot_QueueOrRequest).isNull() || + getFixedSlot(Slot_QueueOrRequest).toObject().is<AsyncGeneratorRequest>(); + } + bool isSingleQueueEmpty() const { + return getFixedSlot(Slot_QueueOrRequest).isNull(); + } + void setSingleQueueRequest(AsyncGeneratorRequest* request) { + setFixedSlot(Slot_QueueOrRequest, ObjectValue(*request)); + } + void clearSingleQueueRequest() { + setFixedSlot(Slot_QueueOrRequest, NullHandleValue); + } + AsyncGeneratorRequest* singleQueueRequest() const { + return &getFixedSlot(Slot_QueueOrRequest).toObject().as<AsyncGeneratorRequest>(); + } + + ArrayObject* queue() const { + return &getFixedSlot(Slot_QueueOrRequest).toObject().as<ArrayObject>(); + } + void setQueue(JSObject* queue_) { + setFixedSlot(Slot_QueueOrRequest, ObjectValue(*queue_)); + } + + public: + static const Class class_; + + static AsyncGeneratorObject* + create(JSContext* cx, HandleFunction asyncGen, HandleValue generatorVal); + + 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); + } + + JS::Value generatorVal() const { + return getFixedSlot(Slot_Generator); + } + GeneratorObject* generatorObj() const { + return &getFixedSlot(Slot_Generator).toObject().as<GeneratorObject>(); + } + + static MOZ_MUST_USE bool + enqueueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + Handle<AsyncGeneratorRequest*> request); + + static AsyncGeneratorRequest* + dequeueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj); + + static AsyncGeneratorRequest* + peekRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj); + + bool isQueueEmpty() const { + if (isSingleQueue()) + return isSingleQueueEmpty(); + return queue()->length() == 0; + } +}; + +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 +AsyncGeneratorResume(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, + CompletionKind completionKind, HandleValue argument); + +} // namespace js + +#endif /* vm_AsyncIteration_h */ diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 99cb02e58..6a8afb56b 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -29,7 +29,10 @@ 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") \ macro(AsyncWrapped, AsyncWrapped, "AsyncWrapped") \ macro(async, async, "async") \ macro(await, await, "await") \ @@ -311,6 +314,7 @@ macro(star, star, "*") \ macro(starDefaultStar, starDefaultStar, "*default*") \ macro(StarGeneratorNext, StarGeneratorNext, "StarGeneratorNext") \ + macro(StarGeneratorReturn, StarGeneratorReturn, "StarGeneratorReturn") \ macro(StarGeneratorThrow, StarGeneratorThrow, "StarGeneratorThrow") \ macro(startTimestamp, startTimestamp, "startTimestamp") \ macro(state, state, "state") \ diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index d68d1b75e..2680699a4 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -32,7 +32,6 @@ #include "js/Vector.h" #include "proxy/ScriptedProxyHandler.h" #include "vm/ArgumentsObject.h" -#include "vm/AsyncFunction.h" #include "vm/DebuggerMemory.h" #include "vm/GeneratorObject.h" #include "vm/SPSProfiler.h" @@ -1560,16 +1559,11 @@ CheckResumptionValue(JSContext* cx, AbstractFramePtr frame, const Maybe<HandleVa JSTrapStatus status, MutableHandleValue vp) { if (status == JSTRAP_RETURN && frame && frame.isFunctionFrame()) { - // Don't let a { return: ... } resumption value make a generator or - // async function violate the iterator protocol. The return value from + // Don't let a { return: ... } resumption value make a generator + // function violate the iterator protocol. The return value from // such a frame must have the form { done: <bool>, value: <anything> }. RootedFunction callee(cx, frame.callee()); - if (callee->isAsync()) { - if (!CheckAsyncResumptionValue(cx, vp)) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_AWAIT); - return false; - } - } else if (callee->isStarGenerator()) { + if (callee->isStarGenerator()) { if (!CheckStarGeneratorResumptionValue(cx, vp)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_YIELD); return false; @@ -7356,7 +7350,8 @@ DebuggerFrame::getEnvironment(JSContext* cx, HandleDebuggerFrame frame, /* static */ bool DebuggerFrame::getIsGenerator(HandleDebuggerFrame frame) { - return DebuggerFrame::getReferent(frame).script()->isGenerator(); + return DebuggerFrame::getReferent(frame).script()->isStarGenerator() || + DebuggerFrame::getReferent(frame).script()->isLegacyGenerator(); } /* static */ bool diff --git a/js/src/vm/EnvironmentObject.cpp b/js/src/vm/EnvironmentObject.cpp index c95bb0597..7834940f1 100644 --- a/js/src/vm/EnvironmentObject.cpp +++ b/js/src/vm/EnvironmentObject.cpp @@ -2448,7 +2448,9 @@ DebugEnvironments::addDebugEnvironment(JSContext* cx, const EnvironmentIter& ei, MOZ_ASSERT(cx->compartment() == debugEnv->compartment()); // Generators should always have environments. MOZ_ASSERT_IF(ei.scope().is<FunctionScope>(), - !ei.scope().as<FunctionScope>().canonicalFunction()->isGenerator()); + !ei.scope().as<FunctionScope>().canonicalFunction()->isStarGenerator() && + !ei.scope().as<FunctionScope>().canonicalFunction()->isLegacyGenerator() && + !ei.scope().as<FunctionScope>().canonicalFunction()->isAsync()); if (!CanUseDebugEnvironmentMaps(cx)) return true; @@ -2594,8 +2596,11 @@ DebugEnvironments::onPopCall(JSContext* cx, AbstractFramePtr frame) if (!frame.environmentChain()->is<CallObject>()) return; - if (frame.callee()->isGenerator()) + if (frame.callee()->isStarGenerator() || frame.callee()->isLegacyGenerator() || + frame.callee()->isAsync()) + { return; + } CallObject& callobj = frame.environmentChain()->as<CallObject>(); envs->liveEnvs.remove(&callobj); @@ -2726,8 +2731,13 @@ DebugEnvironments::updateLiveEnvironments(JSContext* cx) if (frame.environmentChain()->compartment() != cx->compartment()) continue; - if (frame.isFunctionFrame() && frame.callee()->isGenerator()) - continue; + if (frame.isFunctionFrame()) { + if (frame.callee()->isStarGenerator() || frame.callee()->isLegacyGenerator() || + frame.callee()->isAsync()) + { + continue; + } + } if (!frame.isDebuggee()) continue; @@ -2882,7 +2892,8 @@ GetDebugEnvironmentForMissing(JSContext* cx, const EnvironmentIter& ei) if (ei.scope().is<FunctionScope>()) { RootedFunction callee(cx, ei.scope().as<FunctionScope>().canonicalFunction()); // Generators should always reify their scopes. - MOZ_ASSERT(!callee->isGenerator()); + MOZ_ASSERT(!callee->isStarGenerator() && !callee->isLegacyGenerator() && + !callee->isAsync()); JS::ExposeObjectToActiveJS(callee); Rooted<CallObject*> callobj(cx, CallObject::createHollowForDebug(cx, callee)); diff --git a/js/src/vm/GeneratorObject.cpp b/js/src/vm/GeneratorObject.cpp index ba28501e6..2f5c63c28 100644 --- a/js/src/vm/GeneratorObject.cpp +++ b/js/src/vm/GeneratorObject.cpp @@ -19,12 +19,13 @@ using namespace js; JSObject* GeneratorObject::create(JSContext* cx, AbstractFramePtr frame) { - MOZ_ASSERT(frame.script()->isGenerator()); + MOZ_ASSERT(frame.script()->isStarGenerator() || frame.script()->isLegacyGenerator() || + frame.script()->isAsync()); MOZ_ASSERT(frame.script()->nfixed() == 0); Rooted<GlobalObject*> global(cx, cx->global()); RootedNativeObject obj(cx); - if (frame.script()->isStarGenerator()) { + if (frame.script()->isStarGenerator() || frame.script()->isAsync()) { RootedValue pval(cx); RootedObject fun(cx, frame.callee()); // FIXME: This would be faster if we could avoid doing a lookup to get @@ -63,10 +64,14 @@ bool GeneratorObject::suspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame, jsbytecode* pc, Value* vp, unsigned nvalues) { - MOZ_ASSERT(*pc == JSOP_INITIALYIELD || *pc == JSOP_YIELD); + MOZ_ASSERT(*pc == JSOP_INITIALYIELD || *pc == JSOP_YIELD || *pc == JSOP_AWAIT); Rooted<GeneratorObject*> genObj(cx, &obj->as<GeneratorObject>()); MOZ_ASSERT(!genObj->hasExpressionStack()); + MOZ_ASSERT_IF(*pc == JSOP_AWAIT, genObj->callee().isAsync()); + MOZ_ASSERT_IF(*pc == JSOP_YIELD, + genObj->callee().isStarGenerator() || + genObj->callee().isLegacyGenerator()); if (*pc == JSOP_YIELD && genObj->isClosing() && genObj->is<LegacyGeneratorObject>()) { RootedValue val(cx, ObjectValue(*frame.callee())); @@ -74,8 +79,8 @@ GeneratorObject::suspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame return false; } - uint32_t yieldIndex = GET_UINT24(pc); - genObj->setYieldIndex(yieldIndex); + uint32_t yieldAndAwaitIndex = GET_UINT24(pc); + genObj->setYieldAndAwaitIndex(yieldAndAwaitIndex); genObj->setEnvironmentChain(*frame.environmentChain()); if (nvalues) { @@ -172,7 +177,7 @@ GeneratorObject::resume(JSContext* cx, InterpreterActivation& activation, } JSScript* script = callee->nonLazyScript(); - uint32_t offset = script->yieldOffsets()[genObj->yieldIndex()]; + uint32_t offset = script->yieldAndAwaitOffsets()[genObj->yieldAndAwaitIndex()]; activation.regs().pc = script->offsetToPC(offset); // Always push on a value, even if we are raising an exception. In the @@ -311,7 +316,8 @@ GlobalObject::initStarGenerators(JSContext* cx, Handle<GlobalObject*> global) RootedObject genFunctionProto(cx, NewSingletonObjectWithFunctionPrototype(cx, global)); if (!genFunctionProto || !JSObject::setDelegate(cx, genFunctionProto)) return false; - if (!LinkConstructorAndPrototype(cx, genFunctionProto, genObjectProto) || + if (!LinkConstructorAndPrototype(cx, genFunctionProto, genObjectProto, JSPROP_READONLY, + JSPROP_READONLY) || !DefineToStringTag(cx, genFunctionProto, cx->names().GeneratorFunction)) { return false; @@ -328,8 +334,11 @@ GlobalObject::initStarGenerators(JSContext* cx, Handle<GlobalObject*> global) SingletonObject)); if (!genFunction) return false; - if (!LinkConstructorAndPrototype(cx, genFunction, genFunctionProto)) + if (!LinkConstructorAndPrototype(cx, genFunction, genFunctionProto, + JSPROP_PERMANENT | JSPROP_READONLY, JSPROP_READONLY)) + { return false; + } global->setReservedSlot(STAR_GENERATOR_OBJECT_PROTO, ObjectValue(*genObjectProto)); global->setReservedSlot(STAR_GENERATOR_FUNCTION, ObjectValue(*genFunction)); @@ -365,3 +374,34 @@ js::CheckStarGeneratorResumptionValue(JSContext* cx, HandleValue v) return true; } + +bool +GeneratorObject::isAfterYield() +{ + return isAfterYieldOrAwait(JSOP_YIELD); +} + +bool +GeneratorObject::isAfterAwait() +{ + return isAfterYieldOrAwait(JSOP_AWAIT); +} + +bool +GeneratorObject::isAfterYieldOrAwait(JSOp op) +{ + if (isClosed() || isClosing() || isRunning()) + return false; + + JSScript* script = callee().nonLazyScript(); + jsbytecode* code = script->code(); + uint32_t nextOffset = script->yieldAndAwaitOffsets()[yieldAndAwaitIndex()]; + if (code[nextOffset] != JSOP_DEBUGAFTERYIELD) + return false; + + uint32_t offset = nextOffset - JSOP_YIELD_LENGTH; + MOZ_ASSERT(code[offset] == JSOP_INITIALYIELD || code[offset] == JSOP_YIELD || + code[offset] == JSOP_AWAIT); + + return code[offset] == op; +} diff --git a/js/src/vm/GeneratorObject.h b/js/src/vm/GeneratorObject.h index ca1452b34..f19ca2aac 100644 --- a/js/src/vm/GeneratorObject.h +++ b/js/src/vm/GeneratorObject.h @@ -21,15 +21,15 @@ class GeneratorObject : public NativeObject public: // Magic values stored in the yield index slot when the generator is // running or closing. See the yield index comment below. - static const int32_t YIELD_INDEX_RUNNING = INT32_MAX; - static const int32_t YIELD_INDEX_CLOSING = INT32_MAX - 1; + static const int32_t YIELD_AND_AWAIT_INDEX_RUNNING = INT32_MAX; + static const int32_t YIELD_AND_AWAIT_INDEX_CLOSING = INT32_MAX - 1; enum { CALLEE_SLOT = 0, ENV_CHAIN_SLOT, ARGS_OBJ_SLOT, EXPRESSION_STACK_SLOT, - YIELD_INDEX_SLOT, + YIELD_AND_AWAIT_INDEX_SLOT, NEWTARGET_SLOT, RESERVED_SLOTS }; @@ -124,47 +124,48 @@ class GeneratorObject : public NativeObject // The yield index slot is abused for a few purposes. It's undefined if // it hasn't been set yet (before the initial yield), and null if the // generator is closed. If the generator is running, the yield index is - // YIELD_INDEX_RUNNING. If the generator is in that bizarre "closing" - // state, the yield index is YIELD_INDEX_CLOSING. + // YIELD_AND_AWAIT_INDEX_RUNNING. If the generator is in that bizarre + // "closing" state, the yield index is YIELD_AND_AWAIT_INDEX_CLOSING. // // If the generator is suspended, it's the yield index (stored as - // JSOP_INITIALYIELD/JSOP_YIELD operand) of the yield instruction that - // suspended the generator. The yield index can be mapped to the bytecode - // offset (interpreter) or to the native code offset (JIT). + // JSOP_INITIALYIELD/JSOP_YIELD/JSOP_AWAIT operand) of the yield + // instruction that suspended the generator. The yield index can be mapped + // to the bytecode offset (interpreter) or to the native code offset (JIT). bool isRunning() const { MOZ_ASSERT(!isClosed()); - return getFixedSlot(YIELD_INDEX_SLOT).toInt32() == YIELD_INDEX_RUNNING; + return getFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT).toInt32() == YIELD_AND_AWAIT_INDEX_RUNNING; } bool isClosing() const { - return getFixedSlot(YIELD_INDEX_SLOT).toInt32() == YIELD_INDEX_CLOSING; + return getFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT).toInt32() == YIELD_AND_AWAIT_INDEX_CLOSING; } bool isSuspended() const { // Note: also update Baseline's IsSuspendedStarGenerator code if this // changes. MOZ_ASSERT(!isClosed()); - static_assert(YIELD_INDEX_CLOSING < YIELD_INDEX_RUNNING, - "test below should return false for YIELD_INDEX_RUNNING"); - return getFixedSlot(YIELD_INDEX_SLOT).toInt32() < YIELD_INDEX_CLOSING; + static_assert(YIELD_AND_AWAIT_INDEX_CLOSING < YIELD_AND_AWAIT_INDEX_RUNNING, + "test below should return false for YIELD_AND_AWAIT_INDEX_RUNNING"); + return getFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT).toInt32() < YIELD_AND_AWAIT_INDEX_CLOSING; } void setRunning() { MOZ_ASSERT(isSuspended()); - setFixedSlot(YIELD_INDEX_SLOT, Int32Value(YIELD_INDEX_RUNNING)); + setFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT, Int32Value(YIELD_AND_AWAIT_INDEX_RUNNING)); } void setClosing() { MOZ_ASSERT(isSuspended()); - setFixedSlot(YIELD_INDEX_SLOT, Int32Value(YIELD_INDEX_CLOSING)); - } - void setYieldIndex(uint32_t yieldIndex) { - MOZ_ASSERT_IF(yieldIndex == 0, getFixedSlot(YIELD_INDEX_SLOT).isUndefined()); - MOZ_ASSERT_IF(yieldIndex != 0, isRunning() || isClosing()); - MOZ_ASSERT(yieldIndex < uint32_t(YIELD_INDEX_CLOSING)); - setFixedSlot(YIELD_INDEX_SLOT, Int32Value(yieldIndex)); + setFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT, Int32Value(YIELD_AND_AWAIT_INDEX_CLOSING)); + } + void setYieldAndAwaitIndex(uint32_t yieldAndAwaitIndex) { + MOZ_ASSERT_IF(yieldAndAwaitIndex == 0, + getFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT).isUndefined()); + MOZ_ASSERT_IF(yieldAndAwaitIndex != 0, isRunning() || isClosing()); + MOZ_ASSERT(yieldAndAwaitIndex < uint32_t(YIELD_AND_AWAIT_INDEX_CLOSING)); + setFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT, Int32Value(yieldAndAwaitIndex)); MOZ_ASSERT(isSuspended()); } - uint32_t yieldIndex() const { + uint32_t yieldAndAwaitIndex() const { MOZ_ASSERT(isSuspended()); - return getFixedSlot(YIELD_INDEX_SLOT).toInt32(); + return getFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT).toInt32(); } bool isClosed() const { return getFixedSlot(CALLEE_SLOT).isNull(); @@ -174,10 +175,17 @@ class GeneratorObject : public NativeObject setFixedSlot(ENV_CHAIN_SLOT, NullValue()); setFixedSlot(ARGS_OBJ_SLOT, NullValue()); setFixedSlot(EXPRESSION_STACK_SLOT, NullValue()); - setFixedSlot(YIELD_INDEX_SLOT, NullValue()); + setFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT, NullValue()); setFixedSlot(NEWTARGET_SLOT, NullValue()); } + bool isAfterYield(); + bool isAfterAwait(); + +private: + bool isAfterYieldOrAwait(JSOp op); + +public: static size_t offsetOfCalleeSlot() { return getFixedSlotOffset(CALLEE_SLOT); } @@ -187,8 +195,8 @@ class GeneratorObject : public NativeObject static size_t offsetOfArgsObjSlot() { return getFixedSlotOffset(ARGS_OBJ_SLOT); } - static size_t offsetOfYieldIndexSlot() { - return getFixedSlotOffset(YIELD_INDEX_SLOT); + static size_t offsetOfYieldAndAwaitIndexSlot() { + return getFixedSlotOffset(YIELD_AND_AWAIT_INDEX_SLOT); } static size_t offsetOfExpressionStackSlot() { return getFixedSlotOffset(EXPRESSION_STACK_SLOT); diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 013208f66..70c33c647 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -586,17 +586,18 @@ GlobalObject::createBlankPrototypeInheriting(JSContext* cx, Handle<GlobalObject* } bool -js::LinkConstructorAndPrototype(JSContext* cx, JSObject* ctor_, JSObject* proto_) +js::LinkConstructorAndPrototype(JSContext* cx, JSObject* ctor_, JSObject* proto_, + unsigned prototypeAttrs, unsigned constructorAttrs) { RootedObject ctor(cx, ctor_), proto(cx, proto_); RootedValue protoVal(cx, ObjectValue(*proto)); RootedValue ctorVal(cx, ObjectValue(*ctor)); - return DefineProperty(cx, ctor, cx->names().prototype, protoVal, - nullptr, nullptr, JSPROP_PERMANENT | JSPROP_READONLY) && - DefineProperty(cx, proto, cx->names().constructor, ctorVal, - nullptr, nullptr, 0); + return DefineProperty(cx, ctor, cx->names().prototype, protoVal, nullptr, nullptr, + prototypeAttrs) && + DefineProperty(cx, proto, cx->names().constructor, ctorVal, nullptr, nullptr, + constructorAttrs); } bool diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index 9179abbb7..3fd2762f8 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -100,6 +100,11 @@ class GlobalObject : public NativeObject STAR_GENERATOR_FUNCTION, ASYNC_FUNCTION_PROTO, ASYNC_FUNCTION, + ASYNC_ITERATOR_PROTO, + ASYNC_FROM_SYNC_ITERATOR_PROTO, + ASYNC_GENERATOR, + ASYNC_GENERATOR_FUNCTION, + ASYNC_GENERATOR_PROTO, MAP_ITERATOR_PROTO, SET_ITERATOR_PROTO, COLLATOR_PROTO, @@ -624,6 +629,36 @@ class GlobalObject : public NativeObject return getOrCreateObject(cx, global, ASYNC_FUNCTION, initAsyncFunction); } + static NativeObject* + getOrCreateAsyncIteratorPrototype(JSContext* cx, Handle<GlobalObject*> global) + { + return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_ITERATOR_PROTO, + initAsyncGenerators)); + } + + static NativeObject* + getOrCreateAsyncFromSyncIteratorPrototype(JSContext* cx, Handle<GlobalObject*> global) { + return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_FROM_SYNC_ITERATOR_PROTO, + initAsyncGenerators)); + } + + static NativeObject* + getOrCreateAsyncGenerator(JSContext* cx, Handle<GlobalObject*> global) { + return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_GENERATOR, + initAsyncGenerators)); + } + + static JSObject* + getOrCreateAsyncGeneratorFunction(JSContext* cx, Handle<GlobalObject*> global) { + return getOrCreateObject(cx, global, ASYNC_GENERATOR_FUNCTION, initAsyncGenerators); + } + + static NativeObject* + getOrCreateAsyncGeneratorPrototype(JSContext* cx, Handle<GlobalObject*> global) { + return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_GENERATOR_PROTO, + initAsyncGenerators)); + } + static JSObject* getOrCreateMapIteratorPrototype(JSContext* cx, Handle<GlobalObject*> global) { return getOrCreateObject(cx, global, MAP_ITERATOR_PROTO, initMapIteratorProto); @@ -782,6 +817,8 @@ class GlobalObject : public NativeObject static bool initAsyncFunction(JSContext* cx, Handle<GlobalObject*> global); + static bool initAsyncGenerators(JSContext* cx, Handle<GlobalObject*> global); + // Implemented in builtin/MapObject.cpp. static bool initMapIteratorProto(JSContext* cx, Handle<GlobalObject*> global); static bool initSetIteratorProto(JSContext* cx, Handle<GlobalObject*> global); @@ -985,12 +1022,14 @@ GlobalObject::createArrayFromBuffer<uint8_clamped>() const } /* - * Define ctor.prototype = proto as non-enumerable, non-configurable, and - * non-writable; define proto.constructor = ctor as non-enumerable but - * configurable and writable. + * Unless otherwise specified, define ctor.prototype = proto as non-enumerable, + * non-configurable, and non-writable; and define proto.constructor = ctor as + * non-enumerable but configurable and writable. */ extern bool -LinkConstructorAndPrototype(JSContext* cx, JSObject* ctor, JSObject* proto); +LinkConstructorAndPrototype(JSContext* cx, JSObject* ctor, JSObject* proto, + unsigned prototypeAttrs = JSPROP_PERMANENT | JSPROP_READONLY, + unsigned constructorAttrs = 0); /* * Define properties and/or functions on any object. Either ps or fs, or both, diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 030f0f3b6..3cf9b57f6 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -39,6 +39,7 @@ #include "jit/Ion.h" #include "jit/IonAnalysis.h" #include "vm/AsyncFunction.h" +#include "vm/AsyncIteration.h" #include "vm/Debugger.h" #include "vm/GeneratorObject.h" #include "vm/Opcodes.h" @@ -1939,9 +1940,6 @@ CASE(EnableInterruptsPseudoOpcode) CASE(JSOP_NOP) CASE(JSOP_NOP_DESTRUCTURING) CASE(JSOP_UNUSED126) -CASE(JSOP_UNUSED192) -CASE(JSOP_UNUSED209) -CASE(JSOP_UNUSED210) CASE(JSOP_UNUSED211) CASE(JSOP_TRY_DESTRUCTURING_ITERCLOSE) CASE(JSOP_UNUSED221) @@ -3585,6 +3583,29 @@ CASE(JSOP_TOASYNC) } END_CASE(JSOP_TOASYNC) +CASE(JSOP_TOASYNCGEN) +{ + ReservedRooted<JSFunction*> unwrapped(&rootFunction0, + ®S.sp[-1].toObject().as<JSFunction>()); + JSObject* wrapped = WrapAsyncGenerator(cx, unwrapped); + if (!wrapped) + goto error; + + REGS.sp[-1].setObject(*wrapped); +} +END_CASE(JSOP_TOASYNCGEN) + +CASE(JSOP_TOASYNCITER) +{ + ReservedRooted<JSObject*> 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); @@ -3994,6 +4015,7 @@ CASE(JSOP_INITIALYIELD) } CASE(JSOP_YIELD) +CASE(JSOP_AWAIT) { MOZ_ASSERT(!cx->isExceptionPending()); MOZ_ASSERT(REGS.fp()->isFunctionFrame()); @@ -5126,6 +5148,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 9fefd75cc..5e7087f7f 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -561,7 +561,8 @@ enum class CheckIsObjectKind : uint8_t { IteratorNext, IteratorReturn, IteratorThrow, - GetIterator + GetIterator, + GetAsyncIterator }; bool diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp index ec0a7aec1..91070b3f6 100644 --- a/js/src/vm/ObjectGroup.cpp +++ b/js/src/vm/ObjectGroup.cpp @@ -184,7 +184,7 @@ ObjectGroup::useSingletonForNewObject(JSContext* cx, JSScript* script, jsbytecod * Sub2 lets us continue to distinguish the two subclasses and any extra * properties added to those prototype objects. */ - if (script->isGenerator()) + if (script->isStarGenerator() || script->isLegacyGenerator() || script->isAsync()) return false; if (JSOp(*pc) != JSOP_NEW) return false; diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 3c4d61a67..e13cd6221 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1952,7 +1952,15 @@ * Stack: this => this */ \ macro(JSOP_CHECKTHISREINIT,191,"checkthisreinit",NULL,1, 1, 1, JOF_BYTE) \ - macro(JSOP_UNUSED192, 192,"unused192", NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Pops the top of stack value as 'unwrapped', converts it to async + * generator 'wrapped', and pushes 'wrapped' back on the stack. + * Category: Statements + * Type: Generator + * Operands: + * Stack: unwrapped => wrapped + */ \ + macro(JSOP_TOASYNCGEN, 192, "toasyncgen", NULL, 1, 1, 1, JOF_BYTE) \ \ /* * Pops the top two values on the stack as 'propval' and 'obj', pushes @@ -2055,7 +2063,7 @@ * interpretation. * Category: Statements * Type: Generator - * Operands: uint24_t yieldIndex + * Operands: uint24_t yieldAndAwaitIndex * Stack: generator => */ \ macro(JSOP_INITIALYIELD, 202,"initialyield", NULL, 4, 1, 1, JOF_UINT24) \ @@ -2064,7 +2072,7 @@ * returns 'rval1'. Pushes sent value from 'send()' onto the stack. * Category: Statements * Type: Generator - * Operands: uint24_t yieldIndex + * Operands: uint24_t yieldAndAwaitIndex * Stack: rval1, gen => rval2 */ \ macro(JSOP_YIELD, 203,"yield", NULL, 4, 2, 1, JOF_UINT24) \ @@ -2119,8 +2127,24 @@ * Stack: => */ \ macro(JSOP_DEBUGAFTERYIELD, 208, "debugafteryield", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED209, 209, "unused209", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED210, 210, "unused210", NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Pops the generator and the return value 'promise', stops interpretation + * and returns 'promise'. Pushes resolved value onto the stack. + * Category: Statements + * Type: Generator + * Operands: uint24_t yieldAndAwaitIndex + * Stack: promise, gen => resolved + */ \ + macro(JSOP_AWAIT, 209, "await", NULL, 4, 2, 1, JOF_UINT24) \ + /* + * 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 diff --git a/js/src/vm/Probes-inl.h b/js/src/vm/Probes-inl.h index 347f842b8..822a8ac59 100644 --- a/js/src/vm/Probes-inl.h +++ b/js/src/vm/Probes-inl.h @@ -41,7 +41,10 @@ probes::EnterScript(JSContext* cx, JSScript* script, JSFunction* maybeFun, if (rt->spsProfiler.enabled()) { if (!rt->spsProfiler.enter(cx, script, maybeFun)) return false; - MOZ_ASSERT_IF(!fp->script()->isGenerator(), !fp->hasPushedSPSFrame()); + MOZ_ASSERT_IF(!fp->script()->isStarGenerator() && + !fp->script()->isLegacyGenerator() && + !fp->script()->isAsync(), + !fp->hasPushedSPSFrame()); fp->setPushedSPSFrame(); } diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index ffd707b14..2216bf91e 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -27,6 +27,7 @@ #include "builtin/MapObject.h" #include "builtin/ModuleObject.h" #include "builtin/Object.h" +#include "builtin/Promise.h" #include "builtin/Reflect.h" #include "builtin/SelfHostingDefines.h" #include "builtin/SIMD.h" @@ -2135,6 +2136,105 @@ intrinsic_PromiseResolve(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool +intrinsic_CreatePendingPromise(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 0); + RootedObject promise(cx, PromiseObject::createSkippingExecutor(cx)); + if (!promise) + return false; + args.rval().setObject(*promise); + return true; +} + +static bool +intrinsic_CreatePromiseResolvedWith(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + RootedObject promise(cx, PromiseObject::unforgeableResolve(cx, args[0])); + if (!promise) + return false; + args.rval().setObject(*promise); + return true; +} + +static bool +intrinsic_CreatePromiseRejectedWith(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + RootedObject promise(cx, PromiseObject::unforgeableReject(cx, args[0])); + if (!promise) + return false; + args.rval().setObject(*promise); + return true; +} + +static bool +intrinsic_ResolvePromise(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + Rooted<PromiseObject*> promise(cx, &args[0].toObject().as<PromiseObject>()); + if (!PromiseObject::resolve(cx, promise, args[1])) + return false; + args.rval().setUndefined(); + return true; +} + +static bool +intrinsic_RejectPromise(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + Rooted<PromiseObject*> promise(cx, &args[0].toObject().as<PromiseObject>()); + if (!PromiseObject::reject(cx, promise, args[1])) + return false; + args.rval().setUndefined(); + return true; +} + +static bool +intrinsic_CallOriginalPromiseThen(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() >= 2); + + RootedObject promise(cx, &args[0].toObject()); + Value val = args[1]; + RootedObject onResolvedObj(cx, val.isUndefined() ? nullptr : val.toObjectOrNull()); + val = args.get(2); + RootedObject onRejectedObj(cx, val.isUndefined() ? nullptr : val.toObjectOrNull()); + + RootedObject resultPromise(cx, JS::CallOriginalPromiseThen(cx, promise, onResolvedObj, + onRejectedObj)); + if (!resultPromise) + return false; + args.rval().setObject(*resultPromise); + return true; +} + +static bool +intrinsic_AddPromiseReactions(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() >= 2); + + RootedObject promise(cx, &args[0].toObject()); + Value val = args[1]; + RootedObject onResolvedObj(cx, val.isUndefined() ? nullptr : val.toObjectOrNull()); + val = args.get(2); + RootedObject onRejectedObj(cx, val.isUndefined() ? nullptr : val.toObjectOrNull()); + + bool result = JS::AddPromiseReactions(cx, promise, onResolvedObj, onRejectedObj); + if (!result) + return false; + args.rval().setUndefined(); + return true; +} + // The self-hosting global isn't initialized with the normal set of builtins. // Instead, individual C++-implemented functions that're required by // self-hosted code are defined as global functions. Accessing these @@ -2536,6 +2636,14 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("AddModuleNamespaceBinding", intrinsic_AddModuleNamespaceBinding, 4, 0), JS_FN("ModuleNamespaceExports", intrinsic_ModuleNamespaceExports, 1, 0), + JS_FN("CreatePendingPromise", intrinsic_CreatePendingPromise, 0, 0), + JS_FN("CreatePromiseResolvedWith", intrinsic_CreatePromiseResolvedWith, 1, 0), + JS_FN("CreatePromiseRejectedWith", intrinsic_CreatePromiseRejectedWith, 1, 0), + JS_FN("ResolvePromise", intrinsic_ResolvePromise, 2, 0), + JS_FN("RejectPromise", intrinsic_RejectPromise, 2, 0), + JS_FN("AddPromiseReactions", intrinsic_AddPromiseReactions, 3, 0), + JS_FN("CallOriginalPromiseThen", intrinsic_CallOriginalPromiseThen, 3, 0), + JS_FN("IsPromiseObject", intrinsic_IsInstanceOfBuiltin<PromiseObject>, 1, 0), JS_FN("CallPromiseMethodIfWrapped", CallNonGenericSelfhostedMethod<Is<PromiseObject>>, 2, 0), JS_FN("PromiseResolve", intrinsic_PromiseResolve, 2, 0), @@ -3005,7 +3113,8 @@ JSRuntime::cloneSelfHostedFunctionScript(JSContext* cx, HandlePropertyName name, return false; // JSFunction::generatorKind can't handle lazy self-hosted functions, so we make sure there // aren't any. - MOZ_ASSERT(!sourceFun->isGenerator()); + MOZ_ASSERT(!sourceFun->isStarGenerator() && !sourceFun->isLegacyGenerator() && + !sourceFun->isAsync()); MOZ_ASSERT(targetFun->isExtended()); MOZ_ASSERT(targetFun->isInterpretedLazy()); MOZ_ASSERT(targetFun->isSelfHostedBuiltin()); diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index 11a19d175..2f2b591e5 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -335,7 +335,7 @@ InterpreterStack::resumeGeneratorCallFrame(JSContext* cx, InterpreterRegs& regs, HandleFunction callee, HandleValue newTarget, HandleObject envChain) { - MOZ_ASSERT(callee->isGenerator()); + MOZ_ASSERT(callee->isStarGenerator() || callee->isLegacyGenerator() || callee->isAsync()); RootedScript script(cx, JSFunction::getOrCreateScript(cx, callee)); InterpreterFrame* prev = regs.fp(); jsbytecode* prevpc = regs.pc; diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index c5f2cf5f3..95940eeaf 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -270,7 +270,9 @@ InterpreterFrame::epilogue(JSContext* cx, jsbytecode* pc) UnwindAllEnvironmentsInFrame(cx, ei); if (isFunctionFrame()) { - if (!callee().isGenerator() && + if (!callee().isStarGenerator() && + !callee().isLegacyGenerator() && + !callee().isAsync() && isConstructing() && thisArgument().isObject() && returnValue().isPrimitive()) diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index 23e621344..fe04a00f2 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -693,7 +693,8 @@ class InterpreterFrame } void resumeGeneratorFrame(JSObject* envChain) { - MOZ_ASSERT(script()->isGenerator()); + MOZ_ASSERT(script()->isStarGenerator() || script()->isLegacyGenerator() || + script()->isAsync()); MOZ_ASSERT(isFunctionFrame()); flags_ |= HAS_INITIAL_ENV; envChain_ = envChain; diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index 52b8eeed1..f3eebb85e 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -8608,9 +8608,12 @@ EstablishPreconditions(ExclusiveContext* cx, AsmJSParser& parser) break; } - if (parser.pc->isGenerator()) + if (parser.pc->isStarGenerator() || parser.pc->isLegacyGenerator()) return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by generator context"); + if (parser.pc->isAsync()) + return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by async context"); + if (parser.pc->isArrowFunction()) return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by arrow function context"); |